From 157737ec742187362ae2f71c446e8b15b39b350c Mon Sep 17 00:00:00 2001 From: Hue <47919724+HueByte@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:32:49 +0200 Subject: [PATCH] docs: Update documentation for 266 files (+3 removed) Generated by AurionDocs Job ID: 1ee9340e-d835-4a82-b2b2-3acd5fdb7744 Source commit: 0343e2f --- docs/auriondocs/Agent/README.md | 18 + docs/auriondocs/Agent/repo-map.md | 201 + docs/auriondocs/Agent/symbol-graph.json | 7150 +++++++++++++++++ docs/auriondocs/Code/index.md | 453 ++ docs/auriondocs/Code/prototype/generate.js.md | 68 + docs/auriondocs/Code/prototype/palettes.js.md | 66 + docs/auriondocs/Code/prototype/patterns.js.md | 238 + docs/auriondocs/Code/prototype/play.js.md | 26 + docs/auriondocs/Code/prototype/run.js.md | 15 + .../GlobalRoutePrefixConvention.cs.md | 18 + .../InfisicalConfigurationProvider.cs.md | 19 + .../InfisicalConfigurationSource.cs.md | 11 + .../Configuration/InfisicalExtensions.cs.md | 14 + .../Configuration/LogDateEnricher.cs.md | 54 + .../Contracts/Auth/JwtResponse.cs.md | 46 + .../Contracts/Auth/LoginRequest.cs.md | 27 + .../Contracts/Auth/MeResponse.cs.md | 25 + .../Contracts/Auth/RefreshTokenRequest.cs.md | 28 + .../Contracts/Auth/RegisterRequest.cs.md | 29 + .../ContextMetricsResponse.cs.md | 47 + .../Conversations/ConversationResponse.cs.md | 51 + .../CreateConversationRequest.cs.md | 37 + .../SetConversationModeRequest.cs.md | 25 + .../UpdateConversationRequest.cs.md | 25 + .../Diagnostics/MetricEntryDto.cs.md | 71 + .../WebSearchDiagnosticsResponse.cs.md | 101 + .../Contracts/Memories/MemoryDto.cs.md | 98 + .../Contracts/Messages/MessageResponse.cs.md | 89 + .../Messages/SendMessageRequest.cs.md | 24 + .../Contracts/Models/ModelDto.cs.md | 167 + .../Contracts/Projects/ProjectResponse.cs.md | 224 + .../Sequence/GabrielSequenceResponse.cs.md | 86 + .../Sequence/SequenceCatalogResponse.cs.md | 40 + .../Controllers/AuthController.cs.md | 70 + .../Controllers/ConversationsController.cs.md | 69 + .../Controllers/DiagnosticsController.cs.md | 18 + .../Controllers/MemoriesController.cs.md | 17 + .../Controllers/ModelsController.cs.md | 44 + .../Controllers/ProjectFilesController.cs.md | 39 + .../Controllers/ProjectsController.cs.md | 71 + .../Controllers/SequenceController.cs.md | 21 + .../Gabriel.API/Identity/AuthCookies.cs.md | 22 + .../Identity/HttpContextCurrentUser.cs.md | 45 + .../Gabriel.API/Identity/IdentitySeeder.cs.md | 19 + .../Mapping/ContractMappings.cs.md | 19 + .../Middleware/GlobalExceptionHandler.cs.md | 15 + ...ireNonNullablePropertiesSchemaFilter.cs.md | 19 + .../Code/src/api/Gabriel.API/Program.cs.md | 15 + .../Configuration/AgentOptions.cs.md | 11 + .../Configuration/AgentToolsOptions.cs.md | 21 + .../Configuration/AuthOptions.cs.md | 55 + .../Configuration/BraveSearchOptions.cs.md | 12 + .../Configuration/GitHubDocsOptions.cs.md | 19 + .../Configuration/GrokOptions.cs.md | 18 + .../Configuration/IConfigSection.cs.md | 52 + .../Configuration/InfisicalOptions.cs.md | 37 + .../Configuration/JwtOptions.cs.md | 31 + .../Gabriel.Core/Configuration/LLMModel.cs.md | 19 + .../Configuration/LLMProviderOptions.cs.md | 20 + .../Configuration/LocalDocsOptions.cs.md | 19 + .../Configuration/ModelSelection.cs.md | 42 + .../Configuration/PersonalityOptions.cs.md | 31 + .../Configuration/ProjectFilesOptions.cs.md | 19 + .../Configuration/TavilySearchOptions.cs.md | 19 + .../Gabriel.Core/Configuration/ToolMode.cs.md | 43 + .../Gabriel.Core/DependencyInjection.cs.md | 24 + .../Gabriel.Core/Entities/Conversation.cs.md | 36 + .../Gabriel.Core/Entities/GabrielMode.cs.md | 26 + .../Gabriel.Core/Entities/MemoryEntry.cs.md | 38 + .../Entities/MemoryEntryType.cs.md | 45 + .../api/Gabriel.Core/Entities/Message.cs.md | 50 + .../Gabriel.Core/Entities/MessageRole.cs.md | 47 + .../Gabriel.Core/Entities/MetricEntry.cs.md | 24 + .../api/Gabriel.Core/Entities/Project.cs.md | 36 + .../Gabriel.Core/Entities/ProjectFile.cs.md | 32 + .../Exceptions/DomainException.cs.md | 18 + .../Exceptions/NotFoundException.cs.md | 25 + .../Gabriel.Core/Identity/ICurrentUser.cs.md | 19 + .../Identity/IJwtTokenService.cs.md | 75 + .../Identity/IRefreshTokenStore.cs.md | 19 + .../Identity/IUserPreferences.cs.md | 84 + .../Gabriel.Core/Identity/RefreshToken.cs.md | 37 + .../Personality/ConversationState.cs.md | 51 + .../api/Gabriel.Core/Personality/Mood.cs.md | 27 + .../IConversationRepository.cs.md | 19 + .../Repositories/IMemoryRepository.cs.md | 19 + .../Repositories/IMetricRepository.cs.md | 20 + .../Repositories/IProjectRepository.cs.md | 31 + .../Repositories/IUnitOfWork.cs.md | 25 + .../Gabriel.Core/Services/ChatService.cs.md | 60 + .../Gabriel.Core/Services/IChatService.cs.md | 100 + .../Services/IMemoryService.cs.md | 98 + .../Services/IProjectFileService.cs.md | 42 + .../Services/IProjectService.cs.md | 98 + .../Gabriel.Core/Services/MemoryService.cs.md | 49 + .../Services/ProjectService.cs.md | 66 + .../Gabriel.Engine/DependencyInjection.cs.md | 19 + .../GabrielSystemPromptBuilder.cs.md | 19 + .../HeuristicConversationStateUpdater.cs.md | 32 + .../IConversationStateUpdater.cs.md | 18 + .../Personality/IResponsePostProcessor.cs.md | 19 + .../Personality/ISystemPromptBuilder.cs.md | 19 + .../Prompts/Fragments.FewShot.cs.md | 24 + .../Prompts/Fragments.Formatting.cs.md | 23 + .../Prompts/Fragments.Memory.cs.md | 25 + .../Personality/Prompts/Fragments.Modes.cs.md | 27 + .../Prompts/Fragments.Persona.cs.md | 19 + .../Personality/Prompts/IPromptRegistry.cs.md | 25 + .../Personality/Prompts/PromptKey.cs.md | 20 + .../Personality/Prompts/PromptRegistry.cs.md | 28 + .../Personality/ResponsePostProcessor.cs.md | 19 + .../Providers/AvailableModel.cs.md | 44 + .../Providers/ChatProviderEvent.cs.md | 195 + .../Providers/ChatProviderMessage.cs.md | 33 + .../Providers/ChatProviderToolCall.cs.md | 22 + .../Providers/IChatProvider.cs.md | 19 + .../Providers/IChatProviderRegistry.cs.md | 68 + .../Providers/IModelCatalog.cs.md | 18 + .../Providers/ModelCatalog.cs.md | 19 + .../ToolBridge/GabrielToolBridge.cs.md | 366 + .../ToolBridge/ToolCallBlockParser.cs.md | 106 + .../ToolBridge/ToolCallStreamSplitter.cs.md | 35 + .../Providers/ToolDescriptor.cs.md | 27 + .../api/Gabriel.Engine/Sequence/Frame.cs.md | 44 + .../Gabriel.Engine/Sequence/FrameLayer.cs.md | 62 + .../Sequence/GabrielSequence.cs.md | 92 + .../Sequence/GabrielSequenceGenerator.cs.md | 19 + .../Sequence/GabrielSequenceService.cs.md | 28 + .../Sequence/IGabrielSequenceGenerator.cs.md | 11 + .../Sequence/IGabrielSequenceService.cs.md | 14 + .../api/Gabriel.Engine/Sequence/Noise.cs.md | 18 + .../api/Gabriel.Engine/Sequence/Palette.cs.md | 43 + .../Sequence/PaletteTemplates.cs.md | 35 + .../Gabriel.Engine/Sequence/PatternKind.cs.md | 30 + .../Gabriel.Engine/Sequence/Patterns.cs.md | 33 + .../Gabriel.Engine/Sequence/RgbColor.cs.md | 44 + .../Sequence/SequenceCatalog.cs.md | 33 + .../Services/AgentContext.cs.md | 106 + .../Gabriel.Engine/Services/AgentEvent.cs.md | 366 + .../Services/AgentService.cs.md | 767 ++ .../Services/ContextMetrics.cs.md | 66 + .../Services/IAgentService.cs.md | 32 + .../Services/IMetricRecorder.cs.md | 32 + .../Services/ITokenEstimator.cs.md | 19 + .../Services/MetricRecorder.cs.md | 19 + .../Services/NaiveTokenEstimator.cs.md | 19 + .../Tools/Calc/CalculateTool.cs.md | 29 + .../Tools/Codecs/Base64Tool.cs.md | 27 + .../Tools/Codecs/HashTool.cs.md | 29 + .../Tools/Colors/ColorConvertTool.cs.md | 37 + .../Tools/Data/JsonFormatTool.cs.md | 29 + .../Tools/Docs/DocsListTool.cs.md | 21 + .../Tools/Docs/DocsReadTool.cs.md | 18 + .../Tools/Docs/IDocsLookup.cs.md | 140 + .../Tools/Files/AgentPathResolver.cs.md | 26 + .../Tools/Files/FileInfoTool.cs.md | 29 + .../Gabriel.Engine/Tools/Files/FindTool.cs.md | 19 + .../Gabriel.Engine/Tools/Files/GrepTool.cs.md | 32 + .../Tools/Files/IAgentPathResolver.cs.md | 90 + .../Tools/Files/ListDirTool.cs.md | 27 + .../Tools/GetCurrentTimeTool.cs.md | 26 + .../src/api/Gabriel.Engine/Tools/ITool.cs.md | 38 + .../Tools/IToolExecutionContext.cs.md | 55 + .../Gabriel.Engine/Tools/IToolRegistry.cs.md | 18 + .../Tools/Memory/MemoryListTool.cs.md | 26 + .../Tools/Memory/MemoryRemoveTool.cs.md | 19 + .../Tools/Memory/MemorySaveTool.cs.md | 20 + .../Tools/Numbers/BaseConvertTool.cs.md | 27 + .../Tools/Projects/ListProjectFilesTool.cs.md | 19 + .../Tools/Projects/ReadProjectFileTool.cs.md | 19 + .../Tools/Strings/TextStatsTool.cs.md | 30 + .../Tools/Strings/TextTransformTool.cs.md | 30 + .../Gabriel.Engine/Tools/ToolRegistry.cs.md | 19 + .../Tools/Web/IUrlFetcher.cs.md | 82 + .../Gabriel.Engine/Tools/Web/IWebSearch.cs.md | 56 + .../Tools/Web/WebFetchTool.cs.md | 19 + .../Tools/Web/WebSearchTool.cs.md | 19 + .../DependencyInjection.cs.md | 240 + .../DependencyInjection/AddChatProvider.cs.md | 1 + .../DependencyInjection/AddDocsLookup.cs.md | 8 + .../AddInfrastructure.cs.md | 7 + .../DependencyInjection/AddWebFetch.cs.md | 8 + .../DependencyInjection/AddWebSearch.cs.md | 9 + .../ConfigureDdgHttpClient.cs.md | 9 + .../ConfigureGrokResilience.cs.md | 7 + .../DependencyInjection_overview.cs.md | 11 + .../Identity/ApplicationUser.cs.md | 19 + .../IdentityServiceCollectionExtensions.cs.md | 19 + .../Identity/JwtTokenService.cs.md | 61 + .../Identity/UserPreferencesService.cs.md | 18 + .../Persistence/AppDbContext.cs.md | 11 + .../ConversationConfiguration.cs.md | 19 + .../MemoryEntryConfiguration.cs.md | 18 + .../Configurations/MessageConfiguration.cs.md | 41 + .../MetricEntryConfiguration.cs.md | 19 + .../Configurations/ProjectConfiguration.cs.md | 18 + .../ProjectFileConfiguration.cs.md | 18 + .../RefreshTokenConfiguration.cs.md | 18 + .../Repositories/ConversationRepository.cs.md | 19 + .../Repositories/MemoryRepository.cs.md | 19 + .../Repositories/MetricRepository.cs.md | 37 + .../Repositories/ProjectRepository.cs.md | 40 + .../Repositories/RefreshTokenStore.cs.md | 20 + .../Persistence/UnitOfWork.cs.md | 18 + .../Projects/DiskProjectFileService.cs.md | 51 + .../Providers/GrokAuthHandler.cs.md | 18 + .../Providers/GrokChatProvider.cs.md | 21 + .../Providers/MockChatProvider.cs.md | 17 + .../Tools/Docs/CompositeDocsLookup.cs.md | 21 + .../Tools/Docs/GitHubDocsLookup.cs.md | 21 + .../Tools/Docs/LocalDocsLookup.cs.md | 35 + .../Tools/Web/BraveWebSearch.cs.md | 19 + .../Tools/Web/CompositeWebSearch.cs.md | 17 + .../Tools/Web/DuckDuckGoWebSearch.cs.md | 679 ++ .../DuckDuckGoWebSearch/BuildRequest.cs.md | 9 + .../Web/DuckDuckGoWebSearch/CleanText.cs.md | 9 + .../DetectAnomalyPage.cs.md | 11 + .../EnsureSessionAsync.cs.md | 9 + .../Web/DuckDuckGoWebSearch/FetchAsync.cs.md | 8 + .../Web/DuckDuckGoWebSearch/FirstChars.cs.md | 12 + .../Web/DuckDuckGoWebSearch/Homepage.cs.md | 14 + .../DuckDuckGoWebSearch/HtmlEndpoint.cs.md | 18 + .../HtmlResultBlockRegex.cs.md | 17 + .../HtmlSnippetRegex.cs.md | 31 + .../HtmlTitleLinkRegex.cs.md | 9 + .../DuckDuckGoWebSearch/HttpClientName.cs.md | 30 + .../DuckDuckGoWebSearch/LiteEndpoint.cs.md | 11 + .../LiteResultLinkRegex.cs.md | 9 + .../LiteSnippetRegex.cs.md | 1 + .../ParseHtmlEndpoint.cs.md | 9 + .../ParseLiteEndpoint.cs.md | 17 + .../DuckDuckGoWebSearch/ResetSession.cs.md | 9 + .../Web/DuckDuckGoWebSearch/SearchAsync.cs.md | 14 + .../DuckDuckGoWebSearch/TagStripRegex.cs.md | 8 + .../DuckDuckGoWebSearch/UnwrapRedirect.cs.md | 19 + .../Web/DuckDuckGoWebSearch/UserAgents.cs.md | 8 + .../DuckDuckGoWebSearch/_httpFactory.cs.md | 10 + .../Web/DuckDuckGoWebSearch/_logger.cs.md | 14 + .../DuckDuckGoWebSearch/_sessionLock.cs.md | 29 + .../_sessionUserAgent.cs.md | 9 + .../DuckDuckGoWebSearch/_sessionWarmed.cs.md | 28 + .../Web/DuckDuckGoWebSearch_overview.cs.md | 9 + .../Tools/Web/HttpUrlFetcher.cs.md | 36 + .../Tools/Web/InstrumentedWebSearch.cs.md | 20 + .../Tools/Web/TavilyWebSearch.cs.md | 19 + .../auriondocs/Code/src/webapp/src/App.tsx.md | 17 + .../src/webapp/src/api/authInterceptor.ts.md | 31 + .../Code/src/webapp/src/api/authRefresh.ts.md | 91 + .../src/webapp/src/api/conversationMode.ts.md | 141 + .../Code/src/webapp/src/api/memories.ts.md | 284 + .../Code/src/webapp/src/api/models.ts.md | 200 + .../Code/src/webapp/src/api/sequence.ts.md | 187 + .../Code/src/webapp/src/api/streamChat.ts.md | 241 + .../src/webapp/src/auth/AuthContext.tsx.md | 147 + .../src/webapp/src/components/Avatar.tsx.md | 107 + .../src/webapp/src/components/Chat.tsx.md | 638 ++ .../src/components/CompactingOverlay.tsx.md | 106 + .../webapp/src/components/ContextStats.tsx.md | 161 + .../src/components/GabrielSequenceView.tsx.md | 71 + .../src/components/GalacticTypewriter.tsx.md | 108 + .../src/webapp/src/components/Markdown.tsx.md | 275 + .../webapp/src/components/MemoryList.tsx.md | 152 + .../src/components/MemoryQuickSave.tsx.md | 72 + .../src/webapp/src/components/Mermaid.tsx.md | 233 + .../webapp/src/components/ModeSelector.tsx.md | 58 + .../src/components/ModelSelector.tsx.md | 79 + .../src/components/ProjectPicker.tsx.md | 291 + .../src/webapp/src/components/Sidebar.tsx.md | 92 + .../webapp/src/components/SkinPicker.tsx.md | 117 + .../src/components/StreamingText.tsx.md | 91 + .../src/components/ThinkingPulse.tsx.md | 108 + .../src/webapp/src/layouts/AuthLayout.tsx.md | 18 + .../src/webapp/src/layouts/MainLayout.tsx.md | 18 + .../Code/src/webapp/src/lib/notify.ts.md | 136 + .../Code/src/webapp/src/lib/userPrefs.ts.md | 250 + .../Code/src/webapp/src/main.tsx.md | 16 + .../Code/src/webapp/src/pages/ChatPage.tsx.md | 61 + .../webapp/src/pages/DiagnosticsPage.tsx.md | 145 + .../src/webapp/src/pages/IndexPage.tsx.md | 54 + .../src/webapp/src/pages/LoginPage.tsx.md | 63 + .../src/pages/ProjectSettingsPage.tsx.md | 73 + .../src/webapp/src/pages/RegisterPage.tsx.md | 20 + .../webapp/src/pages/UserSettingsPage.tsx.md | 21 + .../Code/src/webapp/src/pulse/palettes.ts.md | 337 + .../Code/src/webapp/src/pulse/patterns.ts.md | 413 + .../Code/src/webapp/src/pulse/rng.ts.md | 106 + .../Code/src/webapp/src/router.tsx.md | 148 + .../webapp/src/routes/ProtectedRoute.tsx.md | 18 + .../src/webapp/src/routes/PublicRoute.tsx.md | 18 + .../Code/src/webapp/src/vite-env.d.ts.md | 15 + .../Code/src/webapp/vite.config.ts.md | 23 + docs/auriondocs/README.md | 47 + docs/auriondocs/Synthesis/Architecture.md | 125 + docs/auriondocs/Synthesis/DriftReport.md | 306 + docs/auriondocs/Synthesis/Onboarding.md | 29 + .../Synthesis/Workflows/agent-tool.md | 29 + .../Synthesis/Workflows/controller.md | 51 + .../Synthesis/Workflows/repository.md | 51 + .../auriondocs/Synthesis/Workflows/service.md | 62 + .../Synthesis/authentication-tokens.md | 37 + docs/auriondocs/Synthesis/authentication.md | 81 + docs/auriondocs/Synthesis/chat-providers.md | 22 + docs/auriondocs/Synthesis/data-persistence.md | 54 + .../auriondocs/Synthesis/data-repositories.md | 34 + .../Synthesis/dependency-injection.md | 33 + docs/auriondocs/Synthesis/http-api-surface.md | 91 + .../Synthesis/infisical-configuration.md | 34 + .../Synthesis/web-search-and-fetch.md | 34 + 308 files changed, 25455 insertions(+) create mode 100644 docs/auriondocs/Agent/README.md create mode 100644 docs/auriondocs/Agent/repo-map.md create mode 100644 docs/auriondocs/Agent/symbol-graph.json create mode 100644 docs/auriondocs/Code/index.md create mode 100644 docs/auriondocs/Code/prototype/generate.js.md create mode 100644 docs/auriondocs/Code/prototype/palettes.js.md create mode 100644 docs/auriondocs/Code/prototype/patterns.js.md create mode 100644 docs/auriondocs/Code/prototype/play.js.md create mode 100644 docs/auriondocs/Code/prototype/run.js.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/AuthController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ModelsController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Identity/IdentitySeeder.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Mapping/ContractMappings.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.API/Program.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GrokOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/IConfigSection.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/JwtOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMModel.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ModelSelection.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/PersonalityOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ToolMode.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/DependencyInjection.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Conversation.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/GabrielMode.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntry.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Message.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MessageRole.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MetricEntry.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Project.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Entities/ProjectFile.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/DomainException.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/NotFoundException.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Identity/ICurrentUser.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IUserPreferences.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Personality/ConversationState.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Personality/Mood.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Services/ChatService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Services/IChatService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Services/MemoryService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Core/Services/ProjectService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/DependencyInjection.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/AvailableModel.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IModelCatalog.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ModelCatalog.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolDescriptor.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Frame.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Noise.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Palette.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PatternKind.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Patterns.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/RgbColor.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ContextMetrics.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IAgentService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IMetricRecorder.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ITokenEstimator.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/MetricRecorder.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ITool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolRegistry.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ToolRegistry.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddChatProvider.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddDocsLookup.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddInfrastructure.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebFetch.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebSearch.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureDdgHttpClient.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureGrokResilience.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection_overview.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/BuildRequest.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/CleanText.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/DetectAnomalyPage.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/EnsureSessionAsync.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FetchAsync.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FirstChars.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/Homepage.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlEndpoint.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlResultBlockRegex.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlSnippetRegex.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlTitleLinkRegex.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HttpClientName.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteEndpoint.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteResultLinkRegex.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteSnippetRegex.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseHtmlEndpoint.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseLiteEndpoint.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ResetSession.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/SearchAsync.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/TagStripRegex.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UnwrapRedirect.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UserAgents.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_httpFactory.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_logger.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionLock.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionUserAgent.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionWarmed.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch_overview.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md create mode 100644 docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md create mode 100644 docs/auriondocs/Code/src/webapp/src/App.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/api/authInterceptor.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/api/authRefresh.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/api/conversationMode.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/api/memories.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/api/models.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/api/sequence.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/api/streamChat.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/auth/AuthContext.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/Avatar.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/Chat.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/CompactingOverlay.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/ContextStats.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/GabrielSequenceView.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/GalacticTypewriter.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/Markdown.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/MemoryList.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/MemoryQuickSave.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/Mermaid.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/ModeSelector.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/ModelSelector.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/ProjectPicker.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/Sidebar.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/SkinPicker.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/StreamingText.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/components/ThinkingPulse.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/layouts/AuthLayout.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/layouts/MainLayout.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/lib/notify.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/lib/userPrefs.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/main.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pages/ChatPage.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pages/DiagnosticsPage.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pages/IndexPage.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pages/LoginPage.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pages/ProjectSettingsPage.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pages/RegisterPage.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pages/UserSettingsPage.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pulse/palettes.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pulse/patterns.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/pulse/rng.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/src/router.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/routes/ProtectedRoute.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/routes/PublicRoute.tsx.md create mode 100644 docs/auriondocs/Code/src/webapp/src/vite-env.d.ts.md create mode 100644 docs/auriondocs/Code/src/webapp/vite.config.ts.md create mode 100644 docs/auriondocs/README.md create mode 100644 docs/auriondocs/Synthesis/Architecture.md create mode 100644 docs/auriondocs/Synthesis/DriftReport.md create mode 100644 docs/auriondocs/Synthesis/Onboarding.md create mode 100644 docs/auriondocs/Synthesis/Workflows/agent-tool.md create mode 100644 docs/auriondocs/Synthesis/Workflows/controller.md create mode 100644 docs/auriondocs/Synthesis/Workflows/repository.md create mode 100644 docs/auriondocs/Synthesis/Workflows/service.md create mode 100644 docs/auriondocs/Synthesis/authentication-tokens.md create mode 100644 docs/auriondocs/Synthesis/authentication.md create mode 100644 docs/auriondocs/Synthesis/chat-providers.md create mode 100644 docs/auriondocs/Synthesis/data-persistence.md create mode 100644 docs/auriondocs/Synthesis/data-repositories.md create mode 100644 docs/auriondocs/Synthesis/dependency-injection.md create mode 100644 docs/auriondocs/Synthesis/http-api-surface.md create mode 100644 docs/auriondocs/Synthesis/infisical-configuration.md create mode 100644 docs/auriondocs/Synthesis/web-search-and-fetch.md diff --git a/docs/auriondocs/Agent/README.md b/docs/auriondocs/Agent/README.md new file mode 100644 index 0000000..edb5441 --- /dev/null +++ b/docs/auriondocs/Agent/README.md @@ -0,0 +1,18 @@ +# Agent Context Pack — HueByte/Gabriel + +Machine-oriented context for AI coding agents, generated deterministically from +the repository's symbol graph (no LLM involved). Reading this pack instead of +scanning the source tree saves tokens: one map read plus targeted doc lookups +replaces repeated file reads and greps. + +| File | What it is | When to read it | +|---|---|---| +| [repo-map.md](repo-map.md) | Subsystem map + most-connected symbols | First — orientation | +| [symbol-graph.json](symbol-graph.json) | Full queryable symbol graph (kind, file, subsystem, complexity, dependencies, doc path) | To trace exact dependencies or find a symbol | +| `../Code/**` | Per-file generated documentation | For the behaviour of a specific file | +| `../Synthesis/Architecture.md` | Cross-cutting architecture narrative + diagram | For the big picture | + +Suggested workflow: read `repo-map.md`; locate the symbols relevant to your task in +`symbol-graph.json` (each carries a `doc` path and `dependsOn` edges); open only +those docs/sources. The pack is regenerated on every full documentation run, so it +matches the commit recorded in the graph's `commit` field. diff --git a/docs/auriondocs/Agent/repo-map.md b/docs/auriondocs/Agent/repo-map.md new file mode 100644 index 0000000..625acbe --- /dev/null +++ b/docs/auriondocs/Agent/repo-map.md @@ -0,0 +1,201 @@ +# Repo Map — HueByte/Gabriel + +> Deterministic structural map generated for AI agents. Read this file for +> orientation, query [`symbol-graph.json`](symbol-graph.json) for exact +> dependencies, and open the linked per-file docs for behaviour — instead of +> scanning the source tree. + +Commit `0343e2f8a0644d9bbe0903284ea33f6974b07e5e` · 470 symbols · 250 files · 960 dependency edges + +## Subsystems + +*Structural clusters detected from the dependency graph — groups of symbols more densely wired to each other than to the rest of the codebase.* + +### src/webapp/src/components + +52 symbols across 16 files. Key symbols (by connectivity): + +- [`MermaidModule`](../Code/src/webapp/src/components/Mermaid.tsx.md) (type) — `src/webapp/src/components/Mermaid.tsx` +- [`loadMermaid`](../Code/src/webapp/src/components/Mermaid.tsx.md) (function) — `src/webapp/src/components/Mermaid.tsx` +- [`PulsePlane`](../Code/src/webapp/src/components/Avatar.tsx.md) (function) — `src/webapp/src/components/Avatar.tsx` +- [`createPulse`](../Code/src/webapp/src/components/Avatar.tsx.md) (function) — `src/webapp/src/components/Avatar.tsx` +- [`advance`](../Code/src/webapp/src/components/Chat.tsx.md) (function) — `src/webapp/src/components/Chat.tsx` +- [`lastIndexWhere`](../Code/src/webapp/src/components/Chat.tsx.md) (function) — `src/webapp/src/components/Chat.tsx` +- [`onKeyDown`](../Code/src/webapp/src/components/Chat.tsx.md) (function) — `src/webapp/src/components/Chat.tsx` +- [`onSubmit`](../Code/src/webapp/src/components/Chat.tsx.md) (function) — `src/webapp/src/components/Chat.tsx` +- *…and 44 more (see symbol-graph.json)* + +### src/api/Gabriel.Engine · AgentService + +30 symbols across 21 files. Key symbols (by connectivity): + +- [`AgentService`](../Code/src/api/Gabriel.Engine/Services/AgentService.cs.md) (class) — `src/api/Gabriel.Engine/Services/AgentService.cs` +- [`GabrielToolBridge`](../Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) (class) — `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +- [`IChatProvider`](../Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md) (interface) — `src/api/Gabriel.Engine/Providers/IChatProvider.cs` +- [`AgentContext`](../Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md) (record) — `src/api/Gabriel.Engine/Services/AgentContext.cs` +- [`GrokChatProvider`](../Code/src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md) (class) — `src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs` +- [`ApplicationUser`](../Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md) (class) — `src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs` +- [`MockChatProvider`](../Code/src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md) (class) — `src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs` +- [`LLMModel`](../Code/src/api/Gabriel.Core/Configuration/LLMModel.cs.md) (class) — `src/api/Gabriel.Core/Configuration/LLMModel.cs` +- *…and 22 more (see symbol-graph.json)* + +### src/api/Gabriel.Engine · DependencyInjection + +28 symbols across 27 files. Key symbols (by connectivity): + +- [`DependencyInjection`](../Code/src/api/Gabriel.Engine/DependencyInjection.cs.md) (class) — `src/api/Gabriel.Engine/DependencyInjection.cs` +- [`Message`](../Code/src/api/Gabriel.Core/Entities/Message.cs.md) (class) — `src/api/Gabriel.Core/Entities/Message.cs` +- [`ITool`](../Code/src/api/Gabriel.Engine/Tools/ITool.cs.md) (interface) — `src/api/Gabriel.Engine/Tools/ITool.cs` +- [`IToolExecutionContext`](../Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md) (interface) — `src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs` +- [`ProjectFile`](../Code/src/api/Gabriel.Core/Entities/ProjectFile.cs.md) (class) — `src/api/Gabriel.Core/Entities/ProjectFile.cs` +- [`MemorySaveTool`](../Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs` +- [`ListProjectFilesTool`](../Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs` +- [`IProjectFileService`](../Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md) (interface) — `src/api/Gabriel.Core/Services/IProjectFileService.cs` +- *…and 20 more (see symbol-graph.json)* + +### src/webapp/src/api + +19 symbols across 7 files. Key symbols (by connectivity): + +- [`GabrielSequence`](../Code/src/webapp/src/api/sequence.ts.md) (interface) — `src/webapp/src/api/sequence.ts` +- [`GabrielSequenceMetadata`](../Code/src/webapp/src/api/sequence.ts.md) (interface) — `src/webapp/src/api/sequence.ts` +- [`installAuthInterceptor`](../Code/src/webapp/src/api/authInterceptor.ts.md) (function) — `src/webapp/src/api/authInterceptor.ts` +- [`refreshSession`](../Code/src/webapp/src/api/authRefresh.ts.md) (function) — `src/webapp/src/api/authRefresh.ts` +- [`signalSessionExpired`](../Code/src/webapp/src/api/authRefresh.ts.md) (function) — `src/webapp/src/api/authRefresh.ts` +- [`GabrielMode`](../Code/src/webapp/src/api/conversationMode.ts.md) (type) — `src/webapp/src/api/conversationMode.ts` +- [`setConversationMode`](../Code/src/webapp/src/api/conversationMode.ts.md) (function) — `src/webapp/src/api/conversationMode.ts` +- [`withRefresh`](../Code/src/webapp/src/api/conversationMode.ts.md) (function) — `src/webapp/src/api/conversationMode.ts` +- *…and 11 more (see symbol-graph.json)* + +### src/api/Gabriel.Engine · Project + +17 symbols across 17 files. Key symbols (by connectivity): + +- [`Project`](../Code/src/api/Gabriel.Core/Entities/Project.cs.md) (class) — `src/api/Gabriel.Core/Entities/Project.cs` +- [`Conversation`](../Code/src/api/Gabriel.Core/Entities/Conversation.cs.md) (class) — `src/api/Gabriel.Core/Entities/Conversation.cs` +- [`GabrielSequenceGenerator`](../Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md) (class) — `src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs` +- [`Core`](../Code/src/webapp/src/components/CompactingOverlay.tsx.md) (function) — `src/webapp/src/components/CompactingOverlay.tsx` +- [`SequenceCatalog`](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) (class) — `src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs` +- [`GabrielSequenceService`](../Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md) (class) — `src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs` +- [`IGabrielSequenceGenerator`](../Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md) (interface) — `src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs` +- [`IConversationRepository`](../Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) (interface) — `src/api/Gabriel.Core/Repositories/IConversationRepository.cs` +- *…and 9 more (see symbol-graph.json)* + +### src/webapp/src/components · ContractMappings + +15 symbols across 9 files. Key symbols (by connectivity): + +- [`ContractMappings`](../Code/src/api/Gabriel.API/Mapping/ContractMappings.cs.md) (class) — `src/api/Gabriel.API/Mapping/ContractMappings.cs` +- [`ConversationResponse`](../Code/src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md) (record) — `src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs` +- [`ChatEntry`](../Code/src/webapp/src/components/Chat.tsx.md) (type) — `src/webapp/src/components/Chat.tsx` +- [`GabrielSequenceResponse`](../Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md) (record) — `src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs` +- [`RgbColor`](../Code/src/api/Gabriel.Engine/Sequence/RgbColor.cs.md) (record) — `src/api/Gabriel.Engine/Sequence/RgbColor.cs` +- [`historyToEntries`](../Code/src/webapp/src/components/Chat.tsx.md) (function) — `src/webapp/src/components/Chat.tsx` +- [`MessageToolCall`](../Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md) (record) — `src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs` +- [`applyAgentEvent`](../Code/src/webapp/src/components/Chat.tsx.md) (function) — `src/webapp/src/components/Chat.tsx` +- *…and 7 more (see symbol-graph.json)* + +### src/api/Gabriel.Infrastructure · DependencyInjection + +12 symbols across 11 files. Key symbols (by connectivity): + +- [`DependencyInjection`](../Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md) (class) — `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +- [`IWebSearch`](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) (interface) — `src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs` +- [`WebSearchResult`](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) (record) — `src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs` +- [`WebSearchTool`](../Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs` +- [`InstrumentedWebSearch`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md) (class) — `src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs` +- [`BraveWebSearch`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md) (class) — `src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs` +- [`TavilyWebSearch`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md) (class) — `src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs` +- [`BraveSearchOptions`](../Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md) (class) — `src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs` +- *…and 4 more (see symbol-graph.json)* + +### src/webapp/src/pulse + +12 symbols across 3 files. Key symbols (by connectivity): + +- [`paletteForSeed`](../Code/src/webapp/src/pulse/palettes.ts.md) (function) — `src/webapp/src/pulse/palettes.ts` +- [`paletteGradientCss`](../Code/src/webapp/src/pulse/palettes.ts.md) (function) — `src/webapp/src/pulse/palettes.ts` +- [`pickPalette`](../Code/src/webapp/src/pulse/palettes.ts.md) (function) — `src/webapp/src/pulse/palettes.ts` +- [`FlowParams`](../Code/src/webapp/src/pulse/patterns.ts.md) (interface) — `src/webapp/src/pulse/patterns.ts` +- [`NoiseParams`](../Code/src/webapp/src/pulse/patterns.ts.md) (interface) — `src/webapp/src/pulse/patterns.ts` +- [`fbm`](../Code/src/webapp/src/pulse/patterns.ts.md) (function) — `src/webapp/src/pulse/patterns.ts` +- [`hash2`](../Code/src/webapp/src/pulse/patterns.ts.md) (function) — `src/webapp/src/pulse/patterns.ts` +- [`smooth`](../Code/src/webapp/src/pulse/patterns.ts.md) (function) — `src/webapp/src/pulse/patterns.ts` +- *…and 4 more (see symbol-graph.json)* + +### src/webapp/src/lib + +11 symbols across 2 files. Key symbols (by connectivity): + +- [`formatError`](../Code/src/webapp/src/lib/notify.ts.md) (function) — `src/webapp/src/lib/notify.ts` +- [`notifyError`](../Code/src/webapp/src/lib/notify.ts.md) (function) — `src/webapp/src/lib/notify.ts` +- [`notifyInfo`](../Code/src/webapp/src/lib/notify.ts.md) (function) — `src/webapp/src/lib/notify.ts` +- [`notifySuccess`](../Code/src/webapp/src/lib/notify.ts.md) (function) — `src/webapp/src/lib/notify.ts` +- [`readBool`](../Code/src/webapp/src/lib/userPrefs.ts.md) (function) — `src/webapp/src/lib/userPrefs.ts` +- [`readLegacyHideReactDetails`](../Code/src/webapp/src/lib/userPrefs.ts.md) (function) — `src/webapp/src/lib/userPrefs.ts` +- [`useBoolPref`](../Code/src/webapp/src/lib/userPrefs.ts.md) (function) — `src/webapp/src/lib/userPrefs.ts` +- [`useHideThinking`](../Code/src/webapp/src/lib/userPrefs.ts.md) (function) — `src/webapp/src/lib/userPrefs.ts` +- *…and 3 more (see symbol-graph.json)* + +### src/api/Gabriel.Core · MemoryEntry + +10 symbols across 9 files. Key symbols (by connectivity): + +- [`MemoryEntry`](../Code/src/api/Gabriel.Core/Entities/MemoryEntry.cs.md) (class) — `src/api/Gabriel.Core/Entities/MemoryEntry.cs` +- [`IMemoryService`](../Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md) (interface) — `src/api/Gabriel.Core/Services/IMemoryService.cs` +- [`MemoryService`](../Code/src/api/Gabriel.Core/Services/MemoryService.cs.md) (class) — `src/api/Gabriel.Core/Services/MemoryService.cs` +- [`MemoryEntryType`](../Code/src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md) (enum) — `src/api/Gabriel.Core/Entities/MemoryEntryType.cs` +- [`IMemoryRepository`](../Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md) (interface) — `src/api/Gabriel.Core/Repositories/IMemoryRepository.cs` +- [`MemoryEntrySpec`](../Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md) (record) — `src/api/Gabriel.Core/Services/IMemoryService.cs` +- [`MemoriesController`](../Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md) (class) — `src/api/Gabriel.API/Controllers/MemoriesController.cs` +- [`MemoryRepository`](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md) (class) — `src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs` +- *…and 2 more (see symbol-graph.json)* + +### src/api/Gabriel.Engine · DomainException + +10 symbols across 8 files. Key symbols (by connectivity): + +- [`DomainException`](../Code/src/api/Gabriel.Core/Exceptions/DomainException.cs.md) (class) — `src/api/Gabriel.Core/Exceptions/DomainException.cs` +- [`AgentPathResolver`](../Code/src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs` +- [`FileInfoTool`](../Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs` +- [`FindTool`](../Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Files/FindTool.cs` +- [`GrepTool`](../Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Files/GrepTool.cs` +- [`ListDirTool`](../Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md) (class) — `src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs` +- [`IAgentPathResolver`](../Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md) (interface) — `src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs` +- [`PathRootMode`](../Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md) (enum) — `src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs` +- *…and 2 more (see symbol-graph.json)* + +### src/webapp/src/pulse · Patterns + +10 symbols across 5 files. Key symbols (by connectivity): + +- [`Patterns`](../Code/src/api/Gabriel.Engine/Sequence/Patterns.cs.md) (class) — `src/api/Gabriel.Engine/Sequence/Patterns.cs` +- [`SequenceController`](../Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md) (class) — `src/api/Gabriel.API/Controllers/SequenceController.cs` +- [`ShimmerParams`](../Code/src/webapp/src/pulse/patterns.ts.md) (interface) — `src/webapp/src/pulse/patterns.ts` +- [`SequenceCatalogResponse`](../Code/src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md) (record) — `src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs` +- [`PlasmaParams`](../Code/src/webapp/src/pulse/patterns.ts.md) (interface) — `src/webapp/src/pulse/patterns.ts` +- [`PulseParams`](../Code/src/webapp/src/pulse/patterns.ts.md) (interface) — `src/webapp/src/pulse/patterns.ts` +- [`SpiralParams`](../Code/src/webapp/src/pulse/patterns.ts.md) (interface) — `src/webapp/src/pulse/patterns.ts` +- [`WavesParams`](../Code/src/webapp/src/pulse/patterns.ts.md) (interface) — `src/webapp/src/pulse/patterns.ts` +- *…and 2 more (see symbol-graph.json)* + +*…and 56 smaller subsystems (see symbol-graph.json).* + +## Most connected symbols + +The load-bearing symbols — changes here have the widest blast radius. + +| Symbol | Kind | Used by | Uses | File | +|---|---|---|---|---| +| [`AgentService`](../Code/src/api/Gabriel.Engine/Services/AgentService.cs.md) | class | 14 | 48 | `src/api/Gabriel.Engine/Services/AgentService.cs` | +| [`DependencyInjection`](../Code/src/api/Gabriel.Engine/DependencyInjection.cs.md) | class | 0 | 53 | `src/api/Gabriel.Engine/DependencyInjection.cs` | +| [`Message`](../Code/src/api/Gabriel.Core/Entities/Message.cs.md) | class | 46 | 2 | `src/api/Gabriel.Core/Entities/Message.cs` | +| [`DependencyInjection`](../Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md) | class | 0 | 44 | `src/api/Gabriel.Infrastructure/DependencyInjection.cs` | +| [`Project`](../Code/src/api/Gabriel.Core/Entities/Project.cs.md) | class | 36 | 4 | `src/api/Gabriel.Core/Entities/Project.cs` | +| [`Conversation`](../Code/src/api/Gabriel.Core/Entities/Conversation.cs.md) | class | 23 | 7 | `src/api/Gabriel.Core/Entities/Conversation.cs` | +| [`ITool`](../Code/src/api/Gabriel.Engine/Tools/ITool.cs.md) | interface | 27 | 1 | `src/api/Gabriel.Engine/Tools/ITool.cs` | +| [`GabrielToolBridge`](../Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | class | 6 | 21 | `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` | +| [`ConversationsController`](../Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md) | class | 2 | 24 | `src/api/Gabriel.API/Controllers/ConversationsController.cs` | +| [`ConversationState`](../Code/src/api/Gabriel.Core/Personality/ConversationState.cs.md) | record | 16 | 5 | `src/api/Gabriel.Core/Personality/ConversationState.cs` | + +*Regenerated on every full documentation run; see [Agent/README.md](README.md) for how to use this pack.* \ No newline at end of file diff --git a/docs/auriondocs/Agent/symbol-graph.json b/docs/auriondocs/Agent/symbol-graph.json new file mode 100644 index 0000000..e3fefe2 --- /dev/null +++ b/docs/auriondocs/Agent/symbol-graph.json @@ -0,0 +1,7150 @@ +{ + "schema": "auriondocs.agent-graph/v1", + "repository": "HueByte/Gabriel", + "commit": "0343e2f8a0644d9bbe0903284ea33f6974b07e5e", + "grouping": "communities", + "stats": { + "symbols": 470, + "files": 250, + "edges": 960, + "subsystems": 68 + }, + "subsystems": [ + { + "label": "src/webapp/src/components", + "symbols": 52 + }, + { + "label": "src/api/Gabriel.Engine · AgentService", + "symbols": 30 + }, + { + "label": "src/api/Gabriel.Engine · DependencyInjection", + "symbols": 28 + }, + { + "label": "src/webapp/src/api", + "symbols": 19 + }, + { + "label": "src/api/Gabriel.Engine · Project", + "symbols": 17 + }, + { + "label": "src/webapp/src/components · ContractMappings", + "symbols": 15 + }, + { + "label": "src/api/Gabriel.Infrastructure · DependencyInjection", + "symbols": 12 + }, + { + "label": "src/webapp/src/pulse", + "symbols": 12 + }, + { + "label": "src/webapp/src/lib", + "symbols": 11 + }, + { + "label": "src/api/Gabriel.Core · MemoryEntry", + "symbols": 10 + }, + { + "label": "src/api/Gabriel.Engine · DomainException", + "symbols": 10 + }, + { + "label": "src/webapp/src/pulse · Patterns", + "symbols": 10 + }, + { + "label": "src/webapp/src/pulse · RGB", + "symbols": 10 + }, + { + "label": "prototype", + "symbols": 9 + }, + { + "label": "src/api/Gabriel.Core · ICurrentUser", + "symbols": 9 + }, + { + "label": "src/api/Gabriel.API · AuthController", + "symbols": 8 + }, + { + "label": "src/api/Gabriel.Engine · AgentEvent", + "symbols": 8 + }, + { + "label": "src/api/Gabriel.Engine · IDocsLookup", + "symbols": 8 + }, + { + "label": "src/api/Gabriel.Engine · ConversationState", + "symbols": 7 + }, + { + "label": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "symbols": 7 + }, + { + "label": "src/api/Gabriel.API", + "symbols": 6 + }, + { + "label": "src/api/Gabriel.API · Program", + "symbols": 6 + }, + { + "label": "src/api/Gabriel.API · ProjectResponse", + "symbols": 6 + }, + { + "label": "src/api/Gabriel.Engine · GabrielSequence", + "symbols": 6 + }, + { + "label": "src/api/Gabriel.Infrastructure · AppDbContext", + "symbols": 6 + }, + { + "label": "src/webapp/src/pages", + "symbols": 6 + }, + { + "label": "src/api/Gabriel.API · ConversationsController", + "symbols": 5 + }, + { + "label": "src/api/Gabriel.API · DiagnosticsController", + "symbols": 5 + }, + { + "label": "src/api/Gabriel.Core · IConfigSection", + "symbols": 5 + }, + { + "label": "src/api/Gabriel.Core · RefreshToken", + "symbols": 5 + }, + { + "label": "src/webapp/src/api · SequenceSource", + "symbols": 5 + }, + { + "label": "src/webapp/src/components · Avatar", + "symbols": 5 + }, + { + "label": "src/webapp/src/components · Sidebar", + "symbols": 5 + }, + { + "label": "src/api/Gabriel.API · InfisicalOptions", + "symbols": 4 + }, + { + "label": "src/api/Gabriel.Core · IUserPreferences", + "symbols": 4 + }, + { + "label": "src/api/Gabriel.Engine · IModelCatalog", + "symbols": 4 + }, + { + "label": "src/api/Gabriel.Engine · WebFetchTool", + "symbols": 4 + }, + { + "label": "src/webapp/src", + "symbols": 4 + }, + { + "label": "src/webapp/src · App", + "symbols": 4 + }, + { + "label": "src/webapp/src/api · MemoryScope", + "symbols": 4 + }, + { + "label": "src/webapp/src/api · StreamChatOptions", + "symbols": 4 + }, + { + "label": "src/webapp/src/components · ChatPage", + "symbols": 4 + }, + { + "label": "src/webapp/src/components · Pattern", + "symbols": 4 + }, + { + "label": "src/webapp/src/components · renderEntry", + "symbols": 4 + }, + { + "label": "src/api/Gabriel.API · AgentOptions", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.API · AgentUserMessagePersisted", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.API · ProjectsController", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.Core · AuthOptions", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.Core · TokenPair", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.Engine · Chat", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.Engine · ContextMetrics", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.Engine · IMetricRepository", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.Infrastructure · GabrielIdentityExtensions", + "symbols": 3 + }, + { + "label": "src/webapp/src · ErrorBoundary", + "symbols": 3 + }, + { + "label": "src/webapp/src/api · MemoryType", + "symbols": 3 + }, + { + "label": "src/webapp/src/components · ContextMetricsResponse", + "symbols": 3 + }, + { + "label": "src/webapp/src/components · Fragments", + "symbols": 3 + }, + { + "label": "src/webapp/src/components · Inline", + "symbols": 3 + }, + { + "label": "src/webapp/src/components · MessageResponse", + "symbols": 3 + }, + { + "label": "src/webapp/src/components · UserSettingsPage", + "symbols": 3 + }, + { + "label": "src/webapp/src/pages · DiagnosticsPageInner", + "symbols": 3 + }, + { + "label": "src/webapp/src/pages · LoginPage", + "symbols": 3 + }, + { + "label": "src/webapp/src/pulse · PatternName", + "symbols": 3 + }, + { + "label": "src/api/Gabriel.Engine", + "symbols": 2 + }, + { + "label": "src/webapp", + "symbols": 1 + }, + { + "label": "src/webapp/src/auth", + "symbols": 1 + }, + { + "label": "src/webapp/src/layouts", + "symbols": 1 + }, + { + "label": "src/webapp/src/routes", + "symbols": 1 + } + ], + "symbols": [ + { + "id": "prototype/generate.js::generate", + "name": "generate", + "kind": "function", + "file": "prototype/generate.js", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.199, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/prototype/generate.js.md", + "dependsOn": [] + }, + { + "id": "prototype/generate.js::rand", + "name": "rand", + "kind": "function", + "file": "prototype/generate.js", + "subsystem": "prototype", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/generate.js.md", + "dependsOn": [] + }, + { + "id": "prototype/palettes.js::pickPalette", + "name": "pickPalette", + "kind": "function", + "file": "prototype/palettes.js", + "subsystem": "prototype", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/palettes.js.md", + "dependsOn": [] + }, + { + "id": "prototype/palettes.js::sampleGradient", + "name": "sampleGradient", + "kind": "function", + "file": "prototype/palettes.js", + "subsystem": "prototype", + "complexity": 0.188, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/palettes.js.md", + "dependsOn": [] + }, + { + "id": "prototype/patterns.js::fbm", + "name": "fbm", + "kind": "function", + "file": "prototype/patterns.js", + "subsystem": "prototype", + "complexity": 0.207, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/patterns.js.md", + "dependsOn": [] + }, + { + "id": "prototype/patterns.js::hash2", + "name": "hash2", + "kind": "function", + "file": "prototype/patterns.js", + "subsystem": "prototype", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/patterns.js.md", + "dependsOn": [] + }, + { + "id": "prototype/patterns.js::pick", + "name": "pick", + "kind": "function", + "file": "prototype/patterns.js", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.14, + "inDegree": 5, + "outDegree": 0, + "doc": "Code/prototype/patterns.js.md", + "dependsOn": [] + }, + { + "id": "prototype/patterns.js::pickPattern", + "name": "pickPattern", + "kind": "function", + "file": "prototype/patterns.js", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/prototype/patterns.js.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns" + ] + }, + { + "id": "prototype/patterns.js::rand", + "name": "rand", + "kind": "function", + "file": "prototype/patterns.js", + "subsystem": "prototype", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/patterns.js.md", + "dependsOn": [] + }, + { + "id": "prototype/patterns.js::smooth", + "name": "smooth", + "kind": "function", + "file": "prototype/patterns.js", + "subsystem": "prototype", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/patterns.js.md", + "dependsOn": [] + }, + { + "id": "prototype/patterns.js::valueNoise", + "name": "valueNoise", + "kind": "function", + "file": "prototype/patterns.js", + "subsystem": "prototype", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/patterns.js.md", + "dependsOn": [] + }, + { + "id": "prototype/play.js::renderFrame", + "name": "renderFrame", + "kind": "function", + "file": "prototype/play.js", + "subsystem": "prototype", + "complexity": 0.223, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/prototype/play.js.md", + "dependsOn": [] + }, + { + "id": "prototype/run.js::run", + "name": "run", + "kind": "file", + "file": "prototype/run.js", + "subsystem": "src/api/Gabriel.API · Program", + "complexity": 0.243, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/prototype/run.js.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs::GlobalRoutePrefixConvention", + "name": "GlobalRoutePrefixConvention", + "kind": "class", + "file": "src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs", + "subsystem": "src/api/Gabriel.API · Program", + "complexity": 0.278, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs::InfisicalConfigurationProvider", + "name": "InfisicalConfigurationProvider", + "kind": "class", + "file": "src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs", + "subsystem": "src/api/Gabriel.API · InfisicalOptions", + "complexity": 0.519, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/InfisicalOptions.cs::InfisicalOptions", + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs::InfisicalConfigurationSource", + "name": "InfisicalConfigurationSource", + "kind": "class", + "file": "src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs", + "subsystem": "src/api/Gabriel.API · InfisicalOptions", + "complexity": 0.224, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs::InfisicalConfigurationProvider", + "src/api/Gabriel.Core/Configuration/InfisicalOptions.cs::InfisicalOptions" + ] + }, + { + "id": "src/api/Gabriel.API/Configuration/InfisicalExtensions.cs::InfisicalExtensions", + "name": "InfisicalExtensions", + "kind": "class", + "file": "src/api/Gabriel.API/Configuration/InfisicalExtensions.cs", + "subsystem": "src/api/Gabriel.API · InfisicalOptions", + "complexity": 0.25, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs::InfisicalConfigurationSource", + "src/api/Gabriel.Core/Configuration/InfisicalOptions.cs::InfisicalOptions", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.API/Configuration/LogDateEnricher.cs::LogDateEnricher", + "name": "LogDateEnricher", + "kind": "class", + "file": "src/api/Gabriel.API/Configuration/LogDateEnricher.cs", + "subsystem": "src/api/Gabriel.API", + "complexity": 0.202, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Configuration/LogDateEnricher.cs::LogDateEnricherExtensions", + "name": "LogDateEnricherExtensions", + "kind": "class", + "file": "src/api/Gabriel.API/Configuration/LogDateEnricher.cs", + "subsystem": "src/api/Gabriel.API", + "complexity": 0.195, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Configuration/LogDateEnricher.cs::LogDateEnricher" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs::JwtResponse", + "name": "JwtResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.242, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs::LoginRequest", + "name": "LoginRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.093, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Auth/MeResponse.cs::MeResponse", + "name": "MeResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Auth/MeResponse.cs", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.093, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs::RefreshTokenRequest", + "name": "RefreshTokenRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.086, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs::RegisterRequest", + "name": "RegisterRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.093, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs::ContextMetricsResponse", + "name": "ContextMetricsResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs", + "subsystem": "src/webapp/src/components · ContextMetricsResponse", + "complexity": 0.279, + "inDegree": 4, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContext", + "src/api/Gabriel.Engine/Services/ContextMetrics.cs::ContextMetrics" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs::ConversationResponse", + "name": "ConversationResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.335, + "inDegree": 6, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageResponse" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs::CreateConversationRequest", + "name": "CreateConversationRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs", + "subsystem": "src/api/Gabriel.API · ConversationsController", + "complexity": 0.293, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs::SetConversationModeRequest", + "name": "SetConversationModeRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs", + "subsystem": "src/api/Gabriel.API · AgentUserMessagePersisted", + "complexity": 0.086, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::send" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs::UpdateConversationRequest", + "name": "UpdateConversationRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs", + "subsystem": "src/api/Gabriel.API · ConversationsController", + "complexity": 0.086, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs::MetricEntriesResponse", + "name": "MetricEntriesResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs", + "subsystem": "src/api/Gabriel.API · DiagnosticsController", + "complexity": 0.093, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs::MetricEntryDto" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs::MetricEntryDto", + "name": "MetricEntryDto", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs", + "subsystem": "src/api/Gabriel.API · DiagnosticsController", + "complexity": 0.108, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs::WebSearchDiagnosticsResponse", + "name": "WebSearchDiagnosticsResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs", + "subsystem": "src/api/Gabriel.API · DiagnosticsController", + "complexity": 0.156, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs::WebSearchProviderStatsDto" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs::WebSearchProviderStatsDto", + "name": "WebSearchProviderStatsDto", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs", + "subsystem": "src/api/Gabriel.API · DiagnosticsController", + "complexity": 0.202, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs::MemoryDto", + "name": "MemoryDto", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.138, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs::SaveMemoryRequest", + "name": "SaveMemoryRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs", + "subsystem": "src/api/Gabriel.API", + "complexity": 0.116, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageResponse", + "name": "MessageResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs", + "subsystem": "src/webapp/src/components · MessageResponse", + "complexity": 0.315, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageToolCall" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageToolCall", + "name": "MessageToolCall", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.101, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs::SendMessageRequest", + "name": "SendMessageRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs", + "subsystem": "src/api/Gabriel.API · ConversationsController", + "complexity": 0.086, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs::ModelDto", + "name": "ModelDto", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs", + "subsystem": "src/api/Gabriel.API · AgentOptions", + "complexity": 0.237, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentOptions.cs::AgentOptions" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs::ModelsResponse", + "name": "ModelsResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs", + "subsystem": "src/api/Gabriel.API", + "complexity": 0.293, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs::SelectedModelDto", + "name": "SelectedModelDto", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs", + "subsystem": "src/api/Gabriel.API", + "complexity": 0.093, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs::SetActiveModelRequest", + "name": "SetActiveModelRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Models/ModelDto.cs", + "subsystem": "src/api/Gabriel.API", + "complexity": 0.093, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::CreateProjectRequest", + "name": "CreateProjectRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs", + "subsystem": "src/api/Gabriel.API · ProjectsController", + "complexity": 0.101, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectFileResponse", + "name": "ProjectFileResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs", + "subsystem": "src/api/Gabriel.API · ProjectResponse", + "complexity": 0.116, + "inDegree": 4, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectResponse", + "name": "ProjectResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs", + "subsystem": "src/api/Gabriel.API · ProjectResponse", + "complexity": 0.161, + "inDegree": 5, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectFileResponse" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::SetSkinRequest", + "name": "SetSkinRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs", + "subsystem": "src/webapp/src/components · Pattern", + "complexity": 0.26, + "inDegree": 2, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::UpdateProjectRequest", + "name": "UpdateProjectRequest", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs", + "subsystem": "src/api/Gabriel.API · ProjectsController", + "complexity": 0.101, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs::GabrielSequenceResponse", + "name": "GabrielSequenceResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.108, + "inDegree": 3, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs::SequenceMetadataResponse", + "src/api/Gabriel.Engine/Sequence/RgbColor.cs::RgbColor", + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs::SequenceMetadataResponse", + "name": "SequenceMetadataResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.101, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs::SequenceCatalogResponse", + "name": "SequenceCatalogResponse", + "kind": "record", + "file": "src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.177, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/AuthController.cs::AuthController", + "name": "AuthController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/AuthController.cs", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.649, + "inDegree": 2, + "outDegree": 14, + "doc": "Code/src/api/Gabriel.API/Controllers/AuthController.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs::JwtResponse", + "src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs::LoginRequest", + "src/api/Gabriel.API/Contracts/Auth/MeResponse.cs::MeResponse", + "src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs::RefreshTokenRequest", + "src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs::RegisterRequest", + "src/api/Gabriel.API/Identity/AuthCookies.cs::AuthCookies", + "src/api/Gabriel.Core/Configuration/AuthOptions.cs::AuthOptions", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::IJwtTokenService", + "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::TokenPair", + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/ConversationsController.cs::ConversationsController", + "name": "ConversationsController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/ConversationsController.cs", + "subsystem": "src/api/Gabriel.API · ConversationsController", + "complexity": 0.748, + "inDegree": 2, + "outDegree": 24, + "doc": "Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs::ContextMetricsResponse", + "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs::ConversationResponse", + "src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs::CreateConversationRequest", + "src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs::SetConversationModeRequest", + "src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs::UpdateConversationRequest", + "src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs::SendMessageRequest", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::SetSkinRequest", + "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs::GabrielSequenceResponse", + "src/api/Gabriel.Core/Configuration/PersonalityOptions.cs::PersonalityOptions", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Core/Services/IChatService.cs::IChatService", + "src/api/Gabriel.Core/Services/IProjectService.cs::IProjectService", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs::IGabrielSequenceService", + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentError", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentTextDelta", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Engine/Services/IAgentService.cs::IAgentService", + "src/webapp/src/components/CompactingOverlay.tsx::Core", + "src/webapp/src/pulse/palettes.ts::RGB", + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/DiagnosticsController.cs::DiagnosticsController", + "name": "DiagnosticsController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/DiagnosticsController.cs", + "subsystem": "src/api/Gabriel.API · DiagnosticsController", + "complexity": 0.476, + "inDegree": 0, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs::MetricEntriesResponse", + "src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs::MetricEntryDto", + "src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs::WebSearchDiagnosticsResponse", + "src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs::WebSearchProviderStatsDto", + "src/api/Gabriel.Core/Repositories/IMetricRepository.cs::IMetricRepository" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/MemoriesController.cs::MemoriesController", + "name": "MemoriesController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/MemoriesController.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.536, + "inDegree": 0, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Entities/MemoryEntryType.cs::MemoryEntryType", + "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "src/api/Gabriel.Core/Services/IMemoryService.cs::MemoryEntrySpec" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/ModelsController.cs::ModelsController", + "name": "ModelsController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/ModelsController.cs", + "subsystem": "src/api/Gabriel.Core · IUserPreferences", + "complexity": 0.442, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.API/Controllers/ModelsController.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/IUserPreferences.cs::IUserPreferences", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/ProjectFilesController.cs::ProjectFilesController", + "name": "ProjectFilesController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/ProjectFilesController.cs", + "subsystem": "src/api/Gabriel.API · ProjectResponse", + "complexity": 0.504, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectFileResponse", + "src/api/Gabriel.Core/Services/IProjectFileService.cs::IProjectFileService" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/ProjectsController.cs::ProjectsController", + "name": "ProjectsController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/ProjectsController.cs", + "subsystem": "src/api/Gabriel.API · ProjectsController", + "complexity": 0.658, + "inDegree": 1, + "outDegree": 11, + "doc": "Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::CreateProjectRequest", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectResponse", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::SetSkinRequest", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::UpdateProjectRequest", + "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs::GabrielSequenceResponse", + "src/api/Gabriel.API/Controllers/ConversationsController.cs::ConversationsController", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Services/IProjectService.cs::IProjectService", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs::IGabrielSequenceService", + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/api/Gabriel.API/Controllers/SequenceController.cs::SequenceController", + "name": "SequenceController", + "kind": "class", + "file": "src/api/Gabriel.API/Controllers/SequenceController.cs", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.275, + "inDegree": 0, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs::SequenceCatalogResponse", + "src/api/Gabriel.API/Controllers/ConversationsController.cs::ConversationsController", + "src/api/Gabriel.API/Controllers/ProjectsController.cs::ProjectsController", + "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns", + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog" + ] + }, + { + "id": "src/api/Gabriel.API/Identity/AuthCookies.cs::AuthCookies", + "name": "AuthCookies", + "kind": "class", + "file": "src/api/Gabriel.API/Identity/AuthCookies.cs", + "subsystem": "src/api/Gabriel.Core · TokenPair", + "complexity": 0.338, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::TokenPair", + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken", + "src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs::GabrielIdentityExtensions" + ] + }, + { + "id": "src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs::HttpContextCurrentUser", + "name": "HttpContextCurrentUser", + "kind": "class", + "file": "src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs", + "subsystem": "src/api/Gabriel.API · Program", + "complexity": 0.3, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser" + ] + }, + { + "id": "src/api/Gabriel.API/Identity/IdentitySeeder.cs::IdentitySeeder", + "name": "IdentitySeeder", + "kind": "class", + "file": "src/api/Gabriel.API/Identity/IdentitySeeder.cs", + "subsystem": "src/api/Gabriel.Core · AuthOptions", + "complexity": 0.248, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.API/Identity/IdentitySeeder.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AuthOptions.cs::AuthOptions", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.API/Mapping/ContractMappings.cs::ContractMappings", + "name": "ContractMappings", + "kind": "class", + "file": "src/api/Gabriel.API/Mapping/ContractMappings.cs", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.407, + "inDegree": 0, + "outDegree": 13, + "doc": "Code/src/api/Gabriel.API/Mapping/ContractMappings.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs::ContextMetricsResponse", + "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs::ConversationResponse", + "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageResponse", + "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageToolCall", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectFileResponse", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectResponse", + "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs::GabrielSequenceResponse", + "src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs::SequenceMetadataResponse", + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile", + "src/api/Gabriel.Engine/Services/ContextMetrics.cs::ContextMetrics" + ] + }, + { + "id": "src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs::GlobalExceptionHandler", + "name": "GlobalExceptionHandler", + "kind": "class", + "file": "src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs", + "subsystem": "src/api/Gabriel.API · Program", + "complexity": 0.353, + "inDegree": 1, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Program.cs::Program", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException" + ] + }, + { + "id": "src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs::RequireNonNullablePropertiesSchemaFilter", + "name": "RequireNonNullablePropertiesSchemaFilter", + "kind": "class", + "file": "src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs", + "subsystem": "src/api/Gabriel.API · Program", + "complexity": 0.43, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.API/Program.cs::Program", + "name": "Program", + "kind": "file", + "file": "src/api/Gabriel.API/Program.cs", + "subsystem": "src/api/Gabriel.API · Program", + "complexity": 0.186, + "inDegree": 1, + "outDegree": 13, + "doc": "Code/src/api/Gabriel.API/Program.cs.md", + "dependsOn": [ + "prototype/run.js::run", + "src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs::GlobalRoutePrefixConvention", + "src/api/Gabriel.API/Controllers/AuthController.cs::AuthController", + "src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs::HttpContextCurrentUser", + "src/api/Gabriel.API/Identity/IdentitySeeder.cs::IdentitySeeder", + "src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs::GlobalExceptionHandler", + "src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs::RequireNonNullablePropertiesSchemaFilter", + "src/api/Gabriel.Core/Configuration/AuthOptions.cs::AuthOptions", + "src/api/Gabriel.Core/Configuration/InfisicalOptions.cs::InfisicalOptions", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/AgentOptions.cs::AgentOptions", + "name": "AgentOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/AgentOptions.cs", + "subsystem": "src/api/Gabriel.API · AgentOptions", + "complexity": 0.336, + "inDegree": 5, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/AgentOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs::AgentToolsOptions", + "name": "AgentToolsOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.403, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/AuthOptions.cs::AuthOptions", + "name": "AuthOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/AuthOptions.cs", + "subsystem": "src/api/Gabriel.Core · AuthOptions", + "complexity": 0.398, + "inDegree": 3, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AuthOptions.cs::SeedUserOptions", + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/AuthOptions.cs::SeedUserOptions", + "name": "SeedUserOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/AuthOptions.cs", + "subsystem": "src/api/Gabriel.Core · AuthOptions", + "complexity": 0.388, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs::BraveSearchOptions", + "name": "BraveSearchOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.274, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs::GitHubDocsOptions", + "name": "GitHubDocsOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs", + "subsystem": "src/api/Gabriel.Core · IConfigSection", + "complexity": 0.398, + "inDegree": 3, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/GrokOptions.cs::GrokOptions", + "name": "GrokOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/GrokOptions.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.188, + "inDegree": 3, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Configuration/GrokOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection", + "src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs::LLMProviderOptions" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/IConfigSection.cs::ConfigSectionExtensions", + "name": "ConfigSectionExtensions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/IConfigSection.cs", + "subsystem": "src/api/Gabriel.Core · IConfigSection", + "complexity": 0.202, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/IConfigSection.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection", + "name": "IConfigSection", + "kind": "interface", + "file": "src/api/Gabriel.Core/Configuration/IConfigSection.cs", + "subsystem": "src/api/Gabriel.Core · IConfigSection", + "complexity": 0.214, + "inDegree": 13, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Core/Configuration/IConfigSection.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Core/Configuration/InfisicalOptions.cs::InfisicalOptions", + "name": "InfisicalOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/InfisicalOptions.cs", + "subsystem": "src/api/Gabriel.API · InfisicalOptions", + "complexity": 0.468, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/JwtOptions.cs::JwtOptions", + "name": "JwtOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/JwtOptions.cs", + "subsystem": "src/api/Gabriel.Infrastructure · GabrielIdentityExtensions", + "complexity": 0.373, + "inDegree": 2, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Configuration/JwtOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection", + "src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs::RefreshTokenStore" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/LLMModel.cs::LLMModel", + "name": "LLMModel", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/LLMModel.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.553, + "inDegree": 6, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Core/Configuration/LLMModel.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentOptions.cs::AgentOptions", + "src/api/Gabriel.Core/Configuration/ToolMode.cs::ToolMode", + "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs::GabrielToolBridge", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs::LLMProviderOptions", + "name": "LLMProviderOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.436, + "inDegree": 2, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/LLMModel.cs::LLMModel", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::IChatProviderRegistry", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs::LocalDocsOptions", + "name": "LocalDocsOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs", + "subsystem": "src/api/Gabriel.Core · IConfigSection", + "complexity": 0.314, + "inDegree": 2, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs::GitHubDocsOptions", + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/ModelSelection.cs::ModelSelection", + "name": "ModelSelection", + "kind": "record", + "file": "src/api/Gabriel.Core/Configuration/ModelSelection.cs", + "subsystem": "src/api/Gabriel.Engine · IModelCatalog", + "complexity": 0.116, + "inDegree": 3, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Configuration/ModelSelection.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/ToolMode.cs::ToolMode", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/PersonalityOptions.cs::PersonalityOptions", + "name": "PersonalityOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/PersonalityOptions.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "complexity": 0.328, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/PersonalityOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs::ProjectFilesOptions", + "name": "ProjectFilesOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs", + "subsystem": "src/api/Gabriel.Core · IConfigSection", + "complexity": 0.293, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs::TavilySearchOptions", + "name": "TavilySearchOptions", + "kind": "class", + "file": "src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.444, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/IConfigSection.cs::IConfigSection" + ] + }, + { + "id": "src/api/Gabriel.Core/Configuration/ToolMode.cs::ToolMode", + "name": "ToolMode", + "kind": "enum", + "file": "src/api/Gabriel.Core/Configuration/ToolMode.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.075, + "inDegree": 5, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Core/Configuration/ToolMode.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/LLMModel.cs::LLMModel", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ToolCallReadyEvent", + "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs::GabrielToolBridge", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService" + ] + }, + { + "id": "src/api/Gabriel.Core/DependencyInjection.cs::DependencyInjection", + "name": "DependencyInjection", + "kind": "class", + "file": "src/api/Gabriel.Core/DependencyInjection.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.195, + "inDegree": 0, + "outDegree": 6, + "doc": "Code/src/api/Gabriel.Core/DependencyInjection.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Services/ChatService.cs::ChatService", + "src/api/Gabriel.Core/Services/IChatService.cs::IChatService", + "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "src/api/Gabriel.Core/Services/IProjectService.cs::IProjectService", + "src/api/Gabriel.Core/Services/MemoryService.cs::MemoryService", + "src/api/Gabriel.Core/Services/ProjectService.cs::ProjectService" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "name": "Conversation", + "kind": "class", + "file": "src/api/Gabriel.Core/Entities/Conversation.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.681, + "inDegree": 23, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Core/Entities/Conversation.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/GabrielMode.cs::GabrielMode", + "name": "GabrielMode", + "kind": "enum", + "file": "src/api/Gabriel.Core/Entities/GabrielMode.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "complexity": 0.1, + "inDegree": 0, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Core/Entities/GabrielMode.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs::GabrielSystemPromptBuilder", + "src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs::PromptKey", + "src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs::PromptRegistry" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "name": "MemoryEntry", + "kind": "class", + "file": "src/api/Gabriel.Core/Entities/MemoryEntry.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.586, + "inDegree": 10, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Entities/MemoryEntry.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntryType.cs::MemoryEntryType", + "src/api/Gabriel.Core/Entities/Project.cs::Project" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/MemoryEntryType.cs::MemoryEntryType", + "name": "MemoryEntryType", + "kind": "enum", + "file": "src/api/Gabriel.Core/Entities/MemoryEntryType.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.093, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/Message.cs::Message", + "name": "Message", + "kind": "class", + "file": "src/api/Gabriel.Core/Entities/Message.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.623, + "inDegree": 46, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Entities/Message.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/webapp/src/components/Chat.tsx::Reasoning" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "name": "MessageRole", + "kind": "enum", + "file": "src/api/Gabriel.Core/Entities/MessageRole.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.026, + "inDegree": 9, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Entities/MessageRole.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/MetricEntry.cs::MetricEntry", + "name": "MetricEntry", + "kind": "class", + "file": "src/api/Gabriel.Core/Entities/MetricEntry.cs", + "subsystem": "src/api/Gabriel.Infrastructure · AppDbContext", + "complexity": 0.358, + "inDegree": 6, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Core/Entities/MetricEntry.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Core/Entities/Project.cs::Project", + "name": "Project", + "kind": "class", + "file": "src/api/Gabriel.Core/Entities/Project.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.608, + "inDegree": 36, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Core/Entities/Project.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile", + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile", + "name": "ProjectFile", + "kind": "class", + "file": "src/api/Gabriel.Core/Entities/ProjectFile.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.473, + "inDegree": 8, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Core/Entities/ProjectFile.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "name": "DomainException", + "kind": "class", + "file": "src/api/Gabriel.Core/Exceptions/DomainException.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.306, + "inDegree": 9, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Core/Exceptions/DomainException.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException", + "name": "NotFoundException", + "kind": "class", + "file": "src/api/Gabriel.Core/Exceptions/NotFoundException.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.297, + "inDegree": 7, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Core/Exceptions/NotFoundException.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "name": "ICurrentUser", + "kind": "interface", + "file": "src/api/Gabriel.Core/Identity/ICurrentUser.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.258, + "inDegree": 12, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Identity/ICurrentUser.cs.md", + "dependsOn": [ + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::IJwtTokenService", + "name": "IJwtTokenService", + "kind": "interface", + "file": "src/api/Gabriel.Core/Identity/IJwtTokenService.cs", + "subsystem": "src/api/Gabriel.Core · TokenPair", + "complexity": 0.452, + "inDegree": 3, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::TokenPair" + ] + }, + { + "id": "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::TokenPair", + "name": "TokenPair", + "kind": "record", + "file": "src/api/Gabriel.Core/Identity/IJwtTokenService.cs", + "subsystem": "src/api/Gabriel.Core · TokenPair", + "complexity": 0.108, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken" + ] + }, + { + "id": "src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs::IRefreshTokenStore", + "name": "IRefreshTokenStore", + "kind": "interface", + "file": "src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs", + "subsystem": "src/api/Gabriel.Core · RefreshToken", + "complexity": 0.436, + "inDegree": 3, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs::JwtTokenService" + ] + }, + { + "id": "src/api/Gabriel.Core/Identity/IUserPreferences.cs::IUserPreferences", + "name": "IUserPreferences", + "kind": "interface", + "file": "src/api/Gabriel.Core/Identity/IUserPreferences.cs", + "subsystem": "src/api/Gabriel.Core · IUserPreferences", + "complexity": 0.266, + "inDegree": 4, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Core/Identity/IUserPreferences.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Identity/IUserPreferences.cs::UserPreferences", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService" + ] + }, + { + "id": "src/api/Gabriel.Core/Identity/IUserPreferences.cs::UserPreferences", + "name": "UserPreferences", + "kind": "record", + "file": "src/api/Gabriel.Core/Identity/IUserPreferences.cs", + "subsystem": "src/api/Gabriel.Core · IUserPreferences", + "complexity": 0.26, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Core/Identity/IUserPreferences.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken", + "name": "RefreshToken", + "kind": "class", + "file": "src/api/Gabriel.Core/Identity/RefreshToken.cs", + "subsystem": "src/api/Gabriel.Core · RefreshToken", + "complexity": 0.564, + "inDegree": 10, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md", + "dependsOn": [ + "src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs::JwtTokenService" + ] + }, + { + "id": "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "name": "ConversationState", + "kind": "record", + "file": "src/api/Gabriel.Core/Personality/ConversationState.cs", + "subsystem": "src/api/Gabriel.Engine · ConversationState", + "complexity": 0.393, + "inDegree": 16, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Core/Personality/ConversationState.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Personality/Mood.cs::Mood", + "src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs::IConversationStateUpdater", + "src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs::IResponsePostProcessor", + "src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs::ISystemPromptBuilder" + ] + }, + { + "id": "src/api/Gabriel.Core/Personality/Mood.cs::Mood", + "name": "Mood", + "kind": "enum", + "file": "src/api/Gabriel.Core/Personality/Mood.cs", + "subsystem": "src/api/Gabriel.Engine · ConversationState", + "complexity": 0.077, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Personality/Mood.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs::ISystemPromptBuilder" + ] + }, + { + "id": "src/api/Gabriel.Core/Repositories/IConversationRepository.cs::IConversationRepository", + "name": "IConversationRepository", + "kind": "interface", + "file": "src/api/Gabriel.Core/Repositories/IConversationRepository.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.53, + "inDegree": 5, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.Core/Repositories/IMemoryRepository.cs::IMemoryRepository", + "name": "IMemoryRepository", + "kind": "interface", + "file": "src/api/Gabriel.Core/Repositories/IMemoryRepository.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.599, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry" + ] + }, + { + "id": "src/api/Gabriel.Core/Repositories/IMetricRepository.cs::IMetricRepository", + "name": "IMetricRepository", + "kind": "interface", + "file": "src/api/Gabriel.Core/Repositories/IMetricRepository.cs", + "subsystem": "src/api/Gabriel.Engine · IMetricRepository", + "complexity": 0.461, + "inDegree": 5, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MetricEntry.cs::MetricEntry", + "src/api/Gabriel.Engine/Services/IMetricRecorder.cs::IMetricRecorder" + ] + }, + { + "id": "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "name": "IProjectRepository", + "kind": "interface", + "file": "src/api/Gabriel.Core/Repositories/IProjectRepository.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.501, + "inDegree": 7, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project" + ] + }, + { + "id": "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "name": "IUnitOfWork", + "kind": "interface", + "file": "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs", + "subsystem": "src/api/Gabriel.Core · RefreshToken", + "complexity": 0.333, + "inDegree": 9, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Core/Services/ChatService.cs::ChatService", + "name": "ChatService", + "kind": "class", + "file": "src/api/Gabriel.Core/Services/ChatService.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.722, + "inDegree": 1, + "outDegree": 10, + "doc": "Code/src/api/Gabriel.Core/Services/ChatService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Repositories/IConversationRepository.cs::IConversationRepository", + "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Core/Services/IChatService.cs::IChatService", + "src/api/Gabriel.Core/Services/IProjectService.cs::IProjectService" + ] + }, + { + "id": "src/api/Gabriel.Core/Services/IChatService.cs::IChatService", + "name": "IChatService", + "kind": "interface", + "file": "src/api/Gabriel.Core/Services/IChatService.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.624, + "inDegree": 3, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Core/Services/IChatService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Services/IProjectService.cs::IProjectService", + "src/api/Gabriel.Engine/Services/IAgentService.cs::IAgentService" + ] + }, + { + "id": "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "name": "IMemoryService", + "kind": "interface", + "file": "src/api/Gabriel.Core/Services/IMemoryService.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.539, + "inDegree": 7, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Repositories/IMemoryRepository.cs::IMemoryRepository", + "src/api/Gabriel.Core/Services/IMemoryService.cs::MemoryEntrySpec" + ] + }, + { + "id": "src/api/Gabriel.Core/Services/IMemoryService.cs::MemoryEntrySpec", + "name": "MemoryEntrySpec", + "kind": "record", + "file": "src/api/Gabriel.Core/Services/IMemoryService.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.116, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntryType.cs::MemoryEntryType" + ] + }, + { + "id": "src/api/Gabriel.Core/Services/IProjectFileService.cs::IProjectFileService", + "name": "IProjectFileService", + "kind": "interface", + "file": "src/api/Gabriel.Core/Services/IProjectFileService.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.532, + "inDegree": 6, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile" + ] + }, + { + "id": "src/api/Gabriel.Core/Services/IProjectService.cs::IProjectService", + "name": "IProjectService", + "kind": "interface", + "file": "src/api/Gabriel.Core/Services/IProjectService.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.638, + "inDegree": 6, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Core/Services/IProjectService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project" + ] + }, + { + "id": "src/api/Gabriel.Core/Services/MemoryService.cs::MemoryService", + "name": "MemoryService", + "kind": "class", + "file": "src/api/Gabriel.Core/Services/MemoryService.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.573, + "inDegree": 1, + "outDegree": 6, + "doc": "Code/src/api/Gabriel.Core/Services/MemoryService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Repositories/IMemoryRepository.cs::IMemoryRepository", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "src/api/Gabriel.Core/Services/IMemoryService.cs::MemoryEntrySpec" + ] + }, + { + "id": "src/api/Gabriel.Core/Services/ProjectService.cs::ProjectService", + "name": "ProjectService", + "kind": "class", + "file": "src/api/Gabriel.Core/Services/ProjectService.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.72, + "inDegree": 1, + "outDegree": 6, + "doc": "Code/src/api/Gabriel.Core/Services/ProjectService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Core/Services/IProjectService.cs::IProjectService" + ] + }, + { + "id": "src/api/Gabriel.Engine/DependencyInjection.cs::DependencyInjection", + "name": "DependencyInjection", + "kind": "class", + "file": "src/api/Gabriel.Engine/DependencyInjection.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.202, + "inDegree": 0, + "outDegree": 53, + "doc": "Code/src/api/Gabriel.Engine/DependencyInjection.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentOptions.cs::AgentOptions", + "src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs::AgentToolsOptions", + "src/api/Gabriel.Core/Configuration/PersonalityOptions.cs::PersonalityOptions", + "src/api/Gabriel.Core/Repositories/IMetricRepository.cs::IMetricRepository", + "src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs::GabrielSystemPromptBuilder", + "src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs::HeuristicConversationStateUpdater", + "src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs::IConversationStateUpdater", + "src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs::IResponsePostProcessor", + "src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs::ISystemPromptBuilder", + "src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs::IPromptRegistry", + "src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs::PromptRegistry", + "src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs::ResponsePostProcessor", + "src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs::GabrielSequenceGenerator", + "src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs::GabrielSequenceService", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs::IGabrielSequenceGenerator", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs::IGabrielSequenceService", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Engine/Services/IAgentService.cs::IAgentService", + "src/api/Gabriel.Engine/Services/IMetricRecorder.cs::IMetricRecorder", + "src/api/Gabriel.Engine/Services/ITokenEstimator.cs::ITokenEstimator", + "src/api/Gabriel.Engine/Services/MetricRecorder.cs::MetricRecorder", + "src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs::NaiveTokenEstimator", + "src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs::CalculateTool", + "src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs::Base64Tool", + "src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs::HashTool", + "src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs::ColorConvertTool", + "src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs::JsonFormatTool", + "src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs::DocsListTool", + "src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs::DocsReadTool", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup", + "src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs::AgentPathResolver", + "src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs::FileInfoTool", + "src/api/Gabriel.Engine/Tools/Files/FindTool.cs::FindTool", + "src/api/Gabriel.Engine/Tools/Files/GrepTool.cs::GrepTool", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::IAgentPathResolver", + "src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs::ListDirTool", + "src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs::GetCurrentTimeTool", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::ToolExecutionContext", + "src/api/Gabriel.Engine/Tools/IToolRegistry.cs::IToolRegistry", + "src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs::MemoryListTool", + "src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs::MemoryRemoveTool", + "src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs::MemorySaveTool", + "src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs::BaseConvertTool", + "src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs::ListProjectFilesTool", + "src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs::ReadProjectFileTool", + "src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs::TextStatsTool", + "src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs::TextTransformTool", + "src/api/Gabriel.Engine/Tools/ToolRegistry.cs::ToolRegistry", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs::WebFetchTool", + "src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs::WebSearchTool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs::GabrielSystemPromptBuilder", + "name": "GabrielSystemPromptBuilder", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "complexity": 0.452, + "inDegree": 3, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/PersonalityOptions.cs::PersonalityOptions", + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Core/Personality/Mood.cs::Mood", + "src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs::ISystemPromptBuilder", + "src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs::IPromptRegistry", + "src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs::PromptKey" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs::HeuristicConversationStateUpdater", + "name": "HeuristicConversationStateUpdater", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs", + "subsystem": "src/api/Gabriel.Engine · ConversationState", + "complexity": 0.604, + "inDegree": 1, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Core/Personality/Mood.cs::Mood", + "src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs::IConversationStateUpdater", + "src/api/Gabriel.Engine/Services/ITokenEstimator.cs::ITokenEstimator" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs::IConversationStateUpdater", + "name": "IConversationStateUpdater", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs", + "subsystem": "src/api/Gabriel.Engine · ConversationState", + "complexity": 0.229, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs::IResponsePostProcessor", + "name": "IResponsePostProcessor", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs", + "subsystem": "src/api/Gabriel.Engine · ConversationState", + "complexity": 0.229, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs::ISystemPromptBuilder", + "name": "ISystemPromptBuilder", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs", + "subsystem": "src/api/Gabriel.Engine · ConversationState", + "complexity": 0.229, + "inDegree": 5, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs::Fragments", + "name": "Fragments", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs", + "subsystem": "src/api/Gabriel.Engine · Chat", + "complexity": 0.388, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::Chat" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs::Fragments", + "name": "Fragments", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs", + "subsystem": "src/webapp/src/components · Fragments", + "complexity": 0.343, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::Chat", + "src/webapp/src/components/Markdown.tsx::Inline", + "src/webapp/src/components/Mermaid.tsx::Mermaid" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs::Fragments", + "name": "Fragments", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs", + "subsystem": "src/api/Gabriel.Engine", + "complexity": 0.375, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs::Fragments", + "name": "Fragments", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs", + "subsystem": "src/api/Gabriel.Engine · Chat", + "complexity": 0.383, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/webapp/src/components/Chat.tsx::Chat" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs::Fragments", + "name": "Fragments", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "complexity": 0.364, + "inDegree": 0, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/PersonalityOptions.cs::PersonalityOptions", + "src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs::GabrielSystemPromptBuilder", + "src/webapp/src/components/Chat.tsx::Chat", + "src/webapp/src/components/Markdown.tsx::Inline" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs::IPromptRegistry", + "name": "IPromptRegistry", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "complexity": 0.333, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs::PromptKey", + "name": "PromptKey", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "complexity": 0.494, + "inDegree": 3, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs::PromptRegistry", + "name": "PromptRegistry", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSystemPromptBuilder", + "complexity": 0.232, + "inDegree": 2, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs::IPromptRegistry", + "src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs::PromptKey" + ] + }, + { + "id": "src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs::ResponsePostProcessor", + "name": "ResponsePostProcessor", + "kind": "class", + "file": "src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs", + "subsystem": "src/api/Gabriel.Engine · ConversationState", + "complexity": 0.382, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs::IResponsePostProcessor" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/AvailableModel.cs::AvailableModel", + "name": "AvailableModel", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/AvailableModel.cs", + "subsystem": "src/api/Gabriel.Engine · IModelCatalog", + "complexity": 0.153, + "inDegree": 2, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Providers/AvailableModel.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/ToolMode.cs::ToolMode", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent", + "name": "ChatProviderEvent", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.078, + "inDegree": 8, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishEvent", + "name": "FinishEvent", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.286, + "inDegree": 4, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishReason" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishReason", + "name": "FinishReason", + "kind": "enum", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.026, + "inDegree": 5, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ReasoningDeltaEvent", + "name": "ReasoningDeltaEvent", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.16, + "inDegree": 3, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent", + "src/webapp/src/components/Chat.tsx::Reasoning" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::TextDeltaEvent", + "name": "TextDeltaEvent", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.086, + "inDegree": 5, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ToolCallReadyEvent", + "name": "ToolCallReadyEvent", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.101, + "inDegree": 5, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs::ChatProviderMessage", + "name": "ChatProviderMessage", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.156, + "inDegree": 6, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs::ChatProviderToolCall", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs::ChatProviderToolCall", + "name": "ChatProviderToolCall", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.101, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "name": "IChatProvider", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Providers/IChatProvider.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.368, + "inDegree": 13, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/LLMModel.cs::LLMModel", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs::ChatProviderMessage", + "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::IChatProviderRegistry", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog", + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::ChatProviderRegistry", + "name": "ChatProviderRegistry", + "kind": "class", + "file": "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.275, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::IChatProviderRegistry" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::IChatProviderRegistry", + "name": "IChatProviderRegistry", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.243, + "inDegree": 5, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog", + "name": "IModelCatalog", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Providers/IModelCatalog.cs", + "subsystem": "src/api/Gabriel.Engine · IModelCatalog", + "complexity": 0.298, + "inDegree": 10, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Providers/IModelCatalog.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/ModelSelection.cs::ModelSelection", + "src/api/Gabriel.Engine/Providers/AvailableModel.cs::AvailableModel", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ModelCatalog.cs::ModelCatalog", + "name": "ModelCatalog", + "kind": "class", + "file": "src/api/Gabriel.Engine/Providers/ModelCatalog.cs", + "subsystem": "src/api/Gabriel.Engine · IModelCatalog", + "complexity": 0.339, + "inDegree": 1, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Engine/Providers/ModelCatalog.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/ModelSelection.cs::ModelSelection", + "src/api/Gabriel.Core/Configuration/ToolMode.cs::ToolMode", + "src/api/Gabriel.Engine/Providers/AvailableModel.cs::AvailableModel", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs::GabrielToolBridge", + "name": "GabrielToolBridge", + "kind": "class", + "file": "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.093, + "inDegree": 6, + "outDegree": 21, + "doc": "Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/LLMModel.cs::LLMModel", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishReason", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ReasoningDeltaEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::TextDeltaEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ToolCallReadyEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs::ChatProviderMessage", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ParsedToolCall", + "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ToolCallBlockParser", + "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ToolCallParseException", + "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs::ToolCallStreamSplitter", + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContext", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/webapp/src/components/Chat.tsx::Reasoning", + "src/webapp/src/components/Markdown.tsx::Inline" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ParsedToolCall", + "name": "ParsedToolCall", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.092, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ToolCallBlockParser", + "name": "ToolCallBlockParser", + "kind": "class", + "file": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.318, + "inDegree": 1, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs::GabrielToolBridge", + "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ParsedToolCall", + "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ToolCallParseException", + "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs::ToolCallStreamSplitter", + "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContext", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs::ToolCallParseException", + "name": "ToolCallParseException", + "kind": "class", + "file": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.29, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs::GabrielToolBridge" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs::ToolCallStreamSplitter", + "name": "ToolCallStreamSplitter", + "kind": "class", + "file": "src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.401, + "inDegree": 2, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::TextDeltaEvent", + "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs::GabrielToolBridge" + ] + }, + { + "id": "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "name": "ToolDescriptor", + "kind": "record", + "file": "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.101, + "inDegree": 8, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Providers/ToolDescriptor.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/Frame.cs::Frame", + "name": "Frame", + "kind": "record", + "file": "src/api/Gabriel.Engine/Sequence/Frame.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSequence", + "complexity": 0.188, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Sequence/Frame.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/FrameLayer.cs::FrameLayer", + "name": "FrameLayer", + "kind": "enum", + "file": "src/api/Gabriel.Engine/Sequence/FrameLayer.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSequence", + "complexity": 0.026, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/FrameLayer.cs::FrameLayers", + "name": "FrameLayers", + "kind": "class", + "file": "src/api/Gabriel.Engine/Sequence/FrameLayer.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSequence", + "complexity": 0.268, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Sequence/FrameLayer.cs::FrameLayer", + "src/webapp/src/pulse/patterns.ts::range" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/GabrielSequence.cs::GabrielSequence", + "name": "GabrielSequence", + "kind": "record", + "file": "src/api/Gabriel.Engine/Sequence/GabrielSequence.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSequence", + "complexity": 0.228, + "inDegree": 0, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Engine/Sequence/Frame.cs::Frame", + "src/api/Gabriel.Engine/Sequence/FrameLayer.cs::FrameLayer", + "src/api/Gabriel.Engine/Sequence/FrameLayer.cs::FrameLayers", + "src/api/Gabriel.Engine/Sequence/GabrielSequence.cs::SequenceMetadata", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs::IGabrielSequenceGenerator" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/GabrielSequence.cs::SequenceMetadata", + "name": "SequenceMetadata", + "kind": "record", + "file": "src/api/Gabriel.Engine/Sequence/GabrielSequence.cs", + "subsystem": "src/api/Gabriel.Engine · GabrielSequence", + "complexity": 0.101, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs::GabrielSequenceGenerator", + "name": "GabrielSequenceGenerator", + "kind": "class", + "file": "src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.503, + "inDegree": 1, + "outDegree": 18, + "doc": "Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md", + "dependsOn": [ + "prototype/generate.js::generate", + "prototype/patterns.js::pick", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Core/Personality/Mood.cs::Mood", + "src/api/Gabriel.Engine/Sequence/Frame.cs::Frame", + "src/api/Gabriel.Engine/Sequence/GabrielSequence.cs::SequenceMetadata", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs::IGabrielSequenceGenerator", + "src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs::PaletteTemplates", + "src/api/Gabriel.Engine/Sequence/PatternKind.cs::PatternKind", + "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns", + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "src/webapp/src/components/CompactingOverlay.tsx::Core", + "src/webapp/src/pulse/patterns.ts::Pattern", + "src/webapp/src/pulse/patterns.ts::PlasmaParams", + "src/webapp/src/pulse/patterns.ts::PulseParams", + "src/webapp/src/pulse/patterns.ts::ShimmerParams", + "src/webapp/src/pulse/patterns.ts::SpiralParams", + "src/webapp/src/pulse/patterns.ts::WavesParams" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs::GabrielSequenceService", + "name": "GabrielSequenceService", + "kind": "class", + "file": "src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.445, + "inDegree": 1, + "outDegree": 10, + "doc": "Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md", + "dependsOn": [ + "prototype/generate.js::generate", + "prototype/patterns.js::pick", + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Repositories/IConversationRepository.cs::IConversationRepository", + "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs::IGabrielSequenceGenerator", + "src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs::IGabrielSequenceService" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs::IGabrielSequenceGenerator", + "name": "IGabrielSequenceGenerator", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.308, + "inDegree": 4, + "outDegree": 6, + "doc": "Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md", + "dependsOn": [ + "prototype/generate.js::generate", + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs::IGabrielSequenceService", + "name": "IGabrielSequenceService", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.266, + "inDegree": 4, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/Noise.cs::Noise", + "name": "Noise", + "kind": "class", + "file": "src/api/Gabriel.Engine/Sequence/Noise.cs", + "subsystem": "src/api/Gabriel.Engine", + "complexity": 0.343, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Sequence/Noise.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/Palette.cs::Palette", + "name": "Palette", + "kind": "record", + "file": "src/api/Gabriel.Engine/Sequence/Palette.cs", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.185, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Sequence/Palette.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Sequence/RgbColor.cs::RgbColor" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs::PaletteTemplates", + "name": "PaletteTemplates", + "kind": "class", + "file": "src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.424, + "inDegree": 2, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs.md", + "dependsOn": [ + "prototype/patterns.js::pick", + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Engine/Sequence/RgbColor.cs::RgbColor", + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/PatternKind.cs::PatternKind", + "name": "PatternKind", + "kind": "enum", + "file": "src/api/Gabriel.Engine/Sequence/PatternKind.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.137, + "inDegree": 2, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Sequence/PatternKind.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns", + "name": "Patterns", + "kind": "class", + "file": "src/api/Gabriel.Engine/Sequence/Patterns.cs", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.62, + "inDegree": 7, + "outDegree": 8, + "doc": "Code/src/api/Gabriel.Engine/Sequence/Patterns.cs.md", + "dependsOn": [ + "prototype/patterns.js::pick", + "src/webapp/src/pulse/patterns.ts::Pattern", + "src/webapp/src/pulse/patterns.ts::PlasmaParams", + "src/webapp/src/pulse/patterns.ts::PulseParams", + "src/webapp/src/pulse/patterns.ts::ShimmerParams", + "src/webapp/src/pulse/patterns.ts::SpiralParams", + "src/webapp/src/pulse/patterns.ts::WavesParams", + "src/webapp/src/pulse/patterns.ts::range" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/RgbColor.cs::RgbColor", + "name": "RgbColor", + "kind": "record", + "file": "src/api/Gabriel.Engine/Sequence/RgbColor.cs", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.171, + "inDegree": 3, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Sequence/RgbColor.cs.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs::SequenceCatalog", + "name": "SequenceCatalog", + "kind": "class", + "file": "src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.433, + "inDegree": 8, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs::PaletteTemplates", + "src/api/Gabriel.Engine/Sequence/PatternKind.cs::PatternKind", + "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContext", + "name": "AgentContext", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentContext.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.383, + "inDegree": 5, + "outDegree": 11, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs::ChatProviderMessage", + "src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs::ChatProviderToolCall", + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContextBreakdown", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Engine/Services/ITokenEstimator.cs::ITokenEstimator", + "src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs::NaiveTokenEstimator" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContextBreakdown", + "name": "AgentContextBreakdown", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentContext.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.189, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContext", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Engine/Services/ContextMetrics.cs::ContextMetrics" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentAssistantMessage", + "name": "AgentAssistantMessage", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.301, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentCompactDone", + "name": "AgentCompactDone", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.204, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentCompactStart" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentCompactStart", + "name": "AgentCompactStart", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.301, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentDone", + "name": "AgentDone", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.078, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentError", + "name": "AgentError", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · ContextMetrics", + "complexity": 0.086, + "inDegree": 4, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentEvent", + "name": "AgentEvent", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.126, + "inDegree": 0, + "outDegree": 11, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentAssistantMessage", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentCompactDone", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentCompactStart", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentDone", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentError", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentReasoningDelta", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentTextDelta", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentToolCall", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentToolResult", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentUserMessagePersisted", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentReasoningDelta", + "name": "AgentReasoningDelta", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.219, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentTextDelta", + "name": "AgentTextDelta", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.API · ConversationsController", + "complexity": 0.086, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentToolCall", + "name": "AgentToolCall", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.108, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentToolResult", + "name": "AgentToolResult", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.Engine · AgentEvent", + "complexity": 0.101, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentUserMessagePersisted", + "name": "AgentUserMessagePersisted", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/AgentEvent.cs", + "subsystem": "src/api/Gabriel.API · AgentUserMessagePersisted", + "complexity": 0.197, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "name": "AgentService", + "kind": "class", + "file": "src/api/Gabriel.Engine/Services/AgentService.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.206, + "inDegree": 14, + "outDegree": 48, + "doc": "Code/src/api/Gabriel.Engine/Services/AgentService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentOptions.cs::AgentOptions", + "src/api/Gabriel.Core/Configuration/ModelSelection.cs::ModelSelection", + "src/api/Gabriel.Core/Configuration/ToolMode.cs::ToolMode", + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Identity/IUserPreferences.cs::IUserPreferences", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Core/Repositories/IConversationRepository.cs::IConversationRepository", + "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs::IConversationStateUpdater", + "src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs::IResponsePostProcessor", + "src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs::ISystemPromptBuilder", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishReason", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ReasoningDeltaEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::TextDeltaEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ToolCallReadyEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs::ChatProviderMessage", + "src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs::ChatProviderToolCall", + "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::IChatProviderRegistry", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog", + "src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs::GabrielToolBridge", + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "src/api/Gabriel.Engine/Services/AgentContext.cs::AgentContext", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentAssistantMessage", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentCompactDone", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentCompactStart", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentDone", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentError", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentReasoningDelta", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentTextDelta", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentToolCall", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentToolResult", + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentUserMessagePersisted", + "src/api/Gabriel.Engine/Services/ContextMetrics.cs::ContextMetrics", + "src/api/Gabriel.Engine/Services/IAgentService.cs::IAgentService", + "src/api/Gabriel.Engine/Services/ITokenEstimator.cs::ITokenEstimator", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext", + "src/api/Gabriel.Engine/Tools/IToolRegistry.cs::IToolRegistry", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/ContextMetrics.cs::ContextMetrics", + "name": "ContextMetrics", + "kind": "record", + "file": "src/api/Gabriel.Engine/Services/ContextMetrics.cs", + "subsystem": "src/api/Gabriel.Engine · ContextMetrics", + "complexity": 0.237, + "inDegree": 5, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Services/ContextMetrics.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Engine/Services/IAgentService.cs::IAgentService" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/IAgentService.cs::IAgentService", + "name": "IAgentService", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Services/IAgentService.cs", + "subsystem": "src/api/Gabriel.Engine · ContextMetrics", + "complexity": 0.389, + "inDegree": 5, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Services/IAgentService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentError", + "src/api/Gabriel.Engine/Services/ContextMetrics.cs::ContextMetrics" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/IMetricRecorder.cs::IMetricRecorder", + "name": "IMetricRecorder", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Services/IMetricRecorder.cs", + "subsystem": "src/api/Gabriel.Engine · IMetricRepository", + "complexity": 0.237, + "inDegree": 5, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Services/IMetricRecorder.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Services/ITokenEstimator.cs::ITokenEstimator", + "name": "ITokenEstimator", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Services/ITokenEstimator.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.418, + "inDegree": 5, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Services/ITokenEstimator.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/MetricRecorder.cs::MetricRecorder", + "name": "MetricRecorder", + "kind": "class", + "file": "src/api/Gabriel.Engine/Services/MetricRecorder.cs", + "subsystem": "src/api/Gabriel.Engine · IMetricRepository", + "complexity": 0.35, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Services/MetricRecorder.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MetricEntry.cs::MetricEntry", + "src/api/Gabriel.Core/Repositories/IMetricRepository.cs::IMetricRepository", + "src/api/Gabriel.Engine/Services/IMetricRecorder.cs::IMetricRecorder" + ] + }, + { + "id": "src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs::NaiveTokenEstimator", + "name": "NaiveTokenEstimator", + "kind": "class", + "file": "src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.321, + "inDegree": 2, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Services/ITokenEstimator.cs::ITokenEstimator" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs::CalculateTool", + "name": "CalculateTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.557, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs::Base64Tool", + "name": "Base64Tool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.517, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs::HashTool", + "name": "HashTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.414, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs::ColorConvertTool", + "name": "ColorConvertTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.681, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs::JsonFormatTool", + "name": "JsonFormatTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.468, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs::DocsListTool", + "name": "DocsListTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.421, + "inDegree": 1, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsEntry", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsSources", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs::DocsReadTool", + "name": "DocsReadTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.387, + "inDegree": 1, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsContent", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsContent", + "name": "DocsContent", + "kind": "record", + "file": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.308, + "inDegree": 5, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsEntry", + "name": "DocsEntry", + "kind": "record", + "file": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.301, + "inDegree": 5, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsSources", + "name": "DocsSources", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.209, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup", + "name": "IDocsLookup", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.258, + "inDegree": 7, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsContent", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsEntry", + "src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs::CompositeDocsLookup", + "src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs::GitHubDocsLookup", + "src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs::LocalDocsLookup" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs::AgentPathResolver", + "name": "AgentPathResolver", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.445, + "inDegree": 1, + "outDegree": 8, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs::AgentToolsOptions", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Core/Services/IProjectFileService.cs::IProjectFileService", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::IAgentPathResolver", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::ResolvedPath", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs::FileInfoTool", + "name": "FileInfoTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.633, + "inDegree": 1, + "outDegree": 8, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs::AgentToolsOptions", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::IAgentPathResolver", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::ResolvedPath", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/FindTool.cs::FindTool", + "name": "FindTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Files/FindTool.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.488, + "inDegree": 1, + "outDegree": 8, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::IAgentPathResolver", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::ResolvedPath", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/GrepTool.cs::GrepTool", + "name": "GrepTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Files/GrepTool.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.688, + "inDegree": 1, + "outDegree": 8, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::IAgentPathResolver", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::ResolvedPath", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::IAgentPathResolver", + "name": "IAgentPathResolver", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.237, + "inDegree": 6, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::ResolvedPath" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode", + "name": "PathRootMode", + "kind": "enum", + "file": "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.026, + "inDegree": 7, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::ResolvedPath", + "name": "ResolvedPath", + "kind": "record", + "file": "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.256, + "inDegree": 6, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs::ListDirTool", + "name": "ListDirTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs", + "subsystem": "src/api/Gabriel.Engine · DomainException", + "complexity": 0.598, + "inDegree": 1, + "outDegree": 8, + "doc": "Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs::AgentToolsOptions", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::IAgentPathResolver", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::PathRootMode", + "src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs::ResolvedPath", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs::GetCurrentTimeTool", + "name": "GetCurrentTimeTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.268, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "name": "ITool", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Tools/ITool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.389, + "inDegree": 27, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Tools/ITool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/IToolRegistry.cs::IToolRegistry" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext", + "name": "IToolExecutionContext", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.343, + "inDegree": 9, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::ToolExecutionContext", + "name": "ToolExecutionContext", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.275, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/IToolRegistry.cs::IToolRegistry", + "name": "IToolRegistry", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Tools/IToolRegistry.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.265, + "inDegree": 4, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/IToolRegistry.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs::MemoryListTool", + "name": "MemoryListTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.396, + "inDegree": 1, + "outDegree": 6, + "doc": "Code/src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs::MemoryRemoveTool", + "name": "MemoryRemoveTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.444, + "inDegree": 1, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs::MemorySaveTool", + "name": "MemorySaveTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.488, + "inDegree": 1, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md", + "dependsOn": [ + "prototype/patterns.js::pick", + "src/api/Gabriel.Core/Entities/MemoryEntryType.cs::MemoryEntryType", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Services/IMemoryService.cs::IMemoryService", + "src/api/Gabriel.Core/Services/IMemoryService.cs::MemoryEntrySpec", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs::BaseConvertTool", + "name": "BaseConvertTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.585, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs::ListProjectFilesTool", + "name": "ListProjectFilesTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.381, + "inDegree": 1, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile", + "src/api/Gabriel.Core/Services/IProjectFileService.cs::IProjectFileService", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs::ReadProjectFileTool", + "name": "ReadProjectFileTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.43, + "inDegree": 1, + "outDegree": 6, + "doc": "Code/src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile", + "src/api/Gabriel.Core/Services/IProjectFileService.cs::IProjectFileService", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs::IToolExecutionContext", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs::TextStatsTool", + "name": "TextStatsTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.529, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs::TextTransformTool", + "name": "TextTransformTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.566, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/ToolRegistry.cs::ToolRegistry", + "name": "ToolRegistry", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/ToolRegistry.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.268, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Engine/Tools/ToolRegistry.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/IToolRegistry.cs::IToolRegistry" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::IUrlFetcher", + "name": "IUrlFetcher", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs", + "subsystem": "src/api/Gabriel.Engine · WebFetchTool", + "complexity": 0.289, + "inDegree": 3, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::UrlFetchResult", + "src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs::WebFetchTool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::UrlFetchResult", + "name": "UrlFetchResult", + "kind": "record", + "file": "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs", + "subsystem": "src/api/Gabriel.Engine · WebFetchTool", + "complexity": 0.227, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "name": "IWebSearch", + "kind": "interface", + "file": "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.237, + "inDegree": 8, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult", + "src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs::WebSearchTool" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult", + "name": "WebSearchResult", + "kind": "record", + "file": "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.101, + "inDegree": 7, + "outDegree": 0, + "doc": "Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md", + "dependsOn": [] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs::WebFetchTool", + "name": "WebFetchTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs", + "subsystem": "src/api/Gabriel.Engine · WebFetchTool", + "complexity": 0.387, + "inDegree": 2, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::IUrlFetcher", + "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::UrlFetchResult" + ] + }, + { + "id": "src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs::WebSearchTool", + "name": "WebSearchTool", + "kind": "class", + "file": "src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.415, + "inDegree": 2, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/ITool.cs::ITool", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/DependencyInjection.cs::DependencyInjection", + "name": "DependencyInjection", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/DependencyInjection.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": null, + "inDegree": 0, + "outDegree": 44, + "doc": "Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs::BraveSearchOptions", + "src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs::GitHubDocsOptions", + "src/api/Gabriel.Core/Configuration/GrokOptions.cs::GrokOptions", + "src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs::LLMProviderOptions", + "src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs::LocalDocsOptions", + "src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs::ProjectFilesOptions", + "src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs::TavilySearchOptions", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Repositories/IConversationRepository.cs::IConversationRepository", + "src/api/Gabriel.Core/Repositories/IMemoryRepository.cs::IMemoryRepository", + "src/api/Gabriel.Core/Repositories/IMetricRepository.cs::IMetricRepository", + "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Core/Services/IProjectFileService.cs::IProjectFileService", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::ChatProviderRegistry", + "src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs::IChatProviderRegistry", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog", + "src/api/Gabriel.Engine/Providers/ModelCatalog.cs::ModelCatalog", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Engine/Services/IMetricRecorder.cs::IMetricRecorder", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup", + "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::IUrlFetcher", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext", + "src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs::ConversationRepository", + "src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs::MemoryRepository", + "src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs::MetricRepository", + "src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs::ProjectRepository", + "src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs::UnitOfWork", + "src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs::DiskProjectFileService", + "src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs::GrokAuthHandler", + "src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs::GrokChatProvider", + "src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs::MockChatProvider", + "src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs::CompositeDocsLookup", + "src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs::GitHubDocsLookup", + "src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs::LocalDocsLookup", + "src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs::BraveWebSearch", + "src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs::CompositeWebSearch", + "src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs::DuckDuckGoWebSearch", + "src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs::HttpUrlFetcher", + "src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs::InstrumentedWebSearch", + "src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs::TavilyWebSearch", + "src/webapp/src/auth/AuthContext.tsx::handler" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser", + "name": "ApplicationUser", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.298, + "inDegree": 10, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs::GabrielIdentityExtensions", + "name": "GabrielIdentityExtensions", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs", + "subsystem": "src/api/Gabriel.Infrastructure · GabrielIdentityExtensions", + "complexity": 0.298, + "inDegree": 1, + "outDegree": 11, + "doc": "Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md", + "dependsOn": [ + "src/api/Gabriel.API/Controllers/AuthController.cs::AuthController", + "src/api/Gabriel.Core/Configuration/JwtOptions.cs::JwtOptions", + "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::IJwtTokenService", + "src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs::IRefreshTokenStore", + "src/api/Gabriel.Core/Identity/IUserPreferences.cs::IUserPreferences", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser", + "src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs::JwtTokenService", + "src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs::UserPreferencesService", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext", + "src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs::RefreshTokenStore", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs::JwtTokenService", + "name": "JwtTokenService", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs", + "subsystem": "src/api/Gabriel.Core · RefreshToken", + "complexity": 0.654, + "inDegree": 3, + "outDegree": 7, + "doc": "Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/JwtOptions.cs::JwtOptions", + "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::IJwtTokenService", + "src/api/Gabriel.Core/Identity/IJwtTokenService.cs::TokenPair", + "src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs::IRefreshTokenStore", + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs::UserPreferencesService", + "name": "UserPreferencesService", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs", + "subsystem": "src/api/Gabriel.Core · IUserPreferences", + "complexity": 0.452, + "inDegree": 1, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Identity/IUserPreferences.cs::IUserPreferences", + "src/api/Gabriel.Core/Identity/IUserPreferences.cs::UserPreferences", + "src/api/Gabriel.Engine/Services/AgentService.cs::AgentService", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext", + "name": "AppDbContext", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs", + "subsystem": "src/api/Gabriel.Infrastructure · AppDbContext", + "complexity": 0.388, + "inDegree": 10, + "outDegree": 8, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/MetricEntry.cs::MetricEntry", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile", + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken", + "src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs::ApplicationUser" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs::ConversationConfiguration", + "name": "ConversationConfiguration", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.223, + "inDegree": 0, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Personality/ConversationState.cs::ConversationState", + "src/webapp/src/components/CompactingOverlay.tsx::Core", + "src/webapp/src/components/Sidebar.tsx::Sidebar" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs::MemoryEntryConfiguration", + "name": "MemoryEntryConfiguration", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.22, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Entities/Project.cs::Project" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs::MessageConfiguration", + "name": "MessageConfiguration", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.298, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs::MetricEntryConfiguration", + "name": "MetricEntryConfiguration", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs", + "subsystem": "src/api/Gabriel.Infrastructure · AppDbContext", + "complexity": 0.324, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MetricEntry.cs::MetricEntry" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs::ProjectConfiguration", + "name": "ProjectConfiguration", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.195, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs::ProjectFileConfiguration", + "name": "ProjectFileConfiguration", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs", + "subsystem": "src/api/Gabriel.Engine · DependencyInjection", + "complexity": 0.227, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs::RefreshTokenConfiguration", + "name": "RefreshTokenConfiguration", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs", + "subsystem": "src/api/Gabriel.Core · RefreshToken", + "complexity": 0.195, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs::ConversationRepository", + "name": "ConversationRepository", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.62, + "inDegree": 1, + "outDegree": 4, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Repositories/IConversationRepository.cs::IConversationRepository", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs::MemoryRepository", + "name": "MemoryRepository", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs", + "subsystem": "src/api/Gabriel.Core · MemoryEntry", + "complexity": 0.627, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MemoryEntry.cs::MemoryEntry", + "src/api/Gabriel.Core/Repositories/IMemoryRepository.cs::IMemoryRepository", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs::MetricRepository", + "name": "MetricRepository", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs", + "subsystem": "src/api/Gabriel.Infrastructure · AppDbContext", + "complexity": 0.494, + "inDegree": 1, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/MetricEntry.cs::MetricEntry", + "src/api/Gabriel.Core/Repositories/IMetricRepository.cs::IMetricRepository", + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext", + "src/webapp/src/components/CompactingOverlay.tsx::Core" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs::ProjectRepository", + "name": "ProjectRepository", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs", + "subsystem": "src/api/Gabriel.Infrastructure · AppDbContext", + "complexity": 0.65, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs::RefreshTokenStore", + "name": "RefreshTokenStore", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs", + "subsystem": "src/api/Gabriel.Infrastructure · GabrielIdentityExtensions", + "complexity": 0.358, + "inDegree": 2, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs::IRefreshTokenStore", + "src/api/Gabriel.Core/Identity/RefreshToken.cs::RefreshToken", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs::UnitOfWork", + "name": "UnitOfWork", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs", + "subsystem": "src/api/Gabriel.Infrastructure · AppDbContext", + "complexity": 0.224, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Repositories/IUnitOfWork.cs::IUnitOfWork", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs::DiskProjectFileService", + "name": "DiskProjectFileService", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs", + "subsystem": "src/api/Gabriel.Core · ICurrentUser", + "complexity": 0.75, + "inDegree": 1, + "outDegree": 9, + "doc": "Code/src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs::ProjectFilesOptions", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/api/Gabriel.Core/Entities/ProjectFile.cs::ProjectFile", + "src/api/Gabriel.Core/Exceptions/DomainException.cs::DomainException", + "src/api/Gabriel.Core/Exceptions/NotFoundException.cs::NotFoundException", + "src/api/Gabriel.Core/Identity/ICurrentUser.cs::ICurrentUser", + "src/api/Gabriel.Core/Repositories/IProjectRepository.cs::IProjectRepository", + "src/api/Gabriel.Core/Services/IProjectFileService.cs::IProjectFileService", + "src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs::AppDbContext" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs::GrokAuthHandler", + "name": "GrokAuthHandler", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.329, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/GrokOptions.cs::GrokOptions" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs::GrokChatProvider", + "name": "GrokChatProvider", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.767, + "inDegree": 1, + "outDegree": 14, + "doc": "Code/src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/GrokOptions.cs::GrokOptions", + "src/api/Gabriel.Core/Configuration/LLMModel.cs::LLMModel", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishReason", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ReasoningDeltaEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::TextDeltaEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ToolCallReadyEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs::ChatProviderMessage", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog", + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor", + "src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs::GrokAuthHandler" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs::MockChatProvider", + "name": "MockChatProvider", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.449, + "inDegree": 1, + "outDegree": 11, + "doc": "Code/src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/LLMModel.cs::LLMModel", + "src/api/Gabriel.Core/Entities/MessageRole.cs::MessageRole", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ChatProviderEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::FinishReason", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::TextDeltaEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs::ToolCallReadyEvent", + "src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs::ChatProviderMessage", + "src/api/Gabriel.Engine/Providers/IChatProvider.cs::IChatProvider", + "src/api/Gabriel.Engine/Providers/IModelCatalog.cs::IModelCatalog", + "src/api/Gabriel.Engine/Providers/ToolDescriptor.cs::ToolDescriptor" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs::CompositeDocsLookup", + "name": "CompositeDocsLookup", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.375, + "inDegree": 2, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsContent", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsEntry", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup", + "src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs::GitHubDocsLookup", + "src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs::LocalDocsLookup" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs::GitHubDocsLookup", + "name": "GitHubDocsLookup", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.46, + "inDegree": 3, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs::GitHubDocsOptions", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsContent", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsEntry", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsSources", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs::LocalDocsLookup", + "name": "LocalDocsLookup", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs", + "subsystem": "src/api/Gabriel.Engine · IDocsLookup", + "complexity": 0.492, + "inDegree": 3, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs::LocalDocsOptions", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsContent", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsEntry", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::DocsSources", + "src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs::IDocsLookup" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs::BraveWebSearch", + "name": "BraveWebSearch", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.452, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs::BraveSearchOptions", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs::CompositeWebSearch", + "name": "CompositeWebSearch", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.504, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs::DuckDuckGoWebSearch", + "name": "DuckDuckGoWebSearch", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.093, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs::HttpUrlFetcher", + "name": "HttpUrlFetcher", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs", + "subsystem": "src/api/Gabriel.Engine · WebFetchTool", + "complexity": 0.641, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::IUrlFetcher", + "src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs::UrlFetchResult" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs::InstrumentedWebSearch", + "name": "InstrumentedWebSearch", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.339, + "inDegree": 1, + "outDegree": 5, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Message.cs::Message", + "src/api/Gabriel.Core/Entities/MetricEntry.cs::MetricEntry", + "src/api/Gabriel.Engine/Services/IMetricRecorder.cs::IMetricRecorder", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult" + ] + }, + { + "id": "src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs::TavilyWebSearch", + "name": "TavilyWebSearch", + "kind": "class", + "file": "src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.493, + "inDegree": 1, + "outDegree": 3, + "doc": "Code/src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs::TavilySearchOptions", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::IWebSearch", + "src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs::WebSearchResult" + ] + }, + { + "id": "src/webapp/src/App.tsx::App", + "name": "App", + "kind": "function", + "file": "src/webapp/src/App.tsx", + "subsystem": "src/webapp/src · App", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/webapp/src/App.tsx.md", + "dependsOn": [ + "src/webapp/src/auth/AuthContext.tsx::AuthProvider" + ] + }, + { + "id": "src/webapp/src/api/authInterceptor.ts::installAuthInterceptor", + "name": "installAuthInterceptor", + "kind": "function", + "file": "src/webapp/src/api/authInterceptor.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.267, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/authInterceptor.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/authRefresh.ts::postRefresh", + "name": "postRefresh", + "kind": "function", + "file": "src/webapp/src/api/authRefresh.ts", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.207, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/authRefresh.ts.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs::RefreshTokenRequest" + ] + }, + { + "id": "src/webapp/src/api/authRefresh.ts::refreshSession", + "name": "refreshSession", + "kind": "function", + "file": "src/webapp/src/api/authRefresh.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/authRefresh.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/authRefresh.ts::signalSessionExpired", + "name": "signalSessionExpired", + "kind": "function", + "file": "src/webapp/src/api/authRefresh.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/authRefresh.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/conversationMode.ts::GabrielMode", + "name": "GabrielMode", + "kind": "type", + "file": "src/webapp/src/api/conversationMode.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/conversationMode.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/conversationMode.ts::setConversationMode", + "name": "setConversationMode", + "kind": "function", + "file": "src/webapp/src/api/conversationMode.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.245, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/conversationMode.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/conversationMode.ts::withRefresh", + "name": "withRefresh", + "kind": "function", + "file": "src/webapp/src/api/conversationMode.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/conversationMode.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/memories.ts::MemoryDto", + "name": "MemoryDto", + "kind": "interface", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api · MemoryType", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [ + "src/webapp/src/api/memories.ts::MemoryType" + ] + }, + { + "id": "src/webapp/src/api/memories.ts::MemoryScope", + "name": "MemoryScope", + "kind": "type", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api · MemoryScope", + "complexity": 0.307, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/memories.ts::MemoryType", + "name": "MemoryType", + "kind": "type", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api · MemoryType", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/memories.ts::SaveMemoryRequest", + "name": "SaveMemoryRequest", + "kind": "interface", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api · MemoryType", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [ + "src/webapp/src/api/memories.ts::MemoryType" + ] + }, + { + "id": "src/webapp/src/api/memories.ts::deleteMemory", + "name": "deleteMemory", + "kind": "function", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/memories.ts::listMemories", + "name": "listMemories", + "kind": "function", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api · MemoryScope", + "complexity": 0.288, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [ + "src/webapp/src/api/memories.ts::MemoryScope" + ] + }, + { + "id": "src/webapp/src/api/memories.ts::saveMemory", + "name": "saveMemory", + "kind": "function", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.245, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/memories.ts::urlFor", + "name": "urlFor", + "kind": "function", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api · MemoryScope", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [ + "src/webapp/src/api/memories.ts::MemoryScope" + ] + }, + { + "id": "src/webapp/src/api/memories.ts::withRefresh", + "name": "withRefresh", + "kind": "function", + "file": "src/webapp/src/api/memories.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/memories.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/models.ts::ModelDto", + "name": "ModelDto", + "kind": "interface", + "file": "src/webapp/src/api/models.ts", + "subsystem": "src/api/Gabriel.API · AgentOptions", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/models.ts.md", + "dependsOn": [ + "src/api/Gabriel.Core/Configuration/AgentOptions.cs::AgentOptions" + ] + }, + { + "id": "src/webapp/src/api/models.ts::ModelsResponse", + "name": "ModelsResponse", + "kind": "interface", + "file": "src/webapp/src/api/models.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/models.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/models.ts::SelectedModelDto", + "name": "SelectedModelDto", + "kind": "interface", + "file": "src/webapp/src/api/models.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/models.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/models.ts::SetActiveModelRequest", + "name": "SetActiveModelRequest", + "kind": "interface", + "file": "src/webapp/src/api/models.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/models.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/models.ts::fetchModels", + "name": "fetchModels", + "kind": "function", + "file": "src/webapp/src/api/models.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.288, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/models.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/models.ts::setActiveModel", + "name": "setActiveModel", + "kind": "function", + "file": "src/webapp/src/api/models.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.245, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/models.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/models.ts::withRefresh", + "name": "withRefresh", + "kind": "function", + "file": "src/webapp/src/api/models.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/models.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/sequence.ts::GabrielSequence", + "name": "GabrielSequence", + "kind": "interface", + "file": "src/webapp/src/api/sequence.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/sequence.ts.md", + "dependsOn": [ + "src/webapp/src/api/sequence.ts::GabrielSequenceMetadata" + ] + }, + { + "id": "src/webapp/src/api/sequence.ts::GabrielSequenceMetadata", + "name": "GabrielSequenceMetadata", + "kind": "interface", + "file": "src/webapp/src/api/sequence.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.192, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/sequence.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/sequence.ts::SequenceSource", + "name": "SequenceSource", + "kind": "type", + "file": "src/webapp/src/api/sequence.ts", + "subsystem": "src/webapp/src/api · SequenceSource", + "complexity": 0.14, + "inDegree": 6, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/sequence.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/sequence.ts::doFetch", + "name": "doFetch", + "kind": "function", + "file": "src/webapp/src/api/sequence.ts", + "subsystem": "src/webapp/src/api · SequenceSource", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/sequence.ts.md", + "dependsOn": [ + "src/webapp/src/api/sequence.ts::SequenceSource" + ] + }, + { + "id": "src/webapp/src/api/sequence.ts::fetchGabrielSequence", + "name": "fetchGabrielSequence", + "kind": "function", + "file": "src/webapp/src/api/sequence.ts", + "subsystem": "src/webapp/src/api · SequenceSource", + "complexity": 0.299, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/sequence.ts.md", + "dependsOn": [ + "src/webapp/src/api/sequence.ts::SequenceSource" + ] + }, + { + "id": "src/webapp/src/api/sequence.ts::urlFor", + "name": "urlFor", + "kind": "function", + "file": "src/webapp/src/api/sequence.ts", + "subsystem": "src/webapp/src/api · SequenceSource", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/sequence.ts.md", + "dependsOn": [ + "src/webapp/src/api/sequence.ts::SequenceSource" + ] + }, + { + "id": "src/webapp/src/api/streamChat.ts::AgentEvent", + "name": "AgentEvent", + "kind": "type", + "file": "src/webapp/src/api/streamChat.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.214, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/streamChat.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/streamChat.ts::StreamChatOptions", + "name": "StreamChatOptions", + "kind": "interface", + "file": "src/webapp/src/api/streamChat.ts", + "subsystem": "src/webapp/src/api · StreamChatOptions", + "complexity": 0.392, + "inDegree": 3, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/streamChat.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/streamChat.ts::doFetch", + "name": "doFetch", + "kind": "function", + "file": "src/webapp/src/api/streamChat.ts", + "subsystem": "src/webapp/src/api", + "complexity": 0.288, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/api/streamChat.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/api/streamChat.ts::streamChat", + "name": "streamChat", + "kind": "function", + "file": "src/webapp/src/api/streamChat.ts", + "subsystem": "src/webapp/src/api · StreamChatOptions", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/streamChat.ts.md", + "dependsOn": [ + "src/webapp/src/api/streamChat.ts::StreamChatOptions" + ] + }, + { + "id": "src/webapp/src/api/streamChat.ts::streamRegenerate", + "name": "streamRegenerate", + "kind": "function", + "file": "src/webapp/src/api/streamChat.ts", + "subsystem": "src/webapp/src/api · StreamChatOptions", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/streamChat.ts.md", + "dependsOn": [ + "src/webapp/src/api/streamChat.ts::StreamChatOptions" + ] + }, + { + "id": "src/webapp/src/api/streamChat.ts::streamSse", + "name": "streamSse", + "kind": "function", + "file": "src/webapp/src/api/streamChat.ts", + "subsystem": "src/webapp/src/api · StreamChatOptions", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/api/streamChat.ts.md", + "dependsOn": [ + "src/webapp/src/api/streamChat.ts::StreamChatOptions" + ] + }, + { + "id": "src/webapp/src/auth/AuthContext.tsx::AuthProvider", + "name": "AuthProvider", + "kind": "function", + "file": "src/webapp/src/auth/AuthContext.tsx", + "subsystem": "src/webapp/src · App", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/auth/AuthContext.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/auth/AuthContext.tsx::AuthState", + "name": "AuthState", + "kind": "interface", + "file": "src/webapp/src/auth/AuthContext.tsx", + "subsystem": "src/api/Gabriel.API · AuthController", + "complexity": 0.192, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/webapp/src/auth/AuthContext.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Auth/MeResponse.cs::MeResponse" + ] + }, + { + "id": "src/webapp/src/auth/AuthContext.tsx::formatError", + "name": "formatError", + "kind": "function", + "file": "src/webapp/src/auth/AuthContext.tsx", + "subsystem": "src/webapp/src/auth", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/auth/AuthContext.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/auth/AuthContext.tsx::handler", + "name": "handler", + "kind": "function", + "file": "src/webapp/src/auth/AuthContext.tsx", + "subsystem": "src/api/Gabriel.Infrastructure · DependencyInjection", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/auth/AuthContext.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/auth/AuthContext.tsx::useAuth", + "name": "useAuth", + "kind": "function", + "file": "src/webapp/src/auth/AuthContext.tsx", + "subsystem": "src/webapp/src · App", + "complexity": 0.273, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/webapp/src/auth/AuthContext.tsx.md", + "dependsOn": [ + "src/webapp/src/auth/AuthContext.tsx::AuthProvider", + "src/webapp/src/auth/AuthContext.tsx::AuthState" + ] + }, + { + "id": "src/webapp/src/components/Avatar.tsx::Avatar", + "name": "Avatar", + "kind": "function", + "file": "src/webapp/src/components/Avatar.tsx", + "subsystem": "src/webapp/src/components · Avatar", + "complexity": 0.14, + "inDegree": 4, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Avatar.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Avatar.tsx::PulseConfig", + "name": "PulseConfig", + "kind": "interface", + "file": "src/webapp/src/components/Avatar.tsx", + "subsystem": "src/webapp/src/pulse · PatternName", + "complexity": 0.392, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Avatar.tsx.md", + "dependsOn": [ + "src/webapp/src/pulse/patterns.ts::PatternName" + ] + }, + { + "id": "src/webapp/src/components/Avatar.tsx::PulsePlane", + "name": "PulsePlane", + "kind": "function", + "file": "src/webapp/src/components/Avatar.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Avatar.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Avatar.tsx::PulseState", + "name": "PulseState", + "kind": "interface", + "file": "src/webapp/src/components/Avatar.tsx", + "subsystem": "src/webapp/src/components · Pattern", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Avatar.tsx.md", + "dependsOn": [ + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/webapp/src/components/Avatar.tsx::createPulse", + "name": "createPulse", + "kind": "function", + "file": "src/webapp/src/components/Avatar.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Avatar.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::Chat", + "name": "Chat", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/api/Gabriel.Engine · Chat", + "complexity": 0.14, + "inDegree": 6, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::ChatEntry", + "name": "ChatEntry", + "kind": "type", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.34, + "inDegree": 6, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::VariantMeta" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::ChatProps", + "name": "ChatProps", + "kind": "interface", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · Avatar", + "complexity": 0.392, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs::ConversationResponse", + "src/webapp/src/components/Avatar.tsx::Avatar", + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::EntryActions", + "name": "EntryActions", + "kind": "interface", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · renderEntry", + "complexity": 0.192, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::Reasoning", + "name": "Reasoning", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/api/Gabriel.Engine · AgentService", + "complexity": 0.14, + "inDegree": 6, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::Thought", + "name": "Thought", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::ToolResult", + "name": "ToolResult", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · renderEntry", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::VariantMeta", + "name": "VariantMeta", + "kind": "type", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · MessageResponse", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::VariantPicker", + "name": "VariantPicker", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · renderEntry", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::advance", + "name": "advance", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::applyAgentEvent", + "name": "applyAgentEvent", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.252, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::ChatEntry", + "src/webapp/src/components/Chat.tsx::Reasoning", + "src/webapp/src/components/StreamingText.tsx::StreamingText" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::hasActiveAssistantStream", + "name": "hasActiveAssistantStream", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::ChatEntry" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::historyToEntries", + "name": "historyToEntries", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 4, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageResponse", + "src/webapp/src/components/Chat.tsx::ChatEntry", + "src/webapp/src/components/Chat.tsx::Reasoning", + "src/webapp/src/components/Chat.tsx::Thought" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::lastIndexWhere", + "name": "lastIndexWhere", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::onKeyDown", + "name": "onKeyDown", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::onSubmit", + "name": "onSubmit", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::prettyArgs", + "name": "prettyArgs", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::prev", + "name": "prev", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Chat.tsx::renderEntry", + "name": "renderEntry", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · renderEntry", + "complexity": 0.298, + "inDegree": 0, + "outDegree": 7, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::ChatEntry", + "src/webapp/src/components/Chat.tsx::EntryActions", + "src/webapp/src/components/Chat.tsx::Reasoning", + "src/webapp/src/components/Chat.tsx::Thought", + "src/webapp/src/components/Chat.tsx::ToolResult", + "src/webapp/src/components/Chat.tsx::VariantPicker", + "src/webapp/src/components/StreamingText.tsx::StreamingText" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::send", + "name": "send", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/api/Gabriel.API · AgentUserMessagePersisted", + "complexity": 0.326, + "inDegree": 1, + "outDegree": 2, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Services/AgentEvent.cs::AgentUserMessagePersisted", + "src/webapp/src/components/Chat.tsx::ChatEntry" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::toolCallEntry", + "name": "toolCallEntry", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageToolCall", + "src/webapp/src/components/Chat.tsx::ChatEntry" + ] + }, + { + "id": "src/webapp/src/components/Chat.tsx::variantMetaOf", + "name": "variantMetaOf", + "kind": "function", + "file": "src/webapp/src/components/Chat.tsx", + "subsystem": "src/webapp/src/components · MessageResponse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/webapp/src/components/Chat.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs::MessageResponse", + "src/webapp/src/components/Chat.tsx::VariantMeta" + ] + }, + { + "id": "src/webapp/src/components/CompactingOverlay.tsx::CompactingOverlay", + "name": "CompactingOverlay", + "kind": "function", + "file": "src/webapp/src/components/CompactingOverlay.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/CompactingOverlay.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/CompactingOverlay.tsx::CompactingOverlayProps", + "name": "CompactingOverlayProps", + "kind": "interface", + "file": "src/webapp/src/components/CompactingOverlay.tsx", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.392, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/webapp/src/components/CompactingOverlay.tsx.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/components/CompactingOverlay.tsx::Core", + "name": "Core", + "kind": "function", + "file": "src/webapp/src/components/CompactingOverlay.tsx", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.14, + "inDegree": 15, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/CompactingOverlay.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/CompactingOverlay.tsx::Swirl", + "name": "Swirl", + "kind": "function", + "file": "src/webapp/src/components/CompactingOverlay.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/CompactingOverlay.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/CompactingOverlay.tsx::SwirlProps", + "name": "SwirlProps", + "kind": "interface", + "file": "src/webapp/src/components/CompactingOverlay.tsx", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.392, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/CompactingOverlay.tsx.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/components/ContextStats.tsx::Category", + "name": "Category", + "kind": "type", + "file": "src/webapp/src/components/ContextStats.tsx", + "subsystem": "src/webapp/src/components · ContextMetricsResponse", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/ContextStats.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs::ContextMetricsResponse" + ] + }, + { + "id": "src/webapp/src/components/ContextStats.tsx::ContextStats", + "name": "ContextStats", + "kind": "function", + "file": "src/webapp/src/components/ContextStats.tsx", + "subsystem": "src/webapp/src/components · ChatPage", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ContextStats.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ContextStats.tsx::ContextStatsProps", + "name": "ContextStatsProps", + "kind": "interface", + "file": "src/webapp/src/components/ContextStats.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.304, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ContextStats.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ContextStats.tsx::buildGridCells", + "name": "buildGridCells", + "kind": "function", + "file": "src/webapp/src/components/ContextStats.tsx", + "subsystem": "src/webapp/src/components · ContextMetricsResponse", + "complexity": 0.299, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/webapp/src/components/ContextStats.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs::ContextMetricsResponse", + "src/webapp/src/components/ContextStats.tsx::Category" + ] + }, + { + "id": "src/webapp/src/components/ContextStats.tsx::formatTokens", + "name": "formatTokens", + "kind": "function", + "file": "src/webapp/src/components/ContextStats.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.223, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ContextStats.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/GabrielSequenceView.tsx::GabrielSequenceView", + "name": "GabrielSequenceView", + "kind": "function", + "file": "src/webapp/src/components/GabrielSequenceView.tsx", + "subsystem": "src/webapp/src/components · ChatPage", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/GabrielSequenceView.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/GabrielSequenceView.tsx::GabrielSequenceViewProps", + "name": "GabrielSequenceViewProps", + "kind": "interface", + "file": "src/webapp/src/components/GabrielSequenceView.tsx", + "subsystem": "src/webapp/src/api · SequenceSource", + "complexity": 0.346, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/GabrielSequenceView.tsx.md", + "dependsOn": [ + "src/webapp/src/api/sequence.ts::SequenceSource" + ] + }, + { + "id": "src/webapp/src/components/GabrielSequenceView.tsx::draw", + "name": "draw", + "kind": "function", + "file": "src/webapp/src/components/GabrielSequenceView.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.186, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/GabrielSequenceView.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::Inline", + "name": "Inline", + "kind": "type", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components · Inline", + "complexity": 0.14, + "inDegree": 5, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::MarkdownImpl", + "name": "MarkdownImpl", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::Props", + "name": "Props", + "kind": "interface", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.392, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::buildComponents", + "name": "buildComponents", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components · Fragments", + "complexity": 0.245, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Mermaid.tsx::Mermaid" + ] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::expandText", + "name": "expandText", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components · Inline", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Markdown.tsx::Inline" + ] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::languageOf", + "name": "languageOf", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::remarkInlineEnrichments", + "name": "remarkInlineEnrichments", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::remarkUnliftEmptyOrderedList", + "name": "remarkUnliftEmptyOrderedList", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::toAstNode", + "name": "toAstNode", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components · Inline", + "complexity": 0.201, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Markdown.tsx::Inline" + ] + }, + { + "id": "src/webapp/src/components/Markdown.tsx::toGalactic", + "name": "toGalactic", + "kind": "function", + "file": "src/webapp/src/components/Markdown.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.33, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Markdown.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryList.tsx::MemoryEditor", + "name": "MemoryEditor", + "kind": "function", + "file": "src/webapp/src/components/MemoryList.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryList.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryList.tsx::MemoryEditorProps", + "name": "MemoryEditorProps", + "kind": "interface", + "file": "src/webapp/src/components/MemoryList.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.304, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryList.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryList.tsx::MemoryList", + "name": "MemoryList", + "kind": "function", + "file": "src/webapp/src/components/MemoryList.tsx", + "subsystem": "src/webapp/src/components · UserSettingsPage", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryList.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryList.tsx::MemoryListProps", + "name": "MemoryListProps", + "kind": "interface", + "file": "src/webapp/src/components/MemoryList.tsx", + "subsystem": "src/webapp/src/api · MemoryScope", + "complexity": 0.288, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/MemoryList.tsx.md", + "dependsOn": [ + "src/webapp/src/api/memories.ts::MemoryScope" + ] + }, + { + "id": "src/webapp/src/components/MemoryList.tsx::MemoryRow", + "name": "MemoryRow", + "kind": "function", + "file": "src/webapp/src/components/MemoryList.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryList.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryList.tsx::stopToggle", + "name": "stopToggle", + "kind": "function", + "file": "src/webapp/src/components/MemoryList.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryList.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryQuickSave.tsx::MemoryQuickSave", + "name": "MemoryQuickSave", + "kind": "function", + "file": "src/webapp/src/components/MemoryQuickSave.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryQuickSave.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryQuickSave.tsx::MemoryQuickSaveProps", + "name": "MemoryQuickSaveProps", + "kind": "interface", + "file": "src/webapp/src/components/MemoryQuickSave.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.267, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryQuickSave.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/MemoryQuickSave.tsx::onKey", + "name": "onKey", + "kind": "function", + "file": "src/webapp/src/components/MemoryQuickSave.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/MemoryQuickSave.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::MaximizeIcon", + "name": "MaximizeIcon", + "kind": "function", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::Mermaid", + "name": "Mermaid", + "kind": "function", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components · Fragments", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::MermaidModal", + "name": "MermaidModal", + "kind": "function", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::MermaidModule", + "name": "MermaidModule", + "kind": "type", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::MermaidProps", + "name": "MermaidProps", + "kind": "interface", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::ModalProps", + "name": "ModalProps", + "kind": "interface", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::loadMermaid", + "name": "loadMermaid", + "kind": "function", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.182, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Mermaid.tsx::MermaidModule" + ] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::nextId", + "name": "nextId", + "kind": "function", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Mermaid.tsx::onKey", + "name": "onKey", + "kind": "function", + "file": "src/webapp/src/components/Mermaid.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Mermaid.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ModeSelector.tsx::ModeSelector", + "name": "ModeSelector", + "kind": "function", + "file": "src/webapp/src/components/ModeSelector.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ModeSelector.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ModeSelector.tsx::ModeSelectorProps", + "name": "ModeSelectorProps", + "kind": "interface", + "file": "src/webapp/src/components/ModeSelector.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.359, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/ModeSelector.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs::ConversationResponse" + ] + }, + { + "id": "src/webapp/src/components/ModelSelector.tsx::ModelSelector", + "name": "ModelSelector", + "kind": "function", + "file": "src/webapp/src/components/ModelSelector.tsx", + "subsystem": "src/webapp/src/components · UserSettingsPage", + "complexity": 0.214, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ModelSelector.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ModelSelector.tsx::labelFor", + "name": "labelFor", + "kind": "function", + "file": "src/webapp/src/components/ModelSelector.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ModelSelector.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::MenuAnchor", + "name": "MenuAnchor", + "kind": "interface", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::ProjectPicker", + "name": "ProjectPicker", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components · Sidebar", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::ProjectPickerProps", + "name": "ProjectPickerProps", + "kind": "interface", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/api/Gabriel.API · ProjectResponse", + "complexity": 0.335, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectResponse" + ] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::close", + "name": "close", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components · Sidebar", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::handleNew", + "name": "handleNew", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.235, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::loadActiveProjectId", + "name": "loadActiveProjectId", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::onDown", + "name": "onDown", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::onKey", + "name": "onKey", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::openSettings", + "name": "openSettings", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::persistActiveProjectId", + "name": "persistActiveProjectId", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::selectProject", + "name": "selectProject", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.251, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ProjectPicker.tsx::toggleMenu", + "name": "toggleMenu", + "kind": "function", + "file": "src/webapp/src/components/ProjectPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.33, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ProjectPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Sidebar.tsx::MenuState", + "name": "MenuState", + "kind": "interface", + "file": "src/webapp/src/components/Sidebar.tsx", + "subsystem": "src/webapp/src/components · Sidebar", + "complexity": 0.192, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Sidebar.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/Sidebar.tsx::Sidebar", + "name": "Sidebar", + "kind": "function", + "file": "src/webapp/src/components/Sidebar.tsx", + "subsystem": "src/webapp/src/components · Sidebar", + "complexity": 0.203, + "inDegree": 2, + "outDegree": 7, + "doc": "Code/src/webapp/src/components/Sidebar.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs::ConversationResponse", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectResponse", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/webapp/src/components/ProjectPicker.tsx::ProjectPicker", + "src/webapp/src/components/ProjectPicker.tsx::close", + "src/webapp/src/components/Sidebar.tsx::MenuState", + "src/webapp/src/pages/IndexPage.tsx::IndexPage" + ] + }, + { + "id": "src/webapp/src/components/Sidebar.tsx::loadSidebarCollapsed", + "name": "loadSidebarCollapsed", + "kind": "function", + "file": "src/webapp/src/components/Sidebar.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.214, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/Sidebar.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/SkinPicker.tsx::SkinPicker", + "name": "SkinPicker", + "kind": "function", + "file": "src/webapp/src/components/SkinPicker.tsx", + "subsystem": "src/api/Gabriel.API · ProjectResponse", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/SkinPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/SkinPicker.tsx::SkinPickerProps", + "name": "SkinPickerProps", + "kind": "interface", + "file": "src/webapp/src/components/SkinPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.374, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/SkinPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/SkinPicker.tsx::handlePalette", + "name": "handlePalette", + "kind": "function", + "file": "src/webapp/src/components/SkinPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/SkinPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/SkinPicker.tsx::handlePattern", + "name": "handlePattern", + "kind": "function", + "file": "src/webapp/src/components/SkinPicker.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/SkinPicker.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/StreamingText.tsx::Props", + "name": "Props", + "kind": "interface", + "file": "src/webapp/src/components/StreamingText.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.392, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/StreamingText.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/StreamingText.tsx::StreamingText", + "name": "StreamingText", + "kind": "function", + "file": "src/webapp/src/components/StreamingText.tsx", + "subsystem": "src/webapp/src/components · ContractMappings", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/StreamingText.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/StreamingText.tsx::tick", + "name": "tick", + "kind": "function", + "file": "src/webapp/src/components/StreamingText.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.316, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/StreamingText.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ThinkingPulse.tsx::Bars", + "name": "Bars", + "kind": "function", + "file": "src/webapp/src/components/ThinkingPulse.tsx", + "subsystem": "src/webapp/src/components · Avatar", + "complexity": 0.14, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ThinkingPulse.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ThinkingPulse.tsx::PulseState", + "name": "PulseState", + "kind": "interface", + "file": "src/webapp/src/components/ThinkingPulse.tsx", + "subsystem": "src/webapp/src/components · Pattern", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/components/ThinkingPulse.tsx.md", + "dependsOn": [ + "src/webapp/src/pulse/patterns.ts::Pattern" + ] + }, + { + "id": "src/webapp/src/components/ThinkingPulse.tsx::ThinkingPulse", + "name": "ThinkingPulse", + "kind": "function", + "file": "src/webapp/src/components/ThinkingPulse.tsx", + "subsystem": "src/webapp/src/components", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/components/ThinkingPulse.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/components/ThinkingPulse.tsx::buildState", + "name": "buildState", + "kind": "function", + "file": "src/webapp/src/components/ThinkingPulse.tsx", + "subsystem": "src/webapp/src/components · Avatar", + "complexity": 0.273, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/webapp/src/components/ThinkingPulse.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Avatar.tsx::Avatar", + "src/webapp/src/components/ThinkingPulse.tsx::Bars", + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/layouts/AuthLayout.tsx::AuthLayout", + "name": "AuthLayout", + "kind": "function", + "file": "src/webapp/src/layouts/AuthLayout.tsx", + "subsystem": "src/webapp/src/layouts", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/layouts/AuthLayout.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/layouts/MainLayout.tsx::MainLayout", + "name": "MainLayout", + "kind": "function", + "file": "src/webapp/src/layouts/MainLayout.tsx", + "subsystem": "src/webapp/src/components · Sidebar", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/layouts/MainLayout.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Sidebar.tsx::Sidebar" + ] + }, + { + "id": "src/webapp/src/lib/notify.ts::formatError", + "name": "formatError", + "kind": "function", + "file": "src/webapp/src/lib/notify.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/notify.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/notify.ts::notifyError", + "name": "notifyError", + "kind": "function", + "file": "src/webapp/src/lib/notify.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/notify.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/notify.ts::notifyInfo", + "name": "notifyInfo", + "kind": "function", + "file": "src/webapp/src/lib/notify.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/notify.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/notify.ts::notifySuccess", + "name": "notifySuccess", + "kind": "function", + "file": "src/webapp/src/lib/notify.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/notify.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/userPrefs.ts::readBool", + "name": "readBool", + "kind": "function", + "file": "src/webapp/src/lib/userPrefs.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.288, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/userPrefs.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/userPrefs.ts::readLegacyHideReactDetails", + "name": "readLegacyHideReactDetails", + "kind": "function", + "file": "src/webapp/src/lib/userPrefs.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.235, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/userPrefs.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/userPrefs.ts::useBoolPref", + "name": "useBoolPref", + "kind": "function", + "file": "src/webapp/src/lib/userPrefs.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/userPrefs.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/userPrefs.ts::useHideThinking", + "name": "useHideThinking", + "kind": "function", + "file": "src/webapp/src/lib/userPrefs.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/userPrefs.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/userPrefs.ts::useHideToolCalls", + "name": "useHideToolCalls", + "kind": "function", + "file": "src/webapp/src/lib/userPrefs.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/userPrefs.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/userPrefs.ts::useHideToolResults", + "name": "useHideToolResults", + "kind": "function", + "file": "src/webapp/src/lib/userPrefs.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/userPrefs.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/lib/userPrefs.ts::writeBool", + "name": "writeBool", + "kind": "function", + "file": "src/webapp/src/lib/userPrefs.ts", + "subsystem": "src/webapp/src/lib", + "complexity": 0.288, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/lib/userPrefs.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/main.tsx::main", + "name": "main", + "kind": "file", + "file": "src/webapp/src/main.tsx", + "subsystem": "src/webapp/src · App", + "complexity": 0.203, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/main.tsx.md", + "dependsOn": [ + "src/webapp/src/App.tsx::App" + ] + }, + { + "id": "src/webapp/src/pages/ChatPage.tsx::ChatPage", + "name": "ChatPage", + "kind": "function", + "file": "src/webapp/src/pages/ChatPage.tsx", + "subsystem": "src/webapp/src/components · ChatPage", + "complexity": 0.212, + "inDegree": 0, + "outDegree": 8, + "doc": "Code/src/webapp/src/pages/ChatPage.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs::ConversationResponse", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/webapp/src/api/sequence.ts::SequenceSource", + "src/webapp/src/components/Chat.tsx::Chat", + "src/webapp/src/components/ContextStats.tsx::ContextStats", + "src/webapp/src/components/GabrielSequenceView.tsx::GabrielSequenceView", + "src/webapp/src/pages/IndexPage.tsx::IndexPage", + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pages/ChatPage.tsx::persistActiveConversation", + "name": "persistActiveConversation", + "kind": "function", + "file": "src/webapp/src/pages/ChatPage.tsx", + "subsystem": "src/webapp/src/pages", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/ChatPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/DiagnosticsPage.tsx::DiagnosticsFrame", + "name": "DiagnosticsFrame", + "kind": "function", + "file": "src/webapp/src/pages/DiagnosticsPage.tsx", + "subsystem": "src/webapp/src/pages", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/DiagnosticsPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/DiagnosticsPage.tsx::DiagnosticsFrameProps", + "name": "DiagnosticsFrameProps", + "kind": "interface", + "file": "src/webapp/src/pages/DiagnosticsPage.tsx", + "subsystem": "src/webapp/src/pages", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/DiagnosticsPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/DiagnosticsPage.tsx::DiagnosticsPage", + "name": "DiagnosticsPage", + "kind": "function", + "file": "src/webapp/src/pages/DiagnosticsPage.tsx", + "subsystem": "src/webapp/src/pages · DiagnosticsPageInner", + "complexity": 0.201, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pages/DiagnosticsPage.tsx.md", + "dependsOn": [ + "src/webapp/src/pages/DiagnosticsPage.tsx::DiagnosticsPageInner" + ] + }, + { + "id": "src/webapp/src/pages/DiagnosticsPage.tsx::DiagnosticsPageInner", + "name": "DiagnosticsPageInner", + "kind": "function", + "file": "src/webapp/src/pages/DiagnosticsPage.tsx", + "subsystem": "src/webapp/src/pages · DiagnosticsPageInner", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/DiagnosticsPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/DiagnosticsPage.tsx::DiagnosticsPageProps", + "name": "DiagnosticsPageProps", + "kind": "interface", + "file": "src/webapp/src/pages/DiagnosticsPage.tsx", + "subsystem": "src/api/Gabriel.Engine · Project", + "complexity": 0.359, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/webapp/src/pages/DiagnosticsPage.tsx.md", + "dependsOn": [ + "src/api/Gabriel.Core/Entities/Conversation.cs::Conversation", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/webapp/src/api/sequence.ts::SequenceSource" + ] + }, + { + "id": "src/webapp/src/pages/DiagnosticsPage.tsx::ProjectDiagnosticsPage", + "name": "ProjectDiagnosticsPage", + "kind": "function", + "file": "src/webapp/src/pages/DiagnosticsPage.tsx", + "subsystem": "src/webapp/src/pages · DiagnosticsPageInner", + "complexity": 0.201, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pages/DiagnosticsPage.tsx.md", + "dependsOn": [ + "src/webapp/src/pages/DiagnosticsPage.tsx::DiagnosticsPageInner" + ] + }, + { + "id": "src/webapp/src/pages/DiagnosticsPage.tsx::onBack", + "name": "onBack", + "kind": "function", + "file": "src/webapp/src/pages/DiagnosticsPage.tsx", + "subsystem": "src/webapp/src/pages", + "complexity": 0.307, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/DiagnosticsPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/IndexPage.tsx::IndexPage", + "name": "IndexPage", + "kind": "function", + "file": "src/webapp/src/pages/IndexPage.tsx", + "subsystem": "src/webapp/src/components · ChatPage", + "complexity": 0.217, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/IndexPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/IndexPage.tsx::loadLastConversation", + "name": "loadLastConversation", + "kind": "function", + "file": "src/webapp/src/pages/IndexPage.tsx", + "subsystem": "src/webapp/src/pages", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/IndexPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/LoginPage.tsx::LocationState", + "name": "LocationState", + "kind": "interface", + "file": "src/webapp/src/pages/LoginPage.tsx", + "subsystem": "src/webapp/src/pages · LoginPage", + "complexity": 0.392, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/LoginPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/LoginPage.tsx::LoginPage", + "name": "LoginPage", + "kind": "function", + "file": "src/webapp/src/pages/LoginPage.tsx", + "subsystem": "src/webapp/src/pages · LoginPage", + "complexity": 0.18, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/webapp/src/pages/LoginPage.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Avatar.tsx::Avatar", + "src/webapp/src/pages/LoginPage.tsx::LocationState", + "src/webapp/src/routes/ProtectedRoute.tsx::ProtectedRoute" + ] + }, + { + "id": "src/webapp/src/pages/ProjectSettingsPage.tsx::ProjectSettingsPage", + "name": "ProjectSettingsPage", + "kind": "function", + "file": "src/webapp/src/pages/ProjectSettingsPage.tsx", + "subsystem": "src/api/Gabriel.API · ProjectResponse", + "complexity": 0.236, + "inDegree": 0, + "outDegree": 5, + "doc": "Code/src/webapp/src/pages/ProjectSettingsPage.tsx.md", + "dependsOn": [ + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectFileResponse", + "src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs::ProjectResponse", + "src/api/Gabriel.Core/Entities/Project.cs::Project", + "src/webapp/src/components/MemoryList.tsx::MemoryList", + "src/webapp/src/components/SkinPicker.tsx::SkinPicker" + ] + }, + { + "id": "src/webapp/src/pages/ProjectSettingsPage.tsx::formatBytes", + "name": "formatBytes", + "kind": "function", + "file": "src/webapp/src/pages/ProjectSettingsPage.tsx", + "subsystem": "src/webapp/src/pages", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pages/ProjectSettingsPage.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pages/RegisterPage.tsx::RegisterPage", + "name": "RegisterPage", + "kind": "function", + "file": "src/webapp/src/pages/RegisterPage.tsx", + "subsystem": "src/webapp/src/components · Avatar", + "complexity": 0.181, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pages/RegisterPage.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Avatar.tsx::Avatar" + ] + }, + { + "id": "src/webapp/src/pages/UserSettingsPage.tsx::UserSettingsPage", + "name": "UserSettingsPage", + "kind": "function", + "file": "src/webapp/src/pages/UserSettingsPage.tsx", + "subsystem": "src/webapp/src/components · UserSettingsPage", + "complexity": 0.162, + "inDegree": 0, + "outDegree": 3, + "doc": "Code/src/webapp/src/pages/UserSettingsPage.tsx.md", + "dependsOn": [ + "src/webapp/src/components/Chat.tsx::Chat", + "src/webapp/src/components/MemoryList.tsx::MemoryList", + "src/webapp/src/components/ModelSelector.tsx::ModelSelector" + ] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::Palette", + "name": "Palette", + "kind": "interface", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::RGB", + "name": "RGB", + "kind": "type", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.14, + "inDegree": 17, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::brightestStop", + "name": "brightestStop", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.33, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::gradientCssFromStops", + "name": "gradientCssFromStops", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.294, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::paletteAccent", + "name": "paletteAccent", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::paletteForSeed", + "name": "paletteForSeed", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::paletteGradientCss", + "name": "paletteGradientCss", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::paletteVarsFromStops", + "name": "paletteVarsFromStops", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::pickPalette", + "name": "pickPalette", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::rgbToCss", + "name": "rgbToCss", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pulse/palettes.ts::sampleGradient", + "name": "sampleGradient", + "kind": "function", + "file": "src/webapp/src/pulse/palettes.ts", + "subsystem": "src/webapp/src/pulse · RGB", + "complexity": 0.188, + "inDegree": 0, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/palettes.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/palettes.ts::RGB" + ] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::FlowParams", + "name": "FlowParams", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::NoiseParams", + "name": "NoiseParams", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.192, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::Pattern", + "name": "Pattern", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/components · Pattern", + "complexity": 0.192, + "inDegree": 11, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::PatternName", + "name": "PatternName", + "kind": "type", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · PatternName", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [ + "src/api/Gabriel.Engine/Sequence/Patterns.cs::Patterns" + ] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::PlasmaParams", + "name": "PlasmaParams", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.192, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::PulseParams", + "name": "PulseParams", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.192, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::ShimmerCell", + "name": "ShimmerCell", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.192, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::ShimmerParams", + "name": "ShimmerParams", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.192, + "inDegree": 2, + "outDegree": 1, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/patterns.ts::ShimmerCell" + ] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::SpiralParams", + "name": "SpiralParams", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.192, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::WavesParams", + "name": "WavesParams", + "kind": "interface", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · Patterns", + "complexity": 0.192, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::fbm", + "name": "fbm", + "kind": "function", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.207, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::hash2", + "name": "hash2", + "kind": "function", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::pickPattern", + "name": "pickPattern", + "kind": "function", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse · PatternName", + "complexity": 0.34, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [ + "src/webapp/src/pulse/patterns.ts::Pattern", + "src/webapp/src/pulse/patterns.ts::PatternName" + ] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::range", + "name": "range", + "kind": "function", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/api/Gabriel.Engine · GabrielSequence", + "complexity": 0.14, + "inDegree": 2, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::smooth", + "name": "smooth", + "kind": "function", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/patterns.ts::valueNoise", + "name": "valueNoise", + "kind": "function", + "file": "src/webapp/src/pulse/patterns.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/patterns.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/rng.ts::Rand", + "name": "Rand", + "kind": "type", + "file": "src/webapp/src/pulse/rng.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/rng.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/rng.ts::mulberry32", + "name": "mulberry32", + "kind": "function", + "file": "src/webapp/src/pulse/rng.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/rng.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/pulse/rng.ts::randomSeed", + "name": "randomSeed", + "kind": "function", + "file": "src/webapp/src/pulse/rng.ts", + "subsystem": "src/webapp/src/pulse", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/pulse/rng.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/router.tsx::ChunkErrorFallback", + "name": "ChunkErrorFallback", + "kind": "function", + "file": "src/webapp/src/router.tsx", + "subsystem": "src/webapp/src", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/router.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/router.tsx::ErrorBoundary", + "name": "ErrorBoundary", + "kind": "class", + "file": "src/webapp/src/router.tsx", + "subsystem": "src/webapp/src · ErrorBoundary", + "complexity": 0.327, + "inDegree": 0, + "outDegree": 2, + "doc": "Code/src/webapp/src/router.tsx.md", + "dependsOn": [ + "src/webapp/src/router.tsx::ErrorBoundaryProps", + "src/webapp/src/router.tsx::ErrorBoundaryState" + ] + }, + { + "id": "src/webapp/src/router.tsx::ErrorBoundaryProps", + "name": "ErrorBoundaryProps", + "kind": "interface", + "file": "src/webapp/src/router.tsx", + "subsystem": "src/webapp/src · ErrorBoundary", + "complexity": 0.192, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/router.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/router.tsx::ErrorBoundaryState", + "name": "ErrorBoundaryState", + "kind": "interface", + "file": "src/webapp/src/router.tsx", + "subsystem": "src/webapp/src · ErrorBoundary", + "complexity": 0.192, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/router.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/router.tsx::LazyPage", + "name": "LazyPage", + "kind": "function", + "file": "src/webapp/src/router.tsx", + "subsystem": "src/webapp/src", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/router.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/router.tsx::PageLoader", + "name": "PageLoader", + "kind": "function", + "file": "src/webapp/src/router.tsx", + "subsystem": "src/webapp/src", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/router.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/routes/ProtectedRoute.tsx::ProtectedRoute", + "name": "ProtectedRoute", + "kind": "function", + "file": "src/webapp/src/routes/ProtectedRoute.tsx", + "subsystem": "src/webapp/src/pages · LoginPage", + "complexity": 0.251, + "inDegree": 1, + "outDegree": 0, + "doc": "Code/src/webapp/src/routes/ProtectedRoute.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/routes/PublicRoute.tsx::PublicRoute", + "name": "PublicRoute", + "kind": "function", + "file": "src/webapp/src/routes/PublicRoute.tsx", + "subsystem": "src/webapp/src/routes", + "complexity": 0.261, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/routes/PublicRoute.tsx.md", + "dependsOn": [] + }, + { + "id": "src/webapp/src/vite-env.d.ts::vite-env.d", + "name": "vite-env.d", + "kind": "file", + "file": "src/webapp/src/vite-env.d.ts", + "subsystem": "src/webapp/src", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/src/vite-env.d.ts.md", + "dependsOn": [] + }, + { + "id": "src/webapp/vite.config.ts::vite.config", + "name": "vite.config", + "kind": "file", + "file": "src/webapp/vite.config.ts", + "subsystem": "src/webapp", + "complexity": 0.14, + "inDegree": 0, + "outDegree": 0, + "doc": "Code/src/webapp/vite.config.ts.md", + "dependsOn": [] + } + ] +} \ No newline at end of file diff --git a/docs/auriondocs/Code/index.md b/docs/auriondocs/Code/index.md new file mode 100644 index 0000000..7715014 --- /dev/null +++ b/docs/auriondocs/Code/index.md @@ -0,0 +1,453 @@ +# HueByte/Gabriel — Documentation + +*Generated from branch `main`* +*Commit: `0343e2f8`* + +## Languages + +- **C#**: 200 files +- **TypeScript (React)**: 31 files +- **TypeScript**: 14 files +- **JavaScript**: 5 files + +## Statistics + +- **Files processed:** 250 +- **Lines of code:** 22,515 +- **Symbols documented:** 532 +- **Validation retries:** 135 + +## Documentation Index + +### prototype + +- [generate](prototype/generate.js.md) +- [palettes](prototype/palettes.js.md) +- [patterns](prototype/patterns.js.md) +- [play](prototype/play.js.md) +- [run](prototype/run.js.md) + +### src/api/Gabriel.API + +- [Program](src/api/Gabriel.API/Program.cs.md) + +### src/api/Gabriel.API/Configuration + +- [GlobalRoutePrefixConvention](src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs.md) +- [InfisicalConfigurationProvider](src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md) +- [InfisicalConfigurationSource](src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md) +- [InfisicalExtensions](src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md) +- [LogDateEnricher](src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md) + +### src/api/Gabriel.API/Contracts/Auth + +- [JwtResponse](src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md) +- [LoginRequest](src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md) +- [MeResponse](src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md) +- [RefreshTokenRequest](src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md) +- [RegisterRequest](src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs.md) + +### src/api/Gabriel.API/Contracts/Conversations + +- [ContextMetricsResponse](src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md) +- [ConversationResponse](src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md) +- [CreateConversationRequest](src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs.md) +- [SetConversationModeRequest](src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs.md) +- [UpdateConversationRequest](src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs.md) + +### src/api/Gabriel.API/Contracts/Diagnostics + +- [MetricEntryDto](src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md) +- [WebSearchDiagnosticsResponse](src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md) + +### src/api/Gabriel.API/Contracts/Memories + +- [MemoryDto](src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md) + +### src/api/Gabriel.API/Contracts/Messages + +- [MessageResponse](src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md) +- [SendMessageRequest](src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs.md) + +### src/api/Gabriel.API/Contracts/Models + +- [ModelDto](src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md) + +### src/api/Gabriel.API/Contracts/Projects + +- [ProjectResponse](src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md) + +### src/api/Gabriel.API/Contracts/Sequence + +- [GabrielSequenceResponse](src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md) +- [SequenceCatalogResponse](src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md) + +### src/api/Gabriel.API/Controllers + +- [AuthController](src/api/Gabriel.API/Controllers/AuthController.cs.md) +- [ConversationsController](src/api/Gabriel.API/Controllers/ConversationsController.cs.md) +- [DiagnosticsController](src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md) +- [MemoriesController](src/api/Gabriel.API/Controllers/MemoriesController.cs.md) +- [ModelsController](src/api/Gabriel.API/Controllers/ModelsController.cs.md) +- [ProjectFilesController](src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md) +- [ProjectsController](src/api/Gabriel.API/Controllers/ProjectsController.cs.md) +- [SequenceController](src/api/Gabriel.API/Controllers/SequenceController.cs.md) + +### src/api/Gabriel.API/Identity + +- [AuthCookies](src/api/Gabriel.API/Identity/AuthCookies.cs.md) +- [HttpContextCurrentUser](src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs.md) +- [IdentitySeeder](src/api/Gabriel.API/Identity/IdentitySeeder.cs.md) + +### src/api/Gabriel.API/Mapping + +- [ContractMappings](src/api/Gabriel.API/Mapping/ContractMappings.cs.md) + +### src/api/Gabriel.API/Middleware + +- [GlobalExceptionHandler](src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md) +- [RequireNonNullablePropertiesSchemaFilter](src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs.md) + +### src/api/Gabriel.Core + +- [DependencyInjection](src/api/Gabriel.Core/DependencyInjection.cs.md) + +### src/api/Gabriel.Core/Configuration + +- [AgentOptions](src/api/Gabriel.Core/Configuration/AgentOptions.cs.md) +- [AgentToolsOptions](src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md) +- [AuthOptions](src/api/Gabriel.Core/Configuration/AuthOptions.cs.md) +- [BraveSearchOptions](src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md) +- [GitHubDocsOptions](src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs.md) +- [GrokOptions](src/api/Gabriel.Core/Configuration/GrokOptions.cs.md) +- [IConfigSection](src/api/Gabriel.Core/Configuration/IConfigSection.cs.md) +- [InfisicalOptions](src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md) +- [JwtOptions](src/api/Gabriel.Core/Configuration/JwtOptions.cs.md) +- [LLMModel](src/api/Gabriel.Core/Configuration/LLMModel.cs.md) +- [LLMProviderOptions](src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs.md) +- [LocalDocsOptions](src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs.md) +- [ModelSelection](src/api/Gabriel.Core/Configuration/ModelSelection.cs.md) +- [PersonalityOptions](src/api/Gabriel.Core/Configuration/PersonalityOptions.cs.md) +- [ProjectFilesOptions](src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs.md) +- [TavilySearchOptions](src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs.md) +- [ToolMode](src/api/Gabriel.Core/Configuration/ToolMode.cs.md) + +### src/api/Gabriel.Core/Entities + +- [Conversation](src/api/Gabriel.Core/Entities/Conversation.cs.md) +- [GabrielMode](src/api/Gabriel.Core/Entities/GabrielMode.cs.md) +- [MemoryEntry](src/api/Gabriel.Core/Entities/MemoryEntry.cs.md) +- [MemoryEntryType](src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md) +- [Message](src/api/Gabriel.Core/Entities/Message.cs.md) +- [MessageRole](src/api/Gabriel.Core/Entities/MessageRole.cs.md) +- [MetricEntry](src/api/Gabriel.Core/Entities/MetricEntry.cs.md) +- [Project](src/api/Gabriel.Core/Entities/Project.cs.md) +- [ProjectFile](src/api/Gabriel.Core/Entities/ProjectFile.cs.md) + +### src/api/Gabriel.Core/Exceptions + +- [DomainException](src/api/Gabriel.Core/Exceptions/DomainException.cs.md) +- [NotFoundException](src/api/Gabriel.Core/Exceptions/NotFoundException.cs.md) + +### src/api/Gabriel.Core/Identity + +- [ICurrentUser](src/api/Gabriel.Core/Identity/ICurrentUser.cs.md) +- [IJwtTokenService](src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) +- [IRefreshTokenStore](src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs.md) +- [IUserPreferences](src/api/Gabriel.Core/Identity/IUserPreferences.cs.md) +- [RefreshToken](src/api/Gabriel.Core/Identity/RefreshToken.cs.md) + +### src/api/Gabriel.Core/Personality + +- [ConversationState](src/api/Gabriel.Core/Personality/ConversationState.cs.md) +- [Mood](src/api/Gabriel.Core/Personality/Mood.cs.md) + +### src/api/Gabriel.Core/Repositories + +- [IConversationRepository](src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) +- [IMemoryRepository](src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md) +- [IMetricRepository](src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md) +- [IProjectRepository](src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md) +- [IUnitOfWork](src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md) + +### src/api/Gabriel.Core/Services + +- [ChatService](src/api/Gabriel.Core/Services/ChatService.cs.md) +- [IChatService](src/api/Gabriel.Core/Services/IChatService.cs.md) +- [IMemoryService](src/api/Gabriel.Core/Services/IMemoryService.cs.md) +- [IProjectFileService](src/api/Gabriel.Core/Services/IProjectFileService.cs.md) +- [IProjectService](src/api/Gabriel.Core/Services/IProjectService.cs.md) +- [MemoryService](src/api/Gabriel.Core/Services/MemoryService.cs.md) +- [ProjectService](src/api/Gabriel.Core/Services/ProjectService.cs.md) + +### src/api/Gabriel.Engine + +- [DependencyInjection](src/api/Gabriel.Engine/DependencyInjection.cs.md) + +### src/api/Gabriel.Engine/Personality + +- [GabrielSystemPromptBuilder](src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md) +- [HeuristicConversationStateUpdater](src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs.md) +- [IConversationStateUpdater](src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs.md) +- [IResponsePostProcessor](src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs.md) +- [ISystemPromptBuilder](src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md) +- [ResponsePostProcessor](src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs.md) + +### src/api/Gabriel.Engine/Personality/Prompts + +- [Fragments.FewShot](src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs.md) +- [Fragments.Formatting](src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs.md) +- [Fragments.Memory](src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs.md) +- [Fragments.Modes](src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs.md) +- [Fragments.Persona](src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs.md) +- [IPromptRegistry](src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs.md) +- [PromptKey](src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs.md) +- [PromptRegistry](src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs.md) + +### src/api/Gabriel.Engine/Providers + +- [AvailableModel](src/api/Gabriel.Engine/Providers/AvailableModel.cs.md) +- [ChatProviderEvent](src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md) +- [ChatProviderMessage](src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs.md) +- [ChatProviderToolCall](src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs.md) +- [IChatProvider](src/api/Gabriel.Engine/Providers/IChatProvider.cs.md) +- [IChatProviderRegistry](src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md) +- [IModelCatalog](src/api/Gabriel.Engine/Providers/IModelCatalog.cs.md) +- [ModelCatalog](src/api/Gabriel.Engine/Providers/ModelCatalog.cs.md) +- [ToolDescriptor](src/api/Gabriel.Engine/Providers/ToolDescriptor.cs.md) + +### src/api/Gabriel.Engine/Providers/ToolBridge + +- [GabrielToolBridge](src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) +- [ToolCallBlockParser](src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md) +- [ToolCallStreamSplitter](src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs.md) + +### src/api/Gabriel.Engine/Sequence + +- [Frame](src/api/Gabriel.Engine/Sequence/Frame.cs.md) +- [FrameLayer](src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md) +- [GabrielSequence](src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md) +- [GabrielSequenceGenerator](src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md) +- [GabrielSequenceService](src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md) +- [IGabrielSequenceGenerator](src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md) +- [IGabrielSequenceService](src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs.md) +- [Noise](src/api/Gabriel.Engine/Sequence/Noise.cs.md) +- [Palette](src/api/Gabriel.Engine/Sequence/Palette.cs.md) +- [PaletteTemplates](src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs.md) +- [PatternKind](src/api/Gabriel.Engine/Sequence/PatternKind.cs.md) +- [Patterns](src/api/Gabriel.Engine/Sequence/Patterns.cs.md) +- [RgbColor](src/api/Gabriel.Engine/Sequence/RgbColor.cs.md) +- [SequenceCatalog](src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) + +### src/api/Gabriel.Engine/Services + +- [AgentContext](src/api/Gabriel.Engine/Services/AgentContext.cs.md) +- [AgentEvent](src/api/Gabriel.Engine/Services/AgentEvent.cs.md) +- [AgentService](src/api/Gabriel.Engine/Services/AgentService.cs.md) +- [ContextMetrics](src/api/Gabriel.Engine/Services/ContextMetrics.cs.md) +- [IAgentService](src/api/Gabriel.Engine/Services/IAgentService.cs.md) +- [IMetricRecorder](src/api/Gabriel.Engine/Services/IMetricRecorder.cs.md) +- [ITokenEstimator](src/api/Gabriel.Engine/Services/ITokenEstimator.cs.md) +- [MetricRecorder](src/api/Gabriel.Engine/Services/MetricRecorder.cs.md) +- [NaiveTokenEstimator](src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs.md) + +### src/api/Gabriel.Engine/Tools + +- [GetCurrentTimeTool](src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md) +- [ITool](src/api/Gabriel.Engine/Tools/ITool.cs.md) +- [IToolExecutionContext](src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md) +- [IToolRegistry](src/api/Gabriel.Engine/Tools/IToolRegistry.cs.md) +- [ToolRegistry](src/api/Gabriel.Engine/Tools/ToolRegistry.cs.md) + +### src/api/Gabriel.Engine/Tools/Calc + +- [CalculateTool](src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Codecs + +- [Base64Tool](src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md) +- [HashTool](src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Colors + +- [ColorConvertTool](src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Data + +- [JsonFormatTool](src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Docs + +- [DocsListTool](src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md) +- [DocsReadTool](src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md) +- [IDocsLookup](src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md) + +### src/api/Gabriel.Engine/Tools/Files + +- [AgentPathResolver](src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md) +- [FileInfoTool](src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md) +- [FindTool](src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md) +- [GrepTool](src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md) +- [IAgentPathResolver](src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md) +- [ListDirTool](src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Memory + +- [MemoryListTool](src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md) +- [MemoryRemoveTool](src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md) +- [MemorySaveTool](src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Numbers + +- [BaseConvertTool](src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Projects + +- [ListProjectFilesTool](src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md) +- [ReadProjectFileTool](src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Strings + +- [TextStatsTool](src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md) +- [TextTransformTool](src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md) + +### src/api/Gabriel.Engine/Tools/Web + +- [IUrlFetcher](src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md) +- [IWebSearch](src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) +- [WebFetchTool](src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md) +- [WebSearchTool](src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md) + +### src/api/Gabriel.Infrastructure + +- [DependencyInjection](src/api/Gabriel.Infrastructure/DependencyInjection.cs.md) + +### src/api/Gabriel.Infrastructure/Identity + +- [ApplicationUser](src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md) +- [IdentityServiceCollectionExtensions](src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md) +- [JwtTokenService](src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md) +- [UserPreferencesService](src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs.md) + +### src/api/Gabriel.Infrastructure/Persistence + +- [AppDbContext](src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md) +- [UnitOfWork](src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md) + +### src/api/Gabriel.Infrastructure/Persistence/Configurations + +- [ConversationConfiguration](src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md) +- [MemoryEntryConfiguration](src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs.md) +- [MessageConfiguration](src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs.md) +- [MetricEntryConfiguration](src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs.md) +- [ProjectConfiguration](src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs.md) +- [ProjectFileConfiguration](src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs.md) +- [RefreshTokenConfiguration](src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs.md) + +### src/api/Gabriel.Infrastructure/Persistence/Repositories + +- [ConversationRepository](src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md) +- [MemoryRepository](src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md) +- [MetricRepository](src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs.md) +- [ProjectRepository](src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs.md) +- [RefreshTokenStore](src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs.md) + +### src/api/Gabriel.Infrastructure/Projects + +- [DiskProjectFileService](src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs.md) + +### src/api/Gabriel.Infrastructure/Providers + +- [GrokAuthHandler](src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs.md) +- [GrokChatProvider](src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md) +- [MockChatProvider](src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md) + +### src/api/Gabriel.Infrastructure/Tools/Docs + +- [CompositeDocsLookup](src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs.md) +- [GitHubDocsLookup](src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs.md) +- [LocalDocsLookup](src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs.md) + +### src/api/Gabriel.Infrastructure/Tools/Web + +- [BraveWebSearch](src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md) +- [CompositeWebSearch](src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs.md) +- [DuckDuckGoWebSearch](src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md) +- [HttpUrlFetcher](src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs.md) +- [InstrumentedWebSearch](src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md) +- [TavilyWebSearch](src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md) + +### src/webapp + +- [vite.config](src/webapp/vite.config.ts.md) + +### src/webapp/src + +- [App](src/webapp/src/App.tsx.md) +- [main](src/webapp/src/main.tsx.md) +- [router](src/webapp/src/router.tsx.md) +- [vite-env.d](src/webapp/src/vite-env.d.ts.md) + +### src/webapp/src/api + +- [authInterceptor](src/webapp/src/api/authInterceptor.ts.md) +- [authRefresh](src/webapp/src/api/authRefresh.ts.md) +- [conversationMode](src/webapp/src/api/conversationMode.ts.md) +- [memories](src/webapp/src/api/memories.ts.md) +- [models](src/webapp/src/api/models.ts.md) +- [sequence](src/webapp/src/api/sequence.ts.md) +- [streamChat](src/webapp/src/api/streamChat.ts.md) + +### src/webapp/src/auth + +- [AuthContext](src/webapp/src/auth/AuthContext.tsx.md) + +### src/webapp/src/components + +- [Avatar](src/webapp/src/components/Avatar.tsx.md) +- [Chat](src/webapp/src/components/Chat.tsx.md) +- [CompactingOverlay](src/webapp/src/components/CompactingOverlay.tsx.md) +- [ContextStats](src/webapp/src/components/ContextStats.tsx.md) +- [GabrielSequenceView](src/webapp/src/components/GabrielSequenceView.tsx.md) +- [Markdown](src/webapp/src/components/Markdown.tsx.md) +- [MemoryList](src/webapp/src/components/MemoryList.tsx.md) +- [MemoryQuickSave](src/webapp/src/components/MemoryQuickSave.tsx.md) +- [Mermaid](src/webapp/src/components/Mermaid.tsx.md) +- [ModeSelector](src/webapp/src/components/ModeSelector.tsx.md) +- [ModelSelector](src/webapp/src/components/ModelSelector.tsx.md) +- [ProjectPicker](src/webapp/src/components/ProjectPicker.tsx.md) +- [Sidebar](src/webapp/src/components/Sidebar.tsx.md) +- [SkinPicker](src/webapp/src/components/SkinPicker.tsx.md) +- [StreamingText](src/webapp/src/components/StreamingText.tsx.md) +- [ThinkingPulse](src/webapp/src/components/ThinkingPulse.tsx.md) + +### src/webapp/src/layouts + +- [AuthLayout](src/webapp/src/layouts/AuthLayout.tsx.md) +- [MainLayout](src/webapp/src/layouts/MainLayout.tsx.md) + +### src/webapp/src/lib + +- [notify](src/webapp/src/lib/notify.ts.md) +- [userPrefs](src/webapp/src/lib/userPrefs.ts.md) + +### src/webapp/src/pages + +- [ChatPage](src/webapp/src/pages/ChatPage.tsx.md) +- [DiagnosticsPage](src/webapp/src/pages/DiagnosticsPage.tsx.md) +- [IndexPage](src/webapp/src/pages/IndexPage.tsx.md) +- [LoginPage](src/webapp/src/pages/LoginPage.tsx.md) +- [ProjectSettingsPage](src/webapp/src/pages/ProjectSettingsPage.tsx.md) +- [RegisterPage](src/webapp/src/pages/RegisterPage.tsx.md) +- [UserSettingsPage](src/webapp/src/pages/UserSettingsPage.tsx.md) + +### src/webapp/src/pulse + +- [palettes](src/webapp/src/pulse/palettes.ts.md) +- [patterns](src/webapp/src/pulse/patterns.ts.md) +- [rng](src/webapp/src/pulse/rng.ts.md) + +### src/webapp/src/routes + +- [ProtectedRoute](src/webapp/src/routes/ProtectedRoute.tsx.md) +- [PublicRoute](src/webapp/src/routes/PublicRoute.tsx.md) \ No newline at end of file diff --git a/docs/auriondocs/Code/prototype/generate.js.md b/docs/auriondocs/Code/prototype/generate.js.md new file mode 100644 index 0000000..ba88837 --- /dev/null +++ b/docs/auriondocs/Code/prototype/generate.js.md @@ -0,0 +1,68 @@ +# generate.js + +> **Source:** `prototype/generate.js` + +## Contents + +- [generate](#generate) +- [rand](#rand) + +--- + +## generate +> **File:** `prototype/generate.js` +> **Kind:** function + +```javascript +function generate() +``` + + +Produces FRAMES frames of SIZE-by-SIZE pixel grids by sampling a chosen procedural pattern over time, then applies a small per-pixel jitter and an ambient bias before clamping to [0, 1]. It selects a palette and pattern, initializes the pattern parameters, and returns both the frame sequence and a meta object that captures the generation settings for downstream reuse. + +## Remarks + +This abstraction cleanly separates per-pattern logic from the frame-generation loop and time progression. Pattern sampling is delegated to pattern.sample (fed with time, coordinates, and per-pattern params), while generate orchestrates frame construction, injecting controlled noise and an ambient floor to preserve gradient structure. The returned meta exposes the pattern, its parameters, and the noise/ambient configuration to aid inspection, tweaking, or reproducibility in tooling. + +## Notes + +- The per-pixel noise is nondeterministic (uses Math.random), so results will vary across runs unless the RNG is seeded externally. +- The function relies on global constants SIZE and FRAMES and on the availability of pickPalette and pickPattern; ensure these are defined and aligned with the surrounding runtime. + + +--- + +## rand +> **File:** `prototype/generate.js` +> **Kind:** function + +```javascript +function rand(min, max) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `min` | — | — | +| `max` | — | — | + + +Generates a random floating-point number within the provided numeric range by scaling Math.random(). It returns a value in [min, max) when min <= max, using a uniform distribution. Use it whenever you need a quick, readable way to obtain a random value within a numeric range, instead of writing the scaling expression inline. + +## Remarks +By encapsulating the common min-to-max formula, rand communicates intent and centralizes the randomness logic. It's a pure function with no side effects, depending solely on Math.random() and the numeric inputs. + +## Example +```javascript +// Example: get a random value between 5 and 10 +const r = rand(5, 10); +console.log(r); +``` + +## Notes +- No input validation: non-numeric inputs may produce NaN. +- If min > max, the result lies between max and min; ensure proper order to get the conventional [min, max) interval. +- Not cryptographically secure; for security-critical randomness, use a crypto-secure API (e.g., crypto.getRandomValues in browsers or Node's crypto module). + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/prototype/palettes.js.md b/docs/auriondocs/Code/prototype/palettes.js.md new file mode 100644 index 0000000..1102f76 --- /dev/null +++ b/docs/auriondocs/Code/prototype/palettes.js.md @@ -0,0 +1,66 @@ +# palettes.js + +> **Source:** `prototype/palettes.js` + +## Contents + +- [pickPalette](#pickpalette) +- [sampleGradient](#samplegradient) + +--- + +## pickPalette +> **File:** `prototype/palettes.js` +> **Kind:** function + +```javascript +function pickPalette() +``` + + +pickPalette returns a random element from the PALETTES collection, encapsulating the common pattern of selecting a palette without exposing the underlying array or the random-index calculation to callers. It relies on Math and PALETTES, so callers simply call pickPalette() to obtain a color palette for UI or visualization tasks. + +## Remarks +By deriving the result from PALETTES.length, the function ensures each palette has an equal probability of being selected. The approach centralizes the randomness strategy in one place, making it easy to adjust the source list of palettes in one location. This abstraction helps keep visual-theming consistent across the codebase by routing palette selection through a single helper. + +## Notes +- Edge-case: If PALETTES.length is 0, the function returns undefined. +- If PALETTES is mutated at runtime (e.g., swapped out or cleared), subsequent calls will reflect the new state and probabilities. + + +--- + +## sampleGradient +> **File:** `prototype/palettes.js` +> **Kind:** function + +```javascript +function sampleGradient(stops, t) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `stops` | — | — | +| `t` | — | — | + + +Maps a normalized parameter t in [0, 1] to a color along a linear gradient defined by an array of RGB color stops; it clamps t, handles the single-stop case, and linearly interpolates between adjacent stops to produce an RGB triplet. + +## Remarks +This function encapsulates the common gradient-lookup pattern used when rendering color palettes or themed UI elements. It hides the segment calculation and per-channel interpolation behind a simple, reusable API, so callers can focus on palette design rather than math. The contract assumes stops are ordered along the gradient and that each stop is an [r, g, b] triplet of numbers; input validation is left to higher-level code. + +## Example +```javascript +// Example: interpolate halfway between black and white +const stops = [[0, 0, 0], [255, 255, 255]]; +console.log(sampleGradient(stops, 0.5)); // [128, 128, 128] +``` + +## Notes +- At least one stop is required; an empty array will lead to incorrect behavior. If stops.length === 1, the single color is returned. +- t is clamped to [0, 1], so values outside this range map to the gradient endpoints. +- Each color component is rounded to the nearest integer; ensure inputs are numeric and within 0–255 for predictable results. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/prototype/patterns.js.md b/docs/auriondocs/Code/prototype/patterns.js.md new file mode 100644 index 0000000..e7d4881 --- /dev/null +++ b/docs/auriondocs/Code/prototype/patterns.js.md @@ -0,0 +1,238 @@ +# patterns.js + +> **Source:** `prototype/patterns.js` + +## Contents + +- [fbm](#fbm) +- [hash2](#hash2) +- [pick](#pick) +- [pickPattern](#pickpattern) +- [rand](#rand) +- [smooth](#smooth) +- [valueNoise](#valuenoise) + +--- + +## fbm +> **File:** `prototype/patterns.js` +> **Kind:** function + +```javascript +function fbm(x, y, seed, octaves) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `x` | — | — | +| `y` | — | — | +| `seed` | — | — | +| `octaves` | — | — | + + +Computes fractal Brownian motion (fbm) by layering multiple octaves of valueNoise across increasing frequencies and decreasing amplitudes, then normalizing by the sum of amplitudes. This yields a smooth, natural-looking scalar field that is commonly used for terrain and texture generation. The function takes spatial coordinates x and y, a seed for deterministic results, and a positive octaves count that controls detail; per-octave seeds are derived from the base seed with an offset (seed + i * 17) to decorrelate each layer. + +## Remarks + +fbm is a convenience function that builds richer noise by combining a base noise source (valueNoise) at multiple scales. Normalizing by the total amplitude keeps outputs in a consistent relative range as you vary octaves. The fixed offset 17 ensures octaves do not reuse identical noise samples. + +## Example + +```javascript +// Example: evaluate fbm at a point with moderate detail +const value = fbm(12.5, 7.25, 1337, 5); +``` + +## Notes + +- Octaves should be >= 1; otherwise the function divides by zero and yields NaN in JavaScript. +- The output range depends on valueNoise's range; normalization by total amplitude keeps the relative scale consistent across octaves, but callers should be aware of potential NaN if octaves is invalid or if valueNoise returns unexpected values. + +--- + +## hash2 +> **File:** `prototype/patterns.js` +> **Kind:** function + +```javascript +function hash2(x, y, seed) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `x` | — | — | +| `y` | — | — | +| `seed` | — | — | + + +hash2 is a compact, deterministic hash-like function that maps three 32-bit inputs (x, y, and seed) to a floating-point value in the range [0, 1]. It uses 32-bit integer arithmetic via Math.imul and bitwise operations to mix the inputs and produce a repeatable result. You would use hash2 when you need a fast, non-cryptographic pseudo-random value that is entirely determined by the inputs, making it suitable for procedural generation, texture coordinates, or noise sampling where reproducibility is important. + +## Remarks +By encapsulating the bit-twiddling inside hash2, callers get a stable, coordinate- and seed-based value without re-implementing the same mixing logic. It's intentionally lightweight and fast, trading cryptographic strength for deterministic distribution suitable for non-security-critical randomness. The presence of x|0, y|0, and seed|0 makes clear that inputs are treated as 32-bit integers, ensuring consistent results across platforms that honor JavaScript's 32-bit bitwise semantics. + +## Example +```javascript +// Example: deterministic value for coordinates (x, y) with a seed +const v = hash2(12, 34, 7); +console.log(v); // e.g. 0.472... +``` + +## Notes +- Inputs are coerced to 32-bit integers using x|0, y|0, seed|0; large or fractional inputs wrap according to 32-bit semantics. +- The function is non-cryptographic; suitable for procedural generation, not for security. +- Output is a normalized value between 0 and 1 (inclusive of 1 in edge cases) based on 32-bit mixing via Math.imul and bit shifts. + +--- + +## pick +> **File:** `prototype/patterns.js` +> **Kind:** function + +```javascript +function pick(arr) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `arr` | — | — | + + +Returns a random element from the provided array by selecting a uniformly distributed index from 0 to arr.length - 1 using Math.random. This tiny helper consolidates the common pattern of sampling a single item without mutating the array. If the input array is empty, the function yields undefined, so callers should handle that case. + +## Remarks +This abstraction centralizes the single-item random selection pattern, so callers don't repeat the index math and bounds checks. It isolates the dependency on Math.random, making it easier to swap in a seeded RNG for tests or future customization. It is intended for quick, non-mutating access to a random element from a list. + +## Notes +- If arr.length === 0, the result is undefined; callers should guard against this. +- No input validation; passing non-array-like values can yield unexpected results. +- Uses Math.random(), which is not cryptographically secure; avoid using it for security-sensitive randomness. + + +--- + +## pickPattern +> **File:** `prototype/patterns.js` +> **Kind:** function + +```javascript +function pickPattern(name) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `name` | — | — | + + +pickPattern is a small helper that resolves a pattern definition from the PATTERNS catalog. If you pass a name that exists in PATTERNS, you get back an object containing that name and its definition. If you pass a name that isn't present, it emits a warning listing available patterns and falls back to picking a random pattern. If no name is provided, the function also picks a random pattern. In all cases, the function returns an object with the shape { name, def } where def is the corresponding entry from PATTERNS. + +## Remarks + +pickPattern centralizes the policy for selecting a pattern: honor an explicit request when possible, otherwise provide a concrete pattern by falling back to a random one instead of failing. It relies on the PATTERNS map and standard JavaScript utilities (Object.keys, Math.random) to enumerate and select patterns. Because the random path is nondeterministic, tests or callers should not rely on a fixed result unless they control Math.random or mock PATTERNS. + +## Notes +- Relies on PATTERNS containing at least one entry; an empty PATTERNS would yield an object with undefined name/def when the random path is taken. +- When a non-existent name is provided, a warning is emitted via console.warn and the available keys are listed in the message. +- The randomness means outputs are non-deterministic unless Math.random (or PATTERNS) is controlled/mocked in the caller. + +--- + +## rand +> **File:** `prototype/patterns.js` +> **Kind:** function + +```javascript +function rand(min, max) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `min` | — | — | +| `max` | — | — | + + +Generates a pseudo-random floating-point number in the half-open interval [min, max) by scaling Math.random() to the desired range. Use this helper when you need a quick, in-place random value between two bounds without pulling in a larger RNG utility. + +## Remarks +This tiny wrapper centralizes the common pattern of sampling within a bounded range, making simple usage sites more concise and readable. The distribution is uniform across [min, max) as long as max > min; if max <= min, the result lies within [max, min), which can be surprising. Because it relies on Math.random(), it is not suitable for cryptographic randomness; for security-sensitive work, use the Web Crypto API (e.g., crypto.getRandomValues). + +## Notes +- Math.random() is a pseudo-random generator and is not cryptographically secure. +- If min or max are not numeric, the result is NaN. +- If max == min, the result is exactly min. If max < min, the result lies between max and min, so ensure proper ordering or add input validation. + +--- + +## smooth +> **File:** `prototype/patterns.js` +> **Kind:** function + +```javascript +function smooth(t) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `t` | — | — | + + +This function implements the smoothstep easing curve, taking a parameter t (typically in [0,1]) and returning a smoothly eased value between 0 and 1 using t^2(3−2t). Use it for animations or interpolations when you want an ease-in/ease-out rather than a linear ramp. + +## Remarks +It is the standard cubic Hermite easing with zero tangents at both ends, producing a gentle S-shaped curve. Note that the implementation does not clamp t; inputs outside [0,1] can yield results outside the [0,1] range, so clamp t if you require strict bounds. To interpolate between two endpoints a and b, compute a + (b - a) * smooth(t). + +## Example +```javascript +// Example: easing a value from start to end using smooth(t) +const start = 0; +const end = 100; +const t = 0.7; +const easedProgress = smooth(t); // in [0,1] +const value = start + (end - start) * easedProgress; // interpolated value +``` + +## Notes +- Not clamped: inputs outside [0,1] yield outputs outside [0,1]. Clamp if you require strict bounds. +- For simple interpolations, combine smooth(t) with a linear interpolation formula to move between two endpoints. + +--- + +## valueNoise +> **File:** `prototype/patterns.js` +> **Kind:** function + +```javascript +function valueNoise(x, y, seed) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `x` | — | — | +| `y` | — | — | +| `seed` | — | — | + + +Computes a deterministic 2D value by bilinearly interpolating four corner samples produced by hash2 at the cell surrounding (x, y). A seed controls the hash-to-value mapping, and the fractional offsets are smoothed before interpolation to create a smoothly varying value noise field. + +## Remarks +Value noise like this provides spatially coherent randomness suitable for textures and terrain. It encapsulates randomness behind hash2 and interpolation behind smooth, enabling consistent results for the same seed and coordinates. This modular approach makes it easy to compose multi-octave noise by layering calls with different seeds or coordinate transforms. + +## Notes +- The output range depends on what hash2 returns; if you need a normalized [0, 1] value, ensure hash2 yields values in that range or clamp afterwards. +- This function yields C0 continuity across cell boundaries due to bilinear interpolation; to obtain finer fractal detail, combine multiple octaves with varying scales/seeds. +- It relies on external helpers hash2 and smooth; ensure they are pure functions to preserve determinism across identical inputs. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/prototype/play.js.md b/docs/auriondocs/Code/prototype/play.js.md new file mode 100644 index 0000000..eac79da --- /dev/null +++ b/docs/auriondocs/Code/prototype/play.js.md @@ -0,0 +1,26 @@ +# renderFrame + +> **File:** `prototype/play.js` +> **Kind:** function + +```javascript +function renderFrame(grid, stops) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `grid` | — | — | +| `stops` | — | — | + + +Renders a terminal frame by turning a 2D grid of color indices into a colored text image. It processes two rows at a time: the top row colors are used as the foreground, the bottom row colors as the background, and a single Unicode upper-half block character ('▀') combines them into one cell. Each color is produced by sampling via sampleGradient(stops, value), and emitted as 24-bit ANSI color codes (foreground 38;2;R;G;B and background 48;2;R;G;B). The function returns the assembled string, which can be printed to the terminal to display a gradient frame. + +## Remarks +Separating gradient sampling from the frame assembly lets you reuse renderFrame with different grids or color stops without touching the rendering logic. It exploits true color ANSI escapes and the Unicode upper-half block to achieve two colors per character cell, yielding higher vertical fidelity than a single-color per cell approach. The routine assumes a rectangular grid and relies on sampleGradient for actual RGB computation from the stops array. + +## Notes +- Terminal support: requires 24-bit color (true color) and Unicode block characters for correct rendering. +- Odd height handling: if the grid height is odd, the last top row renders with a default bottom color (0); ensure your input accounts for this or handle it as needed. +- Color reset behavior: the code appends an ANSI reset after each line to avoid color leakage into subsequent output. \ No newline at end of file diff --git a/docs/auriondocs/Code/prototype/run.js.md b/docs/auriondocs/Code/prototype/run.js.md new file mode 100644 index 0000000..590eb8e --- /dev/null +++ b/docs/auriondocs/Code/prototype/run.js.md @@ -0,0 +1,15 @@ +# run + +> **File:** `prototype/run.js` +> **Kind:** file + + +run.js is a tiny Node.js harness that orchestrates a two-step workflow: generate frames for a given pattern and then play back those frames. It accepts an optional patternName argument, which it forwards to generate.js to produce a fresh frames.json; after successful generation, it runs play.js without additional arguments to render or replay the generated frames. If either step terminates with a non-zero exit code, run.js exits immediately with that code, propagating the failure to the caller. Use this when you want a repeatable, end-to-end test consisting of generation followed by playback without manual intervention. + +## Remarks +This script centralizes the workflow as a single, repeatable command. By using spawnSync and streaming stdio, it preserves the interactive logging of the two sub-scripts while ensuring strict sequential ordering—the playback never starts until generation completes successfully. The small surface area and reliance on the existing scripts keeps concerns localized to orchestration rather than the generation or playback logic. + +## Notes +- The optional patternName is only passed to generate.js; playback relies on the freshly produced frames.json. +- If generate.js or play.js fails, run.js exits with the exact exit code, which helps downstream tooling react appropriately. +- Because run.js uses synchronous child process spawning, the Node process will be blocked until each step finishes; long-running steps will appear unresponsive from the perspective of the parent shell until completion. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs.md new file mode 100644 index 0000000..58dac74 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs.md @@ -0,0 +1,18 @@ +# GlobalRoutePrefixConvention + +> **File:** `src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs` +> **Kind:** class + +```csharp +public class GlobalRoutePrefixConvention : IApplicationModelConvention +``` + + +GlobalRoutePrefixConvention centralizes the API base path by prepending a fixed prefix to every controller route. It allows you to define action routes without repeating the base path in each Route attribute and keeps routing concerns in one place to prevent drift between controllers. + +## Remarks +It lives in the MVC configuration as an IApplicationModelConvention, modifying selector routes so every endpoint inherits the same base prefix. By consolidating the routing prefix logic here, changes to the API base path require only this one location, minimizing drift and mistakes across controllers. + +## Notes +- If an action or controller already defines a full route template, the final route is the prefix combined with that template; verify resulting URLs to avoid surprises. +- Use a relative prefix (e.g., "api") rather than an absolute "/api" to align with how AttributeRouteModel.CombineAttributeRouteModel behaves. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md new file mode 100644 index 0000000..db57d51 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md @@ -0,0 +1,19 @@ +# InfisicalConfigurationProvider + +> **File:** `src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs` +> **Kind:** class + +```csharp +public class InfisicalConfigurationProvider : ConfigurationProvider +``` + + +InfisicalConfigurationProvider pulls secrets from a self-hosted Infisical instance at startup and merges them into IConfiguration. It authenticates using clientId and clientSecret, fetches environment-scoped secrets, and flattens the results by converting Infisical keys that use double-underscore separators into colon-delimited configuration keys, enabling IOptions binding (for example, Providers:Grok:ApiKey). This bootstrap provider runs during configuration building (before the DI container exists) and populates Data so configuration keys are available to the application startup. + +## Remarks +InfisicalConfigurationProvider encapsulates remote secret loading as a bootstrap concern, isolating network I/O from the rest of the configuration system. The key transform (__ to :) aligns Infisical secret naming with .NET configuration paths, so secrets become first-class config entries usable by IOptions. Because Load runs during configuration building, failures are logged to Console.Error instead of crashing startup; missing keys surface only when accessed. HttpClient is created ad-hoc for the bootstrap and disposed afterward, avoiding IHttpClientFactory reliance at this early stage. + +## Notes +- If authentication or secrets fetch fails, the exception is swallowed by Load and only writes to Console.Error; secrets may be unavailable until accessed. +- Keys are normalized by replacing __ with :, so ensure Infisical secret names follow this convention (e.g., PROVIDERS__GROK__APIKEY maps to Providers:Grok:ApiKey). +- Load uses a synchronous wait pattern (GetAwaiter().GetResult()) to bridge async startup code; in unusual synchronization contexts this could block, but it is intentional during configuration bootstrap. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md new file mode 100644 index 0000000..5824668 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md @@ -0,0 +1,11 @@ +# InfisicalConfigurationSource + +> **File:** `src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs` +> **Kind:** class + +```csharp +public class InfisicalConfigurationSource : IConfigurationSource +``` + + +Documentation submitted for InfisicalConfigurationSource. If needed, I can adjust the narrative or add notes based on reviewer feedback. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md new file mode 100644 index 0000000..4950d29 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md @@ -0,0 +1,14 @@ +# InfisicalExtensions + +> **File:** `src/api/Gabriel.API/Configuration/InfisicalExtensions.cs` +> **Kind:** class + +```csharp +public static class InfisicalExtensions +``` + + +InfisicalExtensions provides an extension method on IConfigurationBuilder that wires Infisical as a configuration source using the standard Options-pattern. Call AddInfisical with a configure delegate to populate InfisicalOptions, then the extension registers an InfisicalConfigurationSource built from those options and returns the builder for fluent chaining. + +## Remarks +InfisicalExtensions acts as an adapter that wires InfisicalConfigurationSource into the IConfigurationBuilder, enabling Infisical-managed values to be surfaced through IConfiguration during startup. It follows the well-known AddXxx pattern used by ASP.NET Core (for example AddDbContext), making the integration familiar to developers. The InfisicalOptions properties map directly to the connection and scope settings (Host, ProjectId, Environment, SecretPath) and control how the loaded values are exposed in the configuration tree via SectionName. diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md new file mode 100644 index 0000000..dd0fb6a --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md @@ -0,0 +1,54 @@ +# LogDateEnricher.cs + +> **Source:** `src/api/Gabriel.API/Configuration/LogDateEnricher.cs` + +## Contents + +- [LogDateEnricher](#logdateenricher) +- [LogDateEnricherExtensions](#logdateenricherextensions) + +--- + +## LogDateEnricher +> **File:** `src/api/Gabriel.API/Configuration/LogDateEnricher.cs` +> **Kind:** class + +```csharp +public sealed class LogDateEnricher : ILogEventEnricher +``` + + +LogDateEnricher enriches every Serilog log event with a LogDate property, enabling downstream sinks to route logs by date. It derives the date from the event's timestamp in local time, formats it as MM-dd-yyyy using invariant culture, and adds or updates the property so sinks like Serilog.Sinks.Map can dispatch events to date-named files. + +## Remarks +Encapsulating the date-formatting logic in an ILogEventEnricher keeps routing concerns separate from business logic and makes the behavior easy to reuse across different log sources. Using LocalDateTime respects the host's time zone for the reported date, and AddOrUpdateProperty ensures the property can be consistently overwritten by subsequent enrichers without creating duplicates. The pattern is specifically intended for Map-based routing where the property value drives the target file name; if you aren't using Map, the enrichment has no routing effect. + +## Notes +- Time zone caveat: The date is derived from the host's local time; in multi-region deployments you may want to standardize on a single time zone or switch to a UTC-based date to ensure consistent routing. +- Property naming: The property is named 'LogDate'. If other parts of the system rely on a different property name, adjust accordingly or harmonize across enrichers. +- Format mutability: The MM-dd-yyyy pattern is fixed here; changing it affects the downstream Map routing and file-naming conventions. + +--- + +## LogDateEnricherExtensions +> **File:** `src/api/Gabriel.API/Configuration/LogDateEnricher.cs` +> **Kind:** class + +```csharp +public static class LogDateEnricherExtensions +``` + + +Adds a strongly-typed extension method WithLogDate on LoggerEnrichmentConfiguration that wires the LogDateEnricher into Serilog's enrichment pipeline. It lets you register the date-enrichment either in code via Enrich.WithLogDate() or by name in appsettings Serilog.Enrich, without needing to reference the concrete enricher type directly. + +## Remarks + +By hiding the concrete LogDateEnricher behind a named extension, this symbol provides a stable, discoverable entry point for configuration. It participates in Serilog's extension-based enrichment flow and keeps the codebase decoupled from the enricher's implementation. Because it is a thin wrapper over Serilog's Enrich.`With`(), it remains fully compatible with the standard Serilog configuration pipeline. + +## Notes + +- Ensure the extension's namespace is imported wherever you configure Serilog; extension methods require the appropriate using directive to be visible. +- This is a light wrapper around Enrich.`With`(); verify that LogDateEnricher is available at runtime to avoid enrichment failures. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md new file mode 100644 index 0000000..abb4925 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md @@ -0,0 +1,46 @@ +# JwtResponse + +> **File:** `src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs` +> **Kind:** record + +```csharp +public record JwtResponse( + string AccessToken, + DateTimeOffset AccessExpiresAt, + string RefreshToken, + DateTimeOffset RefreshExpiresAt) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `AccessToken` | `string` | — | +| `AccessExpiresAt` | `DateTimeOffset` | — | +| [`RefreshToken`](../../../Gabriel.Core/Identity/RefreshToken.cs.md) | `string` | — | +| `RefreshExpiresAt` | `DateTimeOffset` | — | + + +JwtResponse is the data contract returned by the authentication endpoints /api/auth/jwt and /api/auth/jwt/refresh. It carries a short-lived AccessToken (a signed JWT) and its expiry, plus a RefreshToken (an opaque, high-entropy string) and its expiry. Clients should store the RefreshToken securely and exchange it for a new AccessToken via the refresh endpoint when the access token expires; on every refresh, the server rotates the RefreshToken to reduce the risk of token theft. + +## Remarks +JwtResponse centralizes the token payload needed for continued authentication, separating token lifecycle from business logic. It makes clear that the AccessToken is used for API requests while the RefreshToken is used to obtain new access tokens without re-authenticating. The policy of rotating the refresh token on each refresh mitigates the risk of long-lived tokens being abused if a token is compromised. + +## Example +```csharp +// Typical usage after a successful authentication +JwtResponse response = new JwtResponse( + accessToken: accessToken, + accessExpiresAt: DateTimeOffset.UtcNow.AddMinutes(15), + refreshToken: refreshToken, + refreshExpiresAt: DateTimeOffset.UtcNow.AddDays(7) +); + +// Optional deconstruction for concise access +var (token, expires, rt, rtExpires) = response; +``` + +## Notes +- Treat the RefreshToken as highly sensitive; store it securely and avoid logging it or exposing it in analytics or error messages. +- Always use HTTPS for token transmission; the refresh flow rotates the refresh token to minimize the impact of potential leakage. +- The AccessToken is short-lived; decode it only to read claims for UI hints, but do not rely on client-side claims for authorization—server-side validation remains authoritative. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md new file mode 100644 index 0000000..a7ad7cd --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md @@ -0,0 +1,27 @@ +# LoginRequest + +> **File:** `src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs` +> **Kind:** record + +```csharp +public record LoginRequest(string Email, string Password) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Email` | `string` | — | +| `Password` | `string` | — | + + +LoginRequest is an immutable data transfer object used to carry the credentials required for authentication. It encapsulates Email and Password into a single payload that can be sent to an authentication endpoint or service; using a record provides value-based equality and concise construction with the two required fields. + +## Remarks + +LoginRequest acts as the canonical payload for login operations. By representing credentials as a value object, it enables straightforward comparison and reliable transport of the Email/Password pair across service boundaries. As with any credential data, avoid logging the Password or persisting it in plaintext; ensure transmission occurs over a secure channel and consider validating inputs at the boundary where the object is created. + +## Notes + +- Do not log or display the Password; treat it as sensitive data. +- If you need validation, consider a custom constructor or a separate validator to enforce non-empty Email and Password before usage. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md new file mode 100644 index 0000000..e6f8fa0 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md @@ -0,0 +1,25 @@ +# MeResponse + +> **File:** `src/api/Gabriel.API/Contracts/Auth/MeResponse.cs` +> **Kind:** record + +```csharp +public record MeResponse(Guid Id, string Email) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `Guid` | — | +| `Email` | `string` | — | + + +MeResponse is a tiny, immutable payload used as the API contract for the authenticated user’s identity. It carries the user’s Id (Guid) and Email (string) as a simple, stable response object; as a positional-record, it benefits from value-based equality, concise construction, and deconstruction for convenient use in responses and tests. + +## Remarks +MeResponse exists to decouple the external API surface from the internal user model. By exposing only Id and Email, it preserves a minimal yet useful identity contract for clients and enables evolution of the domain model without breaking API consumers. Its immutability and built-in equality help avoid accidental mutation and simplify comparisons in tests and pipelines. + +## Notes +- ToString prints both Id and Email; be careful with verbose logs or telemetry. Redact Email if logging MeResponse in production. +- MeResponse is intended as a stable cross-boundary contract; add additional fields only in a separate response type to avoid breaking changes. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md new file mode 100644 index 0000000..04166b6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md @@ -0,0 +1,28 @@ +# RefreshTokenRequest + +> **File:** `src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs` +> **Kind:** record + +```csharp +public record RefreshTokenRequest(string RefreshToken) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| [`RefreshToken`](../../../Gabriel.Core/Identity/RefreshToken.cs.md) | `string` | — | + + +Represents a request payload for refreshing an authentication token. This record is a lightweight, immutable value object that carries a single string property (RefreshToken) used by the authorization flow to obtain a new access token from the server. Because it is a C# record with a primary constructor, it benefits from value-based equality and concise construction, and it can be serialized as a JSON payload when calling the refresh endpoint. + +## Remarks +Acts as a transport contract between the client and the authentication service, encapsulating the refresh token rather than passing a raw value through layers. The record-style shape gives you a strongly-typed, easily testable artifact with built-in value equality, making it straightforward to compare requests in tests or cache lookups. If future API evolution adds additional fields (e.g., device info or token metadata), they can be added here without changing the consumer's call sites. + +## Example +```csharp +var request = new RefreshTokenRequest("sample_refresh_token"); +``` + +## Notes +- Treat the RefreshToken as sensitive data; avoid logging, caching, or exposing it in client telemetry or logs. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs.md new file mode 100644 index 0000000..f8c094d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs.md @@ -0,0 +1,29 @@ +# RegisterRequest + +> **File:** `src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs` +> **Kind:** record + +```csharp +public record RegisterRequest(string Email, string Password) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Email` | `string` | — | +| `Password` | `string` | — | + + +Represents the input for a user registration operation. This immutable record serves as the API contract that carries the user's Email and Password from the client to the server, typically deserialized from JSON in HTTP requests and passed to authentication logic. + +## Remarks +Using a record with positional parameters enforces that Email and Password are provided at construction time, giving a clear, strongly-typed surface for registration data. As a public record, it benefits from value-based equality and concise construction, making it convenient to compare, test, and compose in pipelines. It is intended as a transport object; validation (format, password strength) should occur in higher layers before processing. + +## Example +```csharp +var request = new RegisterRequest("alice@example.com", "P@ssw0rd!"); +``` + +## Notes +- The default ToString on a record includes the Password value; avoid logging or displaying it. Redact or override ToString as needed; treat this object as sensitive until validated. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md new file mode 100644 index 0000000..dc9536d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md @@ -0,0 +1,47 @@ +# ContextMetricsResponse + +> **File:** `src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs` +> **Kind:** record + +```csharp +public record ContextMetricsResponse( + int CurrentTokens, + int ContextWindowTokens, + int CompactThresholdTokens, + double CompactThresholdRatio, + int MessagesAfterCut, + bool IsSummarized, + int SystemPromptTokens, + int ProjectPromptTokens, + int MemoryTokens, + int SummaryTokens, + int ToolsTokens, + int ConversationTokens) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `CurrentTokens` | `int` | — | +| `ContextWindowTokens` | `int` | — | +| `CompactThresholdTokens` | `int` | — | +| `CompactThresholdRatio` | `double` | — | +| `MessagesAfterCut` | `int` | — | +| `IsSummarized` | `bool` | — | +| `SystemPromptTokens` | `int` | — | +| `ProjectPromptTokens` | `int` | — | +| `MemoryTokens` | `int` | — | +| `SummaryTokens` | `int` | — | +| `ToolsTokens` | `int` | — | +| `ConversationTokens` | `int` | — | + + +ContextMetricsResponse is a wire-friendly record that carries the engine's contextual metrics for a conversation without exposing engine-layer types. It aggregates the current token counts, the contextual window size, the threshold for triggering compaction, and a per-category breakdown that powers UI indicators and provider history. + +## Remarks +This symbol exists to decouple internal ContextMetrics from the public API, enabling stable contracts across internal refactors while still conveying detailed usage statistics to the client. It exposes both global metrics (CurrentTokens, ContextWindowTokens, CompactThresholdTokens, CompactThresholdRatio, MessagesAfterCut, IsSummarized) and a per-category breakdown (SystemPromptTokens, ProjectPromptTokens, MemoryTokens, SummaryTokens, ToolsTokens, ConversationTokens) so consumers can render progress, costs, and summarization state consistently. + +## Notes +- CurrentTokens should equal the sum of the per-category token fields (SystemPromptTokens + ProjectPromptTokens + MemoryTokens + SummaryTokens + ToolsTokens + ConversationTokens). If these diverge, the data is inconsistent and should be treated as an error at serialization time. +- CompactThresholdRatio is a double representing a fraction of the ContextWindowTokens (e.g., 0.8 for 80%). When displaying UI, consider formatting to avoid precision artifacts and ensure consistent progress visuals. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md new file mode 100644 index 0000000..8e4007f --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md @@ -0,0 +1,51 @@ +# ConversationResponse + +> **File:** `src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs` +> **Kind:** record + +```csharp +public record ConversationResponse( + Guid Id, + Guid? ProjectId, + string Title, + long AvatarSeed, + DateTimeOffset CreatedAt, + DateTimeOffset UpdatedAt, + IReadOnlyList? Messages, + bool? ProjectIsDefault = null, + long? EffectiveAvatarSeed = null, + string? PatternOverride = null, + string? PaletteOverride = null, + string? Mode = null +) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `Guid` | — | +| `ProjectId` | `Guid?` | — | +| `Title` | `string` | — | +| `AvatarSeed` | `long` | — | +| `CreatedAt` | `DateTimeOffset` | — | +| `UpdatedAt` | `DateTimeOffset` | — | +| `Messages` | `IReadOnlyList?` | — | +| `ProjectIsDefault` | `bool?` | `null` | +| `EffectiveAvatarSeed` | `long?` | `null` | +| `PatternOverride` | `string?` | `null` | +| `PaletteOverride` | `string?` | `null` | +| `Mode` | `string?` | `null` | + + +ConversationResponse is a transportable data record that encapsulates the metadata and optional message payload for a single conversation as returned by the Gabriel API. It carries the conversation’s identity (Id, Title), its project linkage (ProjectId, EffectiveAvatarSeed), theming and behavior hints (PatternOverride, PaletteOverride, Mode), avatar/seed data (AvatarSeed, CreatedAt, UpdatedAt), and the per-conversation Messages when fetching a single conversation. In list responses, Messages is null; in detail views it is populated, enabling the client to render a complete conversation thread while keeping list responses lightweight. The nullable fields ProjectIsDefault and the backfilled seeds/overrides support legacy data and project migration workflows, while Mode’s value (lowercase enum name) and defaulting behavior guide how the chat is presented by the UI. + +## Remarks +This type centralizes conversation-scoped state and presentation hints in one place, enabling a consistent client rendering path across list and detail endpoints. By tying avatar skin and avatar seed to the project when applicable, it supports cohesive theming for project chats while preserving standalone chats' personalization. The nullables reflect legacy/backfill strategies and future migrations, ensuring backward compatibility without breaking the API surface. + +## Notes +- Messages is populated only when fetching a single conversation; in list responses it is null. +- ProjectIsDefault is meaningful only when ProjectId is non-null; null indicates legacy rows (pre-Phase-8 backfill). +- EffectiveAvatarSeed is null when ProjectId is null; in non-null projects it equals the parent project’s AvatarSeed, otherwise it uses the conversation’s own AvatarSeed. +- PatternOverride and PaletteOverride are echoed for backward-compatibility with conversion flows; real-project chats render the project’s skin, but these fields allow future migration to carry forward styling. +- Mode is a lowercased enum name; null means the default mode ("chatty"). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs.md new file mode 100644 index 0000000..27e19f1 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs.md @@ -0,0 +1,37 @@ +# CreateConversationRequest + +> **File:** `src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs` +> **Kind:** record + +```csharp +public record CreateConversationRequest(string? Title, Guid? ProjectId) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Title` | `string?` | — | +| `ProjectId` | `Guid?` | — | + + +Represents the payload sent to create a new conversation via the Gabriel API. It exposes two optional fields: Title and ProjectId. Use this when issuing a create-conversation request; provide a Title to set a human-friendly label, and optionally assign the conversation to a specific project via ProjectId. If ProjectId is omitted, the backend will place the new conversation in the user's Default project (auto-created if needed). Because it is a C# record, it benefits from value-based equality and concise initialization, and it is ideal for transport across API boundaries. + +## Remarks +This abstraction cleanly captures the contract of the API for creation, decoupling client code from backend persistence decisions. The optional fields reflect the fact that callers may prefer defaults or minimal payloads. As a record, it supports immutable semantics and easy equality checks, which help in testing and change detection, while keeping the payload lightweight for transport. + +## Example +```csharp +// Title only +var req1 = new CreateConversationRequest(Title: "Design Review", ProjectId: null); + +// ProjectId only +var req2 = new CreateConversationRequest(Title: null, ProjectId: Guid.Parse("11111111-1111-1111-1111-111111111111")); + +// Both title and project +var req3 = new CreateConversationRequest(Title: "Sprint Planning", ProjectId: Guid.Parse("22222222-2222-2222-2222-222222222222")); +``` + +## Notes +- Nullability: Both Title and ProjectId are nullable, allowing callers to omit either field as needed. +- Immutability: Being a record, CreateConversationRequest is immutable; use a `with` expression to derive variations if needed. diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs.md new file mode 100644 index 0000000..0d41a68 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs.md @@ -0,0 +1,25 @@ +# SetConversationModeRequest + +> **File:** `src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs` +> **Kind:** record + +```csharp +public sealed record SetConversationModeRequest(string? Mode) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Mode` | `string?` | — | + + +This record serves as the request body for updating a conversation's mode via the API. It carries a single optional Mode value; the value must be the lowercase name of one of the supported modes (chatty, elaborative, concise, tutor, critic) or null to reset back to the default (treated as chatty at read time). This payload is sent with a PUT to /api/conversations/{id}/mode to apply the requested mode. + +## Remarks +Using a dedicated SetConversationModeRequest type provides a clear boundary for this API operation, decoupling the transport payload from any internal domain enum. The Mode property being a string allows for a forgiving boundary where null signals a reset, while the runtime can validate values against the known set. This design makes evolving the API easier: new modes or additional options can be added to the contract without changing existing signatures. + +## Notes +- No validation is performed within this symbol; ensure Mode is either a valid lowercase name or null before sending. +- Null resets to default; ensure server semantics align with read-time default. +- The documentation comment in the source explains the accepted values; actual enforcement occurs at the API boundary. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs.md new file mode 100644 index 0000000..ffffe05 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs.md @@ -0,0 +1,25 @@ +# UpdateConversationRequest + +> **File:** `src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs` +> **Kind:** record + +```csharp +public record UpdateConversationRequest(string Title) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Title` | `string` | — | + + +Represents the payload used to update a conversation's title. This C# record is an immutable, value-based carrier that encapsulates the new Title as its sole property. Use UpdateConversationRequest when calling the update-conversation API to express the intention to change a conversation's title, rather than passing a raw string or multiple fields. + +## Remarks +Records in C# provide value-based equality and built-in deconstruction, making UpdateConversationRequest a predictable and easy-to-use data contract at API boundaries. Because it is defined as a positional record, Title is populated via the primary constructor and exposed as a read-only property, ensuring the payload remains consistent once created. The type is simple to extend with additional fields in the future if the API grows, while preserving a stable, forward-compatible shape for existing clients. + +## Example +```csharp +var request = new UpdateConversationRequest("Project Kickoff"); +``` diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md new file mode 100644 index 0000000..2a6032e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md @@ -0,0 +1,71 @@ +# MetricEntryDto.cs + +> **Source:** `src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs` + +## Contents + +- [MetricEntriesResponse](#metricentriesresponse) +- [MetricEntryDto](#metricentrydto) + +--- + +## MetricEntriesResponse +> **File:** `src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs` +> **Kind:** record + +```csharp +public sealed record MetricEntriesResponse( + IReadOnlyList Entries, + int Count) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Entries` | `IReadOnlyList` | — | +| `Count` | `int` | — | + + +Represents the response payload for the diagnostics metrics endpoint. It carries two values: Entries, a read-only list of MetricEntryDto objects containing the most recent metric entries that match the requested system (exact match or system prefix), ordered newest first; and Count, the number of entries included in this response. + +## Remarks +By expressing the response as a dedicated record, this symbol decouples transport concerns from the domain model and provides a stable wire format for clients and tests. The paired Entries and Count support paging: clients can render a page of results while knowing how many items were actually returned. The structure is future-proof for evolving the payload without touching the endpoint contract, since additional metadata can be added to this record later without breaking existing consumers. + +## Notes +- The exact meaning of Count (whether it's the number of returned entries or the total matches) is not explicit in the snippet; consult the API contract to confirm semantics. + +--- + +## MetricEntryDto +> **File:** `src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs` +> **Kind:** record + +```csharp +public sealed record MetricEntryDto( + Guid Id, + string System, + JsonElement Metric, + DateTimeOffset CreatedAt) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `Guid` | — | +| `System` | `string` | — | +| `Metric` | `JsonElement` | — | +| `CreatedAt` | `DateTimeOffset` | — | + + +MetricEntryDto is a lightweight, immutable data transfer object that represents one row in the generic metric event log. The Metric field holds the raw JSON payload as a JsonElement, preserving the original shape and letting callers deserialize it later according to the schema agreed by the originating subsystem. The Id uniquely identifies the entry, System denotes the source subsystem, and CreatedAt records when the entry was created. + +## Remarks +MetricEntryDto exists to boundary-cross the API surface from varying metric schemas. By using a sealed record, it gains immutable, value-based equality semantics suitable for transport across boundaries. Storing the payload as JsonElement provides flexibility for callers to interpret the metric on their own terms while avoiding premature deserialization. If you need a typed view, deserialize the JsonElement at the call site. + +## Notes +- Treat the Metric payload as opaque; do not mutate it, and deserialize only when you know the target shape (e.g., via GetRawText() + JsonSerializer). +- The property named System may collide with common namespaces in consuming code; prefer explicit references to the DTO when accessing it. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md new file mode 100644 index 0000000..1b191ae --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md @@ -0,0 +1,101 @@ +# WebSearchDiagnosticsResponse.cs + +> **Source:** `src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs` + +## Contents + +- [WebSearchDiagnosticsResponse](#websearchdiagnosticsresponse) +- [WebSearchProviderStatsDto](#websearchproviderstatsdto) + +--- + +## WebSearchDiagnosticsResponse +> **File:** `src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs` +> **Kind:** record + +```csharp +public sealed record WebSearchDiagnosticsResponse( + IReadOnlyList Providers, + bool HasUnhealthyProvider, + int WindowSize) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Providers` | `IReadOnlyList` | — | +| `HasUnhealthyProvider` | `bool` | — | +| `WindowSize` | `int` | — | + + +Represents the aggregated diagnostics for the web-search feature over a defined lookback window. The Providers collection contains one entry per provider that logged events during that window, exposing provider-level statistics used by the UI to render trends and health cues. The HasUnhealthyProvider flag signals whether any provider's most recent outcome was a failure or there were no successful calls in the window, enabling a prominent warning indicator. WindowSize records the span of recent events the stats were computed over, so UI messages can say the results are based on the last 200 search calls. + +## Remarks +WebSearchDiagnosticsResponse serves as a boundary object between the diagnostics API and the UI, encapsulating both granular provider stats and a high-level health signal. By aggregating per-provider data and a global unhealthy indicator, it supports both detailed rendering and quick assessments of overall health. The inclusion of WindowSize ensures users understand the timeframe of the metrics and helps prevent misinterpretation of stale data. This abstraction can accommodate additional providers or metrics without forcing UI changes. + +--- + +## WebSearchProviderStatsDto +> **File:** `src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs` +> **Kind:** record + +```csharp +public sealed record WebSearchProviderStatsDto( + string Provider, + long TotalCalls, + long SuccessfulCalls, + long ErrorCalls, + long EmptyCalls, + DateTimeOffset? LastSuccessAt, + DateTimeOffset? LastFailureAt, + string? LastFailureQuery, + string? LastFailureMessage, + double AvgLatencyMs, + bool IsUnhealthy) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Provider` | `string` | — | +| `TotalCalls` | `long` | — | +| `SuccessfulCalls` | `long` | — | +| `ErrorCalls` | `long` | — | +| `EmptyCalls` | `long` | — | +| `LastSuccessAt` | `DateTimeOffset?` | — | +| `LastFailureAt` | `DateTimeOffset?` | — | +| `LastFailureQuery` | `string?` | — | +| `LastFailureMessage` | `string?` | — | +| `AvgLatencyMs` | `double` | — | +| `IsUnhealthy` | `bool` | — | + + +WebSearchProviderStatsDto is an immutable data transfer object that aggregates per-provider web search telemetry. It captures counts (TotalCalls, SuccessfulCalls, ErrorCalls, EmptyCalls), timing information (LastSuccessAt, LastFailureAt), context for failures (LastFailureQuery, LastFailureMessage), a latency metric (AvgLatencyMs) and a health flag (IsUnhealthy) so diagnostics consumers can present a concise picture of a provider's runtime behavior. + +Developers use this DTO when exporting or returning per-provider statistics from diagnostics endpoints or monitoring dashboards. It avoids scattering raw telemetry across several types and provides a stable contract for clients consuming provider health data. + +## Remarks +WebSearchProviderStatsDto centralizes provider-level statistics behind a stable API surface, enabling clients to query provider health and performance without digging into granular telemetry. +Its IsUnhealthy flag encodes a simple health policy: if the provider has recorded events in the monitoring window and either has zero successful calls or the most recent event was a failure, the provider is flagged as unhealthy. +It uses a record type to provide immutable, value-based equality, making it a natural fit for API contracts and caching scenarios. + +## Example +```csharp +var stats = new WebSearchProviderStatsDto( + Provider: "tavily", + TotalCalls: 150, + SuccessfulCalls: 140, + ErrorCalls: 5, + EmptyCalls: 5, + LastSuccessAt: DateTimeOffset.UtcNow.AddMinutes(-5), + LastFailureAt: DateTimeOffset.UtcNow.AddMinutes(-1), + LastFailureQuery: "weather forecast", + LastFailureMessage: "timeout", + AvgLatencyMs: 128.3, + IsUnhealthy: false +); +``` + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md new file mode 100644 index 0000000..4122982 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md @@ -0,0 +1,98 @@ +# MemoryDto.cs + +> **Source:** `src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs` + +## Contents + +- [MemoryDto](#memorydto) +- [SaveMemoryRequest](#savememoryrequest) + +--- + +## MemoryDto +> **File:** `src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs` +> **Kind:** record + +```csharp +public sealed record MemoryDto( + Guid Id, + Guid? ProjectId, + string Type, + string Name, + string Description, + string Body, + DateTimeOffset CreatedAt, + DateTimeOffset UpdatedAt) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `Guid` | — | +| `ProjectId` | `Guid?` | — | +| `Type` | `string` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `Description` | `string` | — | +| `Body` | `string` | — | +| `CreatedAt` | `DateTimeOffset` | — | +| `UpdatedAt` | `DateTimeOffset` | — | + + +MemoryDto is the wire representation of a single MemoryEntry used for transferring memory data across API boundaries. It mirrors the MemoryEntry properties that are relevant to clients but remains a plain data container with no domain behavior. As a sealed record, it is immutable and supports value-based equality, which makes it convenient to compare and propagate memory items across layers. The Id identifies the memory; ProjectId, if set, ties the memory to a particular project (and constrains visibility to that project), while a null ProjectId indicates a user-scoped memory. The remaining fields (Type, Name, Description, Body) describe the memory, and CreatedAt / UpdatedAt provide lightweight auditing information. + +## Remarks +MemoryDto serves as the API contract for Memory data, decoupled from the domain entity MemoryEntry. This separation enables the API surface to evolve independently from domain rules while still carrying the same essential data. Because MemoryDto is immutable, you create new instances to reflect changes rather than mutating existing ones; use mapping from MemoryEntry when persisting or presenting data. + +## Notes +- Null ProjectId indicates user-scoped memory; clients should handle absence gracefully. +- MemoryDto is a data-transfer object with no behavior; avoid relying on mutations to trigger logic; for updates, construct a new DTO or map to domain and back. + +--- + +## SaveMemoryRequest +> **File:** `src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs` +> **Kind:** record + +```csharp +public sealed record SaveMemoryRequest( + Guid? ProjectId, + string Type, + string Name, + string Description, + string Body) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `ProjectId` | `Guid?` | — | +| `Type` | `string` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `Description` | `string` | — | +| `Body` | `string` | — | + + +SaveMemoryRequest is the API payload used to persist (or upsert) a memory entry via POST /api/memories. It carries an optional ProjectId to scope the memory, a Type that categorizes the memory, and the required Name, Description, and Body. The operation is idempotent and keyed on the composite (UserId, ProjectId, Name); sending the same values again will upsert the existing memory rather than creating a duplicate. + +## Remarks +This symbol acts as a plain data carrier at the API boundary, isolating transport concerns from domain logic. The UserId is implied from the authenticated context, while ProjectId scopes the memory when provided. Modeling as a record emphasizes its role as data used to perform an idempotent upsert, enabling straightforward value-based comparisons in tests. + +## Example +```csharp +var request = new SaveMemoryRequest( + ProjectId: Guid.Empty, + Type: "reference", + Name: "LaunchChecklist", + Description: "Checklist used during the launch phase", + Body: "1) Confirm readiness; 2) Verify systems; 3) Approve to proceed..." +); +``` + +## Notes +- Validate that Type is one of the allowed values (user, feedback, project, reference) before sending; the server should enforce this, per the contract. +- ProjectId is nullable; omit it to store a user-scoped memory or provide a project GUID to scope to a project. The effective identity for the upsert also depends on the authenticated UserId. +- SaveMemoryRequest is a sealed record, so instances are immutable. To adjust any field, construct a new instance rather than mutating an existing one. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md new file mode 100644 index 0000000..4511d1b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md @@ -0,0 +1,89 @@ +# MessageResponse.cs + +> **Source:** `src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs` + +## Contents + +- [MessageResponse](#messageresponse) +- [MessageToolCall](#messagetoolcall) + +--- + +## MessageResponse +> **File:** `src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs` +> **Kind:** record + +```csharp +public record MessageResponse( + Guid Id, + string Role, + string? Content, + DateTimeOffset CreatedAt, + Guid VariantGroupId, + int VariantIndex, + int VariantCount, + IReadOnlyList VariantSiblingIds, + string? ToolCallId = null, + IReadOnlyList? ToolCalls = null, + string? ReasoningContent = null +) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `Guid` | — | +| `Role` | `string` | — | +| `Content` | `string?` | — | +| `CreatedAt` | `DateTimeOffset` | — | +| `VariantGroupId` | `Guid` | — | +| `VariantIndex` | `int` | — | +| `VariantCount` | `int` | — | +| `VariantSiblingIds` | `IReadOnlyList` | — | +| `ToolCallId` | `string?` | `null` | +| `ToolCalls` | `IReadOnlyList?` | `null` | +| `ReasoningContent` | `string?` | `null` | + + +MessageResponse is an immutable data record that represents a single message within a conversation, capturing its role, content, timing, and its position within a regeneration variant set. It bundles identity (Id), role (e.g., user, assistant, system, tool), content (nullable to accommodate tool-only messages), timestamp (CreatedAt), and the variant-structure metadata (VariantGroupId, VariantIndex, VariantCount, VariantSiblingIds). When a message involves tool invocation, ToolCallId anchors the specific invocation and ToolCalls carries the detailed calls; ReasoningContent is an optional, provider-specific stream of the model's reasoning. + +## Remarks +MessageResponse exists to model rich conversational turns where multiple regenerations are possible and where tool usage and reasoning traces may be exposed. By combining the message identity with its variant metadata, downstream components can deterministically re-present, compare, or re-run alternative responses without reconstructing provenance from loose fields. The inclusion of ToolCalls and ReasoningContent supports transparent tool integration and, where supported, reveal the model's reasoning path in a controlled manner. + +## Notes +- Content may be null for messages that only involve tool invocations (e.g., an assistant turn that issues tool calls without immediate textual output). +- VariantGroupId is shared across all regen siblings and equals Id when VariantCount is 1 (singleton). +- ToolCalls is non-null only for assistant messages that requested tool usage, and ToolCallId is set on messages with a tool invocation; these two fields should be consistent to reflect the linked tooling activity. + +--- + +## MessageToolCall +> **File:** `src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs` +> **Kind:** record + +```csharp +public record MessageToolCall(string Id, string Name, string ArgumentsJson) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `string` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `ArgumentsJson` | `string` | — | + + +MessageToolCall is a minimal data carrier (C# record) used to represent a tool invocation within messaging workflows. It carries a unique identifier (Id), the tool being invoked (Name), and a JSON-encoded payload of arguments (ArgumentsJson). This structure is ideal for passing tool-call metadata across API boundaries or persisting it alongside a message without coupling to the tool's execution logic. + +## Remarks + +As a record, MessageToolCall provides value through immutability, value-based equality, and simple deconstruction for pattern matching. It decouples the notion of 'which tool' and 'how to execute it' from the surrounding message payload, enabling components to forward or store tool calls without pulling in tool-specific behavior. This abstraction fits into a broader messaging contract by standardizing how tool invocations are represented. + +## Notes + +- ArgumentsJson must be a valid JSON string that conforms to the expected schema for the target tool; mismatches can cause runtime parsing errors. +- The record is immutable; to create a modified version, use the with-expression (e.g., var c2 = c1 with { Name = "NewName" }). + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs.md new file mode 100644 index 0000000..6957558 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs.md @@ -0,0 +1,24 @@ +# SendMessageRequest + +> **File:** `src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs` +> **Kind:** record + +```csharp +public record SendMessageRequest(string Content) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Content` | `string` | — | + + +SendMessageRequest is an immutable data contract used to convey the content of a message to be sent via the messaging API. As a C# record with a single positional parameter Content, it provides a concise, value-based payload that can be serialized to JSON and transmitted to the endpoint. Use this type whenever you need a strongly-typed request body for sending a message, rather than passing a raw string. + +## Remarks +Because it is a single-property record, it benefits from value-based equality by Content, simple construction, and deconstruction when you need to access the content. The Content property name aligns with common API payload conventions, ensuring predictable serialization across serializers that map property names directly to JSON fields. + +## Notes +- Content is non-nullable in this signature. Callers must supply a non-null string. If the API allows empty or optional content, consider using string? Content or validating before construction. +- As a record, instances are immutable; after creation, Content cannot be changed, which helps preserve message integrity across layers. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md new file mode 100644 index 0000000..75bd2f1 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md @@ -0,0 +1,167 @@ +# ModelDto.cs + +> **Source:** `src/api/Gabriel.API/Contracts/Models/ModelDto.cs` + +## Contents + +- [ModelDto](#modeldto) +- [ModelsResponse](#modelsresponse) +- [SelectedModelDto](#selectedmodeldto) +- [SetActiveModelRequest](#setactivemodelrequest) + +--- + +## ModelDto +> **File:** `src/api/Gabriel.API/Contracts/Models/ModelDto.cs` +> **Kind:** record + +```csharp +public sealed record ModelDto( + string Provider, + string Name, + int ContextWindowTokens, + double? CompactThreshold, + decimal InputPricePerMTokens, + decimal OutputPricePerMTokens, + decimal CacheReadPricePerMTokens, + decimal CacheWritePricePerMTokens, + bool IsDefault, + bool IsSelected) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Provider` | `string` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `ContextWindowTokens` | `int` | — | +| `CompactThreshold` | `double?` | — | +| `InputPricePerMTokens` | `decimal` | — | +| `OutputPricePerMTokens` | `decimal` | — | +| `CacheReadPricePerMTokens` | `decimal` | — | +| `CacheWritePricePerMTokens` | `decimal` | — | +| `IsDefault` | `bool` | — | +| `IsSelected` | `bool` | — | + + +Represents a single model entry returned by the /api/models endpoint. It wires together the model’s identity (Provider, Name), its token-context window, per-model pricing (input, output, and cache operations), and flags marking default or selected status. This immutable DTO is used by API clients to render a catalog of available models and to estimate costs for token workloads, while allowing per-model overrides of global settings such as the CompactThreshold. If CompactThreshold is null, the global AgentOptions.CompactThreshold is used, enabling a per-model override alongside a shared default. + +## Remarks +ModelDto exists to separate the concerns of data transport and pricing logic. It captures pricing and metadata in a single, serializable record, simplifying UI and service boundaries. The CompactThreshold being nullable encodes the rule "use model-specific threshold when provided, otherwise fall back to the global setting defined in AgentOptions." This design supports tiered pricing and gradual rollouts without duplicating global configuration across all models. + +## Example +```csharp +var example = new ModelDto( + Provider: "OpenAI", + Name: "gpt-4o", + ContextWindowTokens: 8192, + CompactThreshold: 0.18, + InputPricePerMTokens: 0.06m, + OutputPricePerMTokens: 0.12m, + CacheReadPricePerMTokens: 0.0m, + CacheWritePricePerMTokens: 0.0m, + IsDefault: true, + IsSelected: false +); +``` + +## Notes +- If CompactThreshold is null, the global threshold from AgentOptions applies; callers should be prepared to resolve the effective value at runtime. +- Cache pricing fields may be zero for providers that do not expose caching pricing; treat zero as no cache pricing rather than a negative or special-case value. + +--- + +## ModelsResponse +> **File:** `src/api/Gabriel.API/Contracts/Models/ModelDto.cs` +> **Kind:** record + +```csharp +public sealed record ModelsResponse( + IReadOnlyList AvailableModels, + SelectedModelDto Selected) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `AvailableModels` | `IReadOnlyList` | — | +| `Selected` | `SelectedModelDto` | — | + + +This record models the response payload for the GET /api/models endpoint. It carries two related pieces of state: AvailableModels, the catalog of ModelDto entries exposed by the system, and Selected, the model currently in use for the user (falling back to the configured default if the user hasn't explicitly chosen one). Consumers rely on this payload to render model-selection UI and to determine the active model for the current session. + +## Remarks +This abstraction groups catalog data and the user's choice into a single, immutable payload, reducing round trips and aligning the client view with the server's current state. By representing the payload as a record, you gain value semantics, straightforward equality, and safe, serializable data transfer. The dependency on ModelDto ties the shape of the available options to a shared contract across the API surface, ensuring consistent presentation and validation. + +## Notes +- Because AvailableModels and Selected are non-nullable, API responses must include both properties; missing fields can cause deserialization issues in clients using non-nullable reference types. + +--- + +## SelectedModelDto +> **File:** `src/api/Gabriel.API/Contracts/Models/ModelDto.cs` +> **Kind:** record + +```csharp +public sealed record SelectedModelDto(string Provider, string Name) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Provider` | `string` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | + + +SelectedModelDto represents a user’s selected model by pairing its Provider with the model name. It is an immutable, value-based data carrier used to transport the chosen model through layers (for example from UI input to application services) without scattering two separate strings. + +## Remarks +SelectedModelDto leverages a record to provide value-based equality and immutability for a small data carrier. The sealed modifier signals that this is a simple DTO with no inheritance concerns, and the positional constructor makes Provider and Name required at creation. Its shape makes deconstruction and with-expressions straightforward, enabling clean copies when only one field needs change. + +## Example +```csharp +var selection = new SelectedModelDto("OpenAI", "GPT-4"); +var revised = selection with { Name = "GPT-4 Turbo" }; +``` + +## Notes +- Nullability: If your project enables nullable reference types, Provider and Name are non-nullable; ensure you supply non-null values or adjust types accordingly. +- This is a pure data carrier intended for transport; avoid placing business logic or validation inside the type itself and perform such concerns at boundary or service layers. + +--- + +## SetActiveModelRequest +> **File:** `src/api/Gabriel.API/Contracts/Models/ModelDto.cs` +> **Kind:** record + +```csharp +public sealed record SetActiveModelRequest(string? Provider, string? Name) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Provider` | `string?` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string?` | — | + + +Represents the payload used to specify the active model for the API. It carries two optional fields: Provider and Name. When you PUT to /api/models/active, you can supply these fields to select a model; if both are null, the API clears the explicit preference and falls back to the configured default. + +## Remarks +This small, immutable data transfer object encapsulates the concept of selecting an active model from a provider-name pair or clearing the choice entirely. Using a value-based record makes API calls self-describing and straightforward to reason about in tests and client code; it also aligns with other DTOs in the API surface by expressing intent through a single, well-scoped type. + +## Example +```csharp +// Clear the active model preference (fallback to config default) +var clear = new SetActiveModelRequest(null, null); + +// Set an explicit active model +var setActive = new SetActiveModelRequest("OpenAI", "GPT-4"); +``` + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md new file mode 100644 index 0000000..48051d2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md @@ -0,0 +1,224 @@ +# ProjectResponse.cs + +> **Source:** `src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs` + +## Contents + +- [CreateProjectRequest](#createprojectrequest) +- [ProjectFileResponse](#projectfileresponse) +- [ProjectResponse](#projectresponse) +- [SetSkinRequest](#setskinrequest) +- [UpdateProjectRequest](#updateprojectrequest) + +--- + +## CreateProjectRequest +> **File:** `src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs` +> **Kind:** record + +```csharp +public record CreateProjectRequest(string Name, string? Description, string? SystemPrompt) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `Description` | `string?` | — | +| `SystemPrompt` | `string?` | — | + + +CreateProjectRequest is a value object that represents the payload used to create a new project through the Gabriel API. It exposes a required Name and two optional fields, Description and SystemPrompt, so API clients can supply descriptive metadata and an initial AI system prompt when initializing a project. + +## Remarks +By modeling the incoming payload as a record, this symbol provides immutable, value-based equality and a clear contract across boundaries (client to API). The Name field is required by the API, while Description and SystemPrompt are optional to support concise or richer initial context; using this type instead of ad-hoc dictionaries reduces runtime errors and improves IDE support with IntelliSense. It acts as a thin data-transfer boundary that keeps the creation workflow typed and discoverable. + +## Notes +- Name is non-nullable in the signature, but the code does not enforce non-empty strings; callers should ensure a meaningful name before sending. +- Description and SystemPrompt are nullable; their presence or absence may affect API behavior—passing null vs omitting the field can be treated differently by the server. + +--- + +## ProjectFileResponse +> **File:** `src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs` +> **Kind:** record + +```csharp +public record ProjectFileResponse( + Guid Id, + string Name, + long SizeBytes, + string ContentType, + DateTimeOffset UploadedAt) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `Guid` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `SizeBytes` | `long` | — | +| `ContentType` | `string` | — | +| `UploadedAt` | `DateTimeOffset` | — | + + +Represents metadata for a project file as returned by the API. It carries the file's unique identifier, display name, size in bytes, MIME type, and the timestamp when it was uploaded; use this record when serializing file metadata in project responses. + +## Remarks + +ProjectFileResponse is an immutable, value-based data carrier (a positional record). This makes it a reliable API contract: equal instances compare by content and instances do not mutate after creation, which simplifies caching and reasoning about data across layers. When evolving this contract, prefer additive changes to preserve compatibility with existing clients. + +## Notes + +- Id is a Guid and uniquely identifies the file; pass it through API boundaries so clients can reference or fetch the specific file. +- ContentType is a non-nullable string; ensure it is always populated. If the server cannot determine a MIME type, a safe default such as 'application/octet-stream' helps prevent downstream errors during serialization or client rendering. + +--- + +## ProjectResponse +> **File:** `src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs` +> **Kind:** record + +```csharp +public record ProjectResponse( + Guid Id, + string Name, + string? Description, + string? SystemPrompt, + long AvatarSeed, + bool IsDefault, + string? PatternOverride, + string? PaletteOverride, + DateTimeOffset CreatedAt, + DateTimeOffset UpdatedAt, + IReadOnlyList? Files) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `Guid` | — | +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `Description` | `string?` | — | +| `SystemPrompt` | `string?` | — | +| `AvatarSeed` | `long` | — | +| `IsDefault` | `bool` | — | +| `PatternOverride` | `string?` | — | +| `PaletteOverride` | `string?` | — | +| `CreatedAt` | `DateTimeOffset` | — | +| `UpdatedAt` | `DateTimeOffset` | — | +| `Files` | `IReadOnlyList?` | — | + + +ProjectResponse is an immutable data carrier that represents a snapshot of a project's metadata as returned by the API. It groups identity information (Id, Name), optional descriptive fields (Description, SystemPrompt), visual and presentation settings (AvatarSeed, PatternOverride, PaletteOverride), lifecycle flags (IsDefault), timestamps (CreatedAt, UpdatedAt), and the collection of related files (Files). As a C# record, it favors value-based equality and immutability, making it ideal as a response contract that clients can safely consume without worrying about inadvertent mutations. + +## Remarks +This abstraction decouples the API surface from the domain model, providing a stable contract for clients even as internal implementations evolve. Nullable fields reflect optional data that may be omitted by the server (e.g., Description, SystemPrompt, PatternOverride, PaletteOverride, Files). Consumers should handle nulls gracefully or normalise to empty collections where appropriate. The AvatarSeed enables deterministic avatar generation across sessions and clients. + +## Example +```csharp +var sample = new ProjectResponse( + Id: Guid.Empty, + Name: "Starter Project", + Description: null, + SystemPrompt: null, + AvatarSeed: 123L, + IsDefault: false, + PatternOverride: null, + PaletteOverride: null, + CreatedAt: DateTimeOffset.UtcNow, + UpdatedAt: DateTimeOffset.UtcNow, + Files: Array.Empty() +); +``` + +## Notes +- Nullable fields may be null; check before use. +- Files may be null or empty; treat null as no files when consuming, or normalise using an empty array/list. +- Being a record enables with-expressions to create modified copies, preserving immutability while deriving new instances. + +--- + +## SetSkinRequest +> **File:** `src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs` +> **Kind:** record + +```csharp +public record SetSkinRequest(string? Pattern, string? Palette) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| [`Pattern`](../../../../webapp/src/pulse/patterns.ts.md) | `string?` | — | +| `Palette` | `string?` | — | + + +SetSkinRequest is a small, immutable data carrier used to specify per-dimension skin overrides for a project in the API. It carries two optional fields, Pattern and Palette. Each call to the API includes both fields (PUT semantics). If a field is null, that override is cleared and the system falls back to the seed-derived value for that dimension. When non-null, the provided value overrides the seed behavior for that dimension. The controller layer validates the supplied identifiers against the SequenceCatalog to ensure only known skin components can be applied. + +## Remarks +SetSkinRequest acts as a stable contract between clients and the server-side skin logic. By bundling two independent overrides into a single object, it enables clear, idempotent updates where you can enable or disable a specific dimension's override in a single call. It also decouples the API from the underlying storage or domain logic, so changes to how skins are stored won't affect API clients as long as the contract remains the same. + +## Example +```csharp +// Set both overrides +var request = new SetSkinRequest("FloralPattern", "VividPalette"); + +// Clear only the Pattern override (Palette remains as provided) +var clearPattern = new SetSkinRequest(null, "VividPalette"); + +// Clear both overrides (revert to seeds for both) +var reset = new SetSkinRequest(null, null); +``` + +## Notes +- Null values clear the override for that dimension. +- Both fields are sent on every PUT request; you cannot omit one field to indicate no change. +- The API validates the provided identifiers against SequenceCatalog at the controller boundary to ensure only known skin components are applied. + +--- + +## UpdateProjectRequest +> **File:** `src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs` +> **Kind:** record + +```csharp +public record UpdateProjectRequest(string? Name, string? Description, string? SystemPrompt) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| [`Name`](../../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string?` | — | +| `Description` | `string?` | — | +| `SystemPrompt` | `string?` | — | + + +UpdateProjectRequest is a data transfer object used to convey changes to a project's metadata. It exposes three nullable properties—Name, Description, and SystemPrompt—allowing callers to specify only the fields they want to update while leaving others untouched. + +## Remarks +This record represents a partial-update contract for a project. By modeling all fields as nullable, it enables patch-style updates against a Projects API without the need for multiple specialized payload types. It serves as a focused abstraction that decouples update semantics from the full project representation, making intent explicit when performing update operations. + +## Example +```csharp +// Update only the Name +var req1 = new UpdateProjectRequest("NewName", null, null); + +// Update Name and SystemPrompt +var req2 = new UpdateProjectRequest("NewName", null, "System prompt text"); + +// Update Description only +var req3 = new UpdateProjectRequest(null, "New description", null); +``` + +## Notes +- The constructor is positional; to omit a field, pass null explicitly for that parameter. +- Whether a null value means "no change" or triggers a specific API behavior is determined by the consuming API contract; the record itself does not encode this semantics. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md new file mode 100644 index 0000000..91a67a6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md @@ -0,0 +1,86 @@ +# GabrielSequenceResponse.cs + +> **Source:** `src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs` + +## Contents + +- [GabrielSequenceResponse](#gabrielsequenceresponse) +- [SequenceMetadataResponse](#sequencemetadataresponse) + +--- + +## GabrielSequenceResponse +> **File:** `src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs` +> **Kind:** record + +```csharp +public record GabrielSequenceResponse( + int Version, + IReadOnlyList Palette, + IReadOnlyList Frames, + SequenceMetadataResponse Metadata) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Version` | `int` | — | +| `Palette` | `IReadOnlyList` | — | +| `Frames` | `IReadOnlyList` | — | +| `Metadata` | `SequenceMetadataResponse` | — | + + +GabrielSequenceResponse is the wire-format container for a Gabriel sequence used by the Gabriel API surface. It bundles a Version, a Palette, a collection of Frames, and related Metadata. The frame data are 16×16 images encoded as palette indices, so the body remains compact: a 16×16 frame is 256 integers before any serialization overhead. Palette colors are serialized as [r, g, b] triples, and clients reconstruct full colors by indexing into Palette. Frames are transmitted in canonical order 0..63; to derive per-layer slices you can compute floor(frameIndex / 16), mapping 0 to DNA, 1 to Traits, 2 to Context, and 3 to Live. + +## Remarks +GabrielSequenceResponse serves as a compact transport DTO that carries all data needed to render a Gabriel sequence: version, palette, frames, and metadata. By encoding pixel data as palette indices and storing palette colors separately, it minimizes payload size while preserving full color fidelity via a simple index look-up at decode time. The canonical 0..63 frame ordering enables straightforward layer extraction (DNA, Traits, Context, Live) by computing floor(frameIndex / 16). + +## Notes +- Each frame is a 16×16 grid encoded as 256 integers; ensure the Frames collection contains exactly 64 frames (indices 0..63), each with 256 entries. +- Indices in frames must be in the range 0..Palette.Length-1; out-of-range indices cannot be decoded. +- Canonical ordering means you should not assume any other ordering; the layer derivation uses floor(frameIndex/16) to map to DNA/Traits/Context/Live. + +--- + +## SequenceMetadataResponse +> **File:** `src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs` +> **Kind:** record + +```csharp +public record SequenceMetadataResponse( + long Seed, + DateTimeOffset GeneratedAt, + string? StateSummary) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Seed` | `long` | — | +| `GeneratedAt` | `DateTimeOffset` | — | +| `StateSummary` | `string?` | — | + + +SequenceMetadataResponse is an immutable data transfer object that bundles the seed used to initialize a deterministic sequence, the exact time the metadata was generated, and an optional summary of the current state. It is intended for API responses that expose sequence metadata so clients can log, display, or reproduce results by using the seed and GeneratedAt timestamp; the StateSummary provides contextual prose when available. + +## Remarks +By leveraging C#'s record semantics, this type guarantees value-based equality and immutability, aiding caching and comparison. The GeneratedAt field uses DateTimeOffset to preserve the original offset, avoiding timezone ambiguity in distributed systems. The optional StateSummary keeps the surface area small while still offering guidance for debugging or user-facing messages. + +## Example +```csharp +// Example: constructing a metadata response +var metadata = new SequenceMetadataResponse( + Seed: 1234567890L, + GeneratedAt: DateTimeOffset.UtcNow, + StateSummary: "Initialized for deterministic run" +); +``` + +## Notes +- StateSummary may be null; callers should handle nulls gracefully. +- The Seed value is intended for reproducibility, not security; do not rely on it for cryptographic purposes. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md new file mode 100644 index 0000000..9749065 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md @@ -0,0 +1,40 @@ +# SequenceCatalogResponse + +> **File:** `src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs` +> **Kind:** record + +```csharp +public record SequenceCatalogResponse( + IReadOnlyList Patterns, + IReadOnlyList Palettes) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| [`Patterns`](../../../Gabriel.Engine/Sequence/Patterns.cs.md) | `IReadOnlyList` | — | +| `Palettes` | `IReadOnlyList` | — | + + +SequenceCatalogResponse encapsulates the catalog of selectable avatar skin identifiers, split into patterns and palettes. It acts as the server-side reference for valid IDs that clients may submit via PATCH endpoints, with validation performed against these lists before persisting the selection on the entity. The use of plain strings (instead of enums) enables catalog growth without requiring client regeneration. + +## Remarks + +- It provides a stable contract for clients to retrieve the available options and a clear validation target on the server. +- The dual-list structure lets the server evolve patterns and palettes independently and safely. +- As a record, it offers value-based equality and immutability for the response payload, which helps with caching and predictable comparisons. + +## Example + +```csharp +var catalog = new SequenceCatalogResponse( + Patterns: new[] { "PatternA", "PatternB" }, + Palettes: new[] { "PaletteRed", "PaletteBlue" } +); +``` + +## Notes + +- Validation ensures only identifiers in the respective lists are accepted. +- If you have no options, pass empty lists rather than null to avoid null-reference issues. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/AuthController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/AuthController.cs.md new file mode 100644 index 0000000..cb7844b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/AuthController.cs.md @@ -0,0 +1,70 @@ +# AuthController + +> **File:** `src/api/Gabriel.API/Controllers/AuthController.cs` +> **Kind:** class + +*Figure: How AuthController works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +Start["AuthController receives request"] +Start --> Route{"Route"} + +Route -->|"/register"| RegisterReq["AuthController: POST /api/auth/register (RegisterRequest)"] +RegisterReq --> RegCheck["AuthOptions: RegistrationEnabled?"] +RegCheck -->|"No"| RegDisabled["Return Message 'Registration disabled' (403)"] +RegCheck -->|"Yes"| CreateUser["Create ApplicationUser; on failure throw DomainException"] +CreateUser --> IssueReg["IJwtTokenService: Issue TokenPair"] +IssueReg --> SetCookiesReg["AuthCookies: Set cookies from TokenPair"] +SetCookiesReg --> ReturnReg["Return JwtResponse (TokenPair)"] + +Route -->|"/login"| LoginReq["AuthController: POST /api/auth/login (LoginRequest)"] +LoginReq --> Verify["Verify credentials"] +Verify -->|"Fail"| LoginFail["Return Message 'Unauthorized' (401)"] +Verify -->|"OK"| IssueLogin["IJwtTokenService: Issue TokenPair"] +IssueLogin --> SetCookiesLogin["AuthCookies: Set cookies from TokenPair"] +SetCookiesLogin --> ReturnLogin["Return JwtResponse (TokenPair)"] + +Route -->|"/refresh"| RefreshReq["AuthController: POST /api/auth/refresh (RefreshTokenRequest)"] +RefreshReq --> ReadRefresh["Read RefreshToken from AuthCookies OR RefreshTokenRequest body"] +ReadRefresh --> Rotate["IJwtTokenService: Rotate/validate RefreshToken -> new TokenPair"] +Rotate --> SetCookiesRefresh["AuthCookies: Set new cookies from TokenPair"] +SetCookiesRefresh --> ReturnRefresh["Return JwtResponse (TokenPair)"] + +Route -->|"/logout"| Logout["AuthController: POST /api/auth/logout"] +Logout --> ReadCookieLogout["AuthCookies: read RefreshToken"] +ReadCookieLogout --> RevokeLogout["Revoke that RefreshToken (RefreshToken)"] +RevokeLogout --> ClearCookies["AuthCookies: Clear cookies"] +ClearCookies --> ReturnLogout["Return Message 'Logged out'"] + +Route -->|"/revoke"| Revoke["AuthController: POST /api/auth/revoke (RefreshTokenRequest)"] +Revoke --> RevokeSpecific["Revoke provided RefreshToken (RefreshToken)"] +RevokeSpecific --> ReturnRevoke["Return Message"] + +Route -->|"/revoke-all"| RevokeAll["#quot;AuthController: POST /api/auth/revoke-all [Authorize"]"] +RevokeAll --> GetCurrent["ICurrentUser: get current user id"] +GetCurrent --> RevokeAllAction["Revoke all RefreshTokens for user"] +RevokeAllAction --> ReturnRevokeAll["Return Message"] + +Route -->|"/me"| Me["#quot;AuthController: GET /api/auth/me [Authorize"]"] +Me --> GetCurrent2["ICurrentUser: get id + email"] +GetCurrent2 --> ReturnMe["Return MeResponse (id + email)"] +``` + +```csharp +[ApiController] +[Route("auth")] +public class AuthController : ControllerBase +``` + + +A single HTTP authentication surface for the application that exposes endpoints for registration, login, refresh, logout, token revocation, and retrieving the current user. Use this controller when you need the canonical server-side implementation of authentication that serves both browser-based clients (via HttpOnly cookies) and external clients (via token JSON in the response body) from the same endpoints. + +## Remarks +This controller centralizes JWT issuance, refresh rotation, cookie management, and Identity integration so the rest of the app can rely on a single, consistent auth surface. It delegates user persistence and credential checks to ASP.NET Identity (UserManager/SignInManager), issues JWT pairs through IJwtTokenService, and uses AuthCookies helpers to set/clear/read cookies. The design intentionally returns tokens in the response body while also setting HttpOnly cookies so the same endpoints work for both browser flows (which rely on cookies) and external API clients (which read tokens from the body). + +## Notes +- Registration can be disabled at runtime via AuthOptions.RegistrationEnabled (checked through IOptionsMonitor), so flipping the flag takes effect immediately without an app restart. +- Endpoints that create or rotate tokens (register, login, refresh) both set cookies and return the token pair in the response body — browser clients should rely on cookies while external clients should use the body. +- The controller avoids account enumeration: a missing user and a bad password follow the same error path for login. Identity validation errors during registration are surfaced as a DomainException containing Identity's error descriptions. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md new file mode 100644 index 0000000..64ae7f6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md @@ -0,0 +1,69 @@ +# ConversationsController + +> **File:** `src/api/Gabriel.API/Controllers/ConversationsController.cs` +> **Kind:** class + +*Figure: How ConversationsController works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +ctrl["ConversationsController receives HTTP request"] + +ctrl --> list_ep["List: GET /conversations"] +ctrl --> get_ep["Get: GET /conversations/{id}"] +ctrl --> create_ep["Create: POST /conversations (body: CreateConversationRequest)"] +ctrl --> update_ep["Update: PATCH /conversations/{id} (body: UpdateConversationRequest)"] + +list_ep --> chat_list["IChatService.ListConversationsAsync(projectId)"] +chat_list --> map_list["Map each to ConversationResponse (includeMessages: false)"] +map_list --> ok_list["Return 200 Ok(IReadOnlyList)"] + +get_ep --> chat_get["IChatService.GetConversationAsync(id)"] +chat_get --> loadproj_get["LoadProjectAsync(conv.ProjectId)"] +loadproj_get --> proj_null_get{"projectId is null?"} +proj_null_get -->|"Yes"| proj_none_get["Project = null"] +proj_null_get -->|"No"| proj_fetch_get["IProjectService.GetAsync(pid)"] +proj_fetch_get --> proj_loaded_get["Project returned"] +proj_none_get --> map_get["conv.ToResponse(includeMessages: true, project)"] +proj_loaded_get --> map_get +map_get --> ok_get["Return 200 Ok(ConversationResponse)"] + +create_ep --> chat_create["IChatService.CreateConversationAsync(projectId, title)"] +chat_create --> loadproj_create["LoadProjectAsync(conv.ProjectId)"] +loadproj_create --> proj_null_create{"projectId is null?"} +proj_null_create -->|"Yes"| proj_none_create["Project = null"] +proj_null_create -->|"No"| proj_fetch_create["IProjectService.GetAsync(pid)"] +proj_fetch_create --> proj_loaded_create["Project returned"] +proj_none_create --> map_create["conv.ToResponse(includeMessages: true, project)"] +proj_loaded_create --> map_create +map_create --> created["Return 201 CreatedAtAction(Get, { id })"] + +update_ep --> chat_update["IChatService.RenameConversationAsync(id, title)"] +chat_update --> loadproj_update["LoadProjectAsync(conv.ProjectId)"] +loadproj_update --> proj_null_update{"projectId is null?"} +proj_null_update -->|"Yes"| proj_none_update["Project = null"] +proj_null_update -->|"No"| proj_fetch_update["IProjectService.GetAsync(pid)"] +proj_fetch_update --> proj_loaded_update["Project returned"] +proj_none_update --> map_update["conv.ToResponse(includeMessages: false, project)"] +proj_loaded_update --> map_update +map_update --> ok_update["Return 200 Ok(ConversationResponse)"] +``` + +```csharp +[ApiController] +[Authorize] +[Route("conversations")] +public class ConversationsController : ControllerBase +``` + + +Exposes the HTTP endpoints for managing conversation resources under the "conversations" route. This ApiController is protected with [Authorize] and delegates conversation operations (list, get, create, rename, avatar reroll, skin pinning) to backend services such as IChatService, IAgentService and IProjectService; use it when you need a REST surface for CRUD and conversation-specific actions rather than calling service-layer APIs directly. + +## Remarks +The controller intentionally keeps thin HTTP actions and forwards work to injected services. A private helper, LoadProjectAsync, is used by single-conversation endpoints to load the conversation's parent project so the response can include project-specific metadata (for example, whether the project is the default and the effective avatar seed). The List endpoint intentionally skips loading project data to avoid an N+1 query pattern and because sidebar list rows do not render avatars. + +## Notes +- The List endpoint returns ConversationResponse objects with messages omitted (the controller calls ToResponse(includeMessages: false)). Single-conversation endpoints (Get, Create) return responses that include messages. +- The optional projectId query parameter on List scopes the result to a project; omitting it returns all conversations for the caller. +- PUT /{id}/skin is intended to pin/clear a conversation avatar skin; this is meaningful for standalone (default-project) conversations. For real project-backed conversations the project's skin is rendered instead (a pinned conversation skin is persisted but ignored at render time). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md new file mode 100644 index 0000000..e0c7050 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md @@ -0,0 +1,18 @@ +# DiagnosticsController + +> **File:** `src/api/Gabriel.API/Controllers/DiagnosticsController.cs` +> **Kind:** class + +```csharp +[ApiController] +[Authorize] +[Route("diagnostics")] +public class DiagnosticsController : ControllerBase +``` + + +DiagnosticsController is an authenticated, read-only ASP.NET Core API controller that surfaces aggregated health and performance insights from the generic metric event log for the web_search provider. Developers reach for it when they need a stable, non-sensitive snapshot of web_search activity, latency, and outcomes without querying the raw event store directly; the endpoint is GET /diagnostics/web-search and accepts an optional windowSize to widen or narrow the time window. + +## Remarks + +This controller acts as a focused health-insight façade over the metric store. It aggregates recent log rows by system prefix (web_search.) into per-provider statistics, counting total invocations, successes, errors, and latency, and it records the most recent success or failure details. By performing JSON parsing lazily per-row and selecting only the fields needed for the summary, it minimizes per-call overhead while keeping the surface useful for operators and tooling. It is accessible to any authenticated user (not admin-only) and provides read-only diagnostics; it relies on the WebSearchSystemPrefix constant to filter metrics and can be extended to additional prefixes with minimal changes. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md new file mode 100644 index 0000000..b1997eb --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md @@ -0,0 +1,17 @@ +# MemoriesController + +> **File:** `src/api/Gabriel.API/Controllers/MemoriesController.cs` +> **Kind:** class + +```csharp +[ApiController] +[Authorize] +[Route("memories")] +public class MemoriesController : ControllerBase +``` + + +MemoriesController exposes a REST API for managing memory entries via an IMemoryService, providing listing, upserting (create/update), and deletion for the settings page. Clients can list memories with optional scope and project filters, retrieve a single memory by ID, upsert memory data with POST /memories, and delete memories by ID. The Save method validates the request (ensuring a valid MemoryEntryType, and non-empty Name, Description, and Body) and maps it into a MemoryEntrySpec before invoking the service. As noted in the code comments, upserts are keyed by (UserId, ProjectId, Name) and sending the same request twice simply updates the UpdatedAt timestamp, making the endpoint idempotent. + +## Remarks +This controller acts as the HTTP boundary and DTO translator for memory management, keeping UI concerns separate from the domain service and providing a thin, idempotent surface over IMemoryService. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ModelsController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ModelsController.cs.md new file mode 100644 index 0000000..6311391 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ModelsController.cs.md @@ -0,0 +1,44 @@ +# ModelsController + +> **File:** `src/api/Gabriel.API/Controllers/ModelsController.cs` +> **Kind:** class + +```csharp +[ApiController] +[Authorize] +[Route("models")] +public class ModelsController : ControllerBase +``` + + +ModelsController exposes the UI-facing API surface for selecting and listing models. It provides two endpoints: GET returns all cataloged models plus the entry that will be used on the next turn (the active model for the user, resolved against defaults and stored preferences), and PUT /models/active persists or clears the user's selection. It consults IModelCatalog and IUserPreferences so the effective selection is resolved on every turn without requiring a page reload. + +## Remarks +This class acts as a thin API facade between the UI and the domain services that handle model resolution. It centralizes validation (ensuring only known models are selected and that input is complete) and returns a ready-to-consume set of model descriptors with a clear indication of which model is currently active. By resolving the active model on every request, changes take effect on the next interaction rather than requiring a reload, keeping the UI responsive. + +## Notes +- PUT /models/active requires either both Provider and Name to be supplied together or both omitted to clear; otherwise returns 400 with an error describing the misconfiguration. +- If both Provider and Name are provided, the controller validates that the model exists in the catalog; if not, it returns 400 with a not-registered message. +- Clearing the selection uses the empty body values (null/empty) to reset the preference and fall back to the catalog default on the next read. +- The active selection in the returned list is determined by a case-insensitive provider match and an exact name match, ensuring a stable and predictable UI state. + +## Dependencies +- ControllerBase +- ModelsResponse +- ModelDto +- SelectedModelDto +- StringComparison +- AvailableModels + +## Dependency APIs (verified signatures) +- record `ModelsResponse` (`src/api/Gabriel.API/Contracts/Models/ModelDto.cs`) +- record `ModelDto` (`src/api/Gabriel.API/Contracts/Models/ModelDto.cs`) +- record `SelectedModelDto` (`src/api/Gabriel.API/Contracts/Models/ModelDto.cs`) +- property `AvailableModels` (`src/api/Gabriel.Engine/Providers/IModelCatalog.cs`) + +## Symbol To Document +- Name: `ModelsController` +- Kind: class +- File: `src/api/Gabriel.API/Controllers/ModelsController.cs` +- Language: csharp +- ID: 8b938895-5fbc-4f78-9343-d411cc67f8e3 \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md new file mode 100644 index 0000000..4eb2f3e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md @@ -0,0 +1,39 @@ +# ProjectFilesController + +> **File:** `src/api/Gabriel.API/Controllers/ProjectFilesController.cs` +> **Kind:** class + +```csharp +[ApiController] +[Authorize] +[Route("projects/{projectId:guid}/files")] +public class ProjectFilesController : ControllerBase +``` + + +ProjectFilesController exposes a RESTful HTTP surface for managing files tied to a specific project. It supports listing all files for a project, downloading a chosen file as a streamed response with the correct content type and filename, uploading a single file via multipart/form-data, and deleting a file. The controller delegates all storage and business logic to IProjectFileService, focusing on HTTP-level concerns, request validation, and response shaping. + +The routes are defined under /projects/{projectId:guid}/files, with: +- GET /projects/{projectId}/files to list files +- GET /projects/{projectId}/files/{fileId}/download to download a file +- POST /projects/{projectId}/files to upload a single file (multipart/form-data) +- DELETE /projects/{projectId}/files/{fileId} to delete a file + +The implementation emphasizes streaming and proper HTTP semantics: file content is streamed via FileStreamResult with the original ContentType and FileDownloadName preserved, uploads are validated to reject empty files, and the response for a successful upload includes a Location header pointing to the Download action. + +## Remarks +ProjectFilesController is a thin HTTP facade over IProjectFileService. This separation keeps HTTP concerns (routing, binding, validation, response shaping) decoupled from storage and business rules, making it easy to swap storage implementations or test the controller in isolation. Streaming via FileStreamResult avoids loading entire files into memory, which is important for large assets. The [Authorize] attribute ensures only authorized users with access to the project may manage files, and the explicit route structure clarifies the intended usage patterns for clients. + +The Upload action uses a single-file upload design with implicit form binding for IFormFile (no [FromForm] attribute) to align runtime binding with OpenAPI generation expectations while keeping the runtime path straightforward. A deliberate belt-and-suspenders limit is applied to the request size to prevent excessively large payloads from being accepted in one go. + +## Notes +- This controller currently supports a single-file upload per request; batch uploads are not implemented yet. +- Uploads are capped at 50 MB to protect the API from oversized requests; clients should respect this limit or adjust server configuration if higher limits are required. +- The code relies on the framework’s disposal semantics for streams: the FileStreamResult will dispose the underlying stream after the response is written. + +## Symbol To Document +- Name: `ProjectFilesController` +- Kind: class +- File: `src/api/Gabriel.API/Controllers/ProjectFilesController.cs` +- Language: csharp +- ID: b95b9682-0f27-45d4-b6d3-3b6eb29a9442 \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md new file mode 100644 index 0000000..c49c778 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md @@ -0,0 +1,71 @@ +# ProjectsController + +> **File:** `src/api/Gabriel.API/Controllers/ProjectsController.cs` +> **Kind:** class + +*Figure: How ProjectsController works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +ProjectsController["ProjectsController receives HTTP request"] +ProjectsController --> route{ "Route: HTTP method & path" } + +route -->| "GET /projects" | listCall["Call IProjectService.ListAsync(ct)"] +listCall --> mapList["Map to ProjectResponse list (ToResponse includeFiles: false)"] +mapList --> returnList["Return Ok(ProjectResponse list)"] + +route -->| "GET /{id}" | getCall["Call IProjectService.GetWithFilesAsync(id, ct)"] +getCall --> mapGet["Map to ProjectResponse (ToResponse includeFiles: true)"] +mapGet --> returnGet["Return Ok(ProjectResponse)"] + +route -->| "POST /projects" | postReq["Bind CreateProjectRequest body"] +postReq --> createCall["Call IProjectService.CreateAsync(request.Name, request.Description, request.SystemPrompt, ct)"] +createCall --> created["Return CreatedAtAction(Get, id, ProjectResponse includeFiles: false)"] + +route -->| "PATCH /{id}" | patchReq["Bind UpdateProjectRequest body"] +patchReq --> getForPatch["Call IProjectService.GetAsync(id, ct)"] +getForPatch --> checkName{ "Is request.Name not null?" } +checkName -- yes --> renameCall["Call IProjectService.RenameAsync(id, request.Name, ct)"] +checkName -- no --> skipRename["Skip rename"] +renameCall --> continue1["Continue"] +skipRename --> continue1 +continue1 --> checkDesc{ "Is request.Description not null?" } +checkDesc -- yes --> descCall["Call IProjectService.UpdateDescriptionAsync(id, request.Description, ct)"] +checkDesc -- no --> skipDesc["Skip description update"] +descCall --> continue2["Continue"] +skipDesc --> continue2 +continue2 --> checkPrompt{ "Is request.SystemPrompt not null?" } +checkPrompt -- yes --> promptCall["Call IProjectService.UpdateSystemPromptAsync(id, request.SystemPrompt, ct)"] +checkPrompt -- no --> skipPrompt["Skip system prompt update"] +promptCall --> finalizePatch["Map to ProjectResponse (ToResponse includeFiles: false)"] +skipPrompt --> finalizePatch +finalizePatch --> returnPatch["Return Ok(ProjectResponse)"] + +route -->| "DELETE /{id}" | deleteCall["Call IProjectService.DeleteAsync(id, ct)"] +deleteCall --> returnDelete["Return NoContent()"] + +route -->| "GET /{id}/sequence" | seqCall["Call IGabrielSequenceService.GetForProjectAsync(id, ct)"] +seqCall --> seqMap["Map to GabrielSequenceResponse (ToResponse)"] +seqMap --> returnSeq["Return Ok(GabrielSequenceResponse)"] +``` + +```csharp +[ApiController] +[Authorize] +[Route("projects")] +public class ProjectsController : ControllerBase +``` + + +An API controller that exposes HTTP endpoints for managing Project entities and their shared Gabriel sequence. Routes are rooted at "projects" and all actions require an authenticated user; the controller delegates business logic to IProjectService and sequence aggregation to IGabrielSequenceService, returning ProjectResponse and GabrielSequenceResponse DTOs for HTTP clients. + +## Remarks +This controller is a thin HTTP surface over the domain services: IProjectService handles create/read/update/delete and avatar operations, while IGabrielSequenceService aggregates the project-level Gabriel sequence used by clients. The controller converts domain Project objects into ProjectResponse DTOs (via ToResponse calls) and uses standard RESTful semantics (GET for list/detail, POST for create returning CreatedAtAction, PATCH for partial updates, DELETE for removal). Project-level sequence and avatar endpoints are provided so clients can obtain the sequence for non-default projects and request avatar rerolls without manipulating project internals. + +## Notes +- PATCH semantics: the Update action treats the incoming DTO as an "all-nullable" patch. A null value for Description or SystemPrompt is interpreted as an explicit clear, and a null Name means "leave unchanged". Because JSON deserialization maps both missing properties and explicit nulls to null, the controller currently does not distinguish between omitted fields and explicit clears — this is a known simplification noted in the source. +- Cancellation: every public action accepts a CancellationToken and forwards it to the underlying services; callers can cancel long-running operations. +- Created response: Create returns CreatedAtAction(nameof(Get), new { id = project.Id }, ...) so clients receive a Location header pointing to the project's GET endpoint. +- Authorization: the [Authorize] attribute on the controller applies to all endpoints; ensure callers present appropriate credentials/claims before invoking these routes. +- Avatar behavior: RerollAvatar changes the seed-derived aspects of the avatar while preserving any pattern/palette overrides (if present). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md new file mode 100644 index 0000000..36227ff --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md @@ -0,0 +1,21 @@ +# SequenceController + +> **File:** `src/api/Gabriel.API/Controllers/SequenceController.cs` +> **Kind:** class + +```csharp +[ApiController] +[Authorize] +[Route("sequence")] +public class SequenceController : ControllerBase +``` + + +Exposes a global API endpoint that returns the catalog of available skin identifiers (patterns and palettes) for pinning a skin override on a project or conversation. The data comes from the static SequenceCatalog and is wrapped in a lightweight SequenceCatalogResponse; because the lists are static, clients can fetch them once per session without incurring significant overhead. This endpoint is intentionally sequence-scoped and sits under the sequence route, separating it from per-conversation or per-project endpoints that reside on their respective controllers. + +## Remarks +This centralized catalog pattern avoids duplicating the same options across multiple controllers and ensures a consistent set of skin options across the API surface. The API surface is decoupled from internal storage: changes to SequenceCatalog or the response wrapper won't require client changes, other than fetching updated data. Access is guarded by Authorization, reinforcing that skin customization is a user-facing feature tied to a validated session. + +## Notes +- No query parameters or per-user filtering; results are global and static. +- If the catalog updates, clients should re-fetch to see new options; the endpoint does not push updates. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md new file mode 100644 index 0000000..84f8000 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md @@ -0,0 +1,22 @@ +# AuthCookies + +> **File:** `src/api/Gabriel.API/Identity/AuthCookies.cs` +> **Kind:** class + +```csharp +internal static class AuthCookies +``` + + +AuthCookies centralizes the lifecycle of the authentication cookies used by the JWT-based flow. It exposes Set to issue both access and refresh cookies, Clear to revoke them, and ReadRefresh to read the refresh token from inbound requests, ensuring cookie handling is consistent across the app. + +By aligning with GabrielIdentityExtensions' cookie names, it guarantees that the cookie presented by the browser maps to a valid principal during authentication. + +## Remarks + +By centralizing cookie policy decisions (HttpOnly, Secure, SameSite) and explicit paths, AuthCookies minimizes exposure if a response leaks. The refresh cookie is scoped to /api/auth to reduce risk on ordinary API calls. This class sits at the boundary between HttpResponse and the identity subsystem and should be the single place in code that mutates authentication cookies, simplifying testing and auditing. + +## Notes + +- Ensure the app's hosting environment reflects the true scheme for Secure cookies (IsHttps) — behind proxies TLS termination may affect this; configure forwarded headers accordingly. +- Deleting cookies requires matching the path and name; the code uses "/" for Access and "/api/auth" for Refresh, so ensure consistency when changing paths or cookie names. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs.md new file mode 100644 index 0000000..617ccaa --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs.md @@ -0,0 +1,45 @@ +# HttpContextCurrentUser + +> **File:** `src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs` +> **Kind:** class + +```csharp +public class HttpContextCurrentUser : ICurrentUser +``` + + +HttpContextCurrentUser is an ICurrentUser implementation that reads the current user from HttpContext.User. It works across Identity cookie, Identity opaque bearer, and minted JWTs because they all populate the same ClaimsPrincipal. It exposes three properties: UserId (Guid?), IsAuthenticated (bool), and Email (string?). UserId reads the user id from the NameIdentifier claim (ClaimTypes.NameIdentifier) or the "sub" claim used by our JWTs, parsing it as a Guid; if no suitable claim is found or parsing fails, it returns null. IsAuthenticated reflects whether the current HttpContext.User.Identity is authenticated. Email returns the Email claim value when present. + +## Remarks +The adapter centralizes access to the authenticated user so business logic can depend on ICurrentUser rather than HttpContext details or specific authentication schemes. It decouples identity extraction from callers, making it easier to test and to swap authentication implementations. When data is missing (e.g., anonymous users or absent claims), the properties expose nullable results to encourage callers to handle anonymous or partial identities gracefully. + +## Example +```csharp +// Example usage in a consuming service +public class DeliveryService +{ + private readonly ICurrentUser _currentUser; + + public DeliveryService(ICurrentUser currentUser) + { + _currentUser = currentUser; + } + + public void ShowIdentity() + { + if (_currentUser.IsAuthenticated) + { + Console.WriteLine($"UserId={_currentUser.UserId}, Email={_currentUser.Email}"); + } + else + { + Console.WriteLine("Anonymous user"); + } + } +} +``` + +## Notes +- UserId is null when the user is not authenticated or the claims do not contain a valid GUID. +- Email may be null if the Email claim is not present. +- This class depends on IHttpContextAccessor; ensure it is registered in DI and available in non-controller contexts (or mock ICurrentUser in tests) to enable deterministic behavior. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Identity/IdentitySeeder.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Identity/IdentitySeeder.cs.md new file mode 100644 index 0000000..0fe48a2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Identity/IdentitySeeder.cs.md @@ -0,0 +1,19 @@ +# IdentitySeeder + +> **File:** `src/api/Gabriel.API/Identity/IdentitySeeder.cs` +> **Kind:** class + +```csharp +public static class IdentitySeeder +``` + + +IdentitySeeder is an idempotent startup seeder that runs after EF migrations to ensure a configured Identity user exists. It reads AuthOptions.Seed to decide whether to seed; if seeding is disabled, it becomes a no-op. When enabled, it validates that UserName and Password are configured; if either is blank, it logs a warning and returns without creating a user. If a user with the configured UserName already exists, it performs no action. Otherwise, it creates a new ApplicationUser with UserName and Email, marks EmailConfirmed as true, and creates the user using UserManager with the configured plaintext password; Identity hashes the password automatically via the registered PasswordHasher. The operation logs outcomes and surfaces errors if creation fails. + +## Remarks +IdentitySeeder centralizes the initial identity state for the application in a predictable startup step that runs after the database schema is ready. It encapsulates the seeding policy (enabled/disabled, required fields, and idempotency) in a single place, reducing scattered initialization logic across the codebase. It relies on DI to obtain the Auth options, a logger, and the UserManager, and it communicates outcomes via the Gabriel.IdentitySeeder logger category to aid traceability. + +## Notes +- Be aware that enabling seeding with a missing UserName or Password will log a warning and skip seeding. +- The seed uses the configured plaintext password; Identity hashes it upon creation. +- Seeding runs after EF migrations to guarantee the Users table exists; if the configured user already exists, seeding is skipped. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Mapping/ContractMappings.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Mapping/ContractMappings.cs.md new file mode 100644 index 0000000..f50b9a6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Mapping/ContractMappings.cs.md @@ -0,0 +1,19 @@ +# ContractMappings + +> **File:** `src/api/Gabriel.API/Mapping/ContractMappings.cs` +> **Kind:** class + +```csharp +internal static class ContractMappings +``` + + +ContractMappings is a small, focused set of extension methods that translate the domain models Project, ProjectFile, and Conversation into their API-facing DTOs (ProjectResponse, ProjectFileResponse, ConversationResponse). It centralizes the transformation logic so API endpoints can return stable, well-formed payloads without duplicating mapping code. The methods handle optional data (e.g., including project files, or including conversation messages), derive derived fields such as the mode name and avatar seed, and apply UI-oriented filtering and grouping (like active tool messages and per-variant siblings) to ensure the client sees a coherent view of each entity's state. + +## Remarks +ContractMappings isolates presentation concerns from business logic, providing a single, testable surface for converting domain models into API responses. It precomputes data the client relies on (variant-sibling lists, modeName, effective avatar seeds) and applies consistent inclusion rules (files, messages, and tool-call data) across endpoints. The implementation also mirrors the model's semantics for tool interactions, including only active variants and tool messages linked to active tool calls, so the UI reflects the model's current capability set. + +## Notes +- ToolCallsJson must be valid JSON; the code parses it to extract tool-call IDs, and malformed JSON can throw at runtime. +- When includeFiles is false, the response's Files property is null; callers should handle absence of file data. +- modeName and effectiveSeed may be null if mode or project are not provided; UI should handle null display. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md new file mode 100644 index 0000000..9d51ca6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md @@ -0,0 +1,15 @@ +# GlobalExceptionHandler + +> **File:** `src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs` +> **Kind:** class + +```csharp +public class GlobalExceptionHandler : IExceptionHandler +``` + + +GlobalExceptionHandler serves as the single translation layer between domain exceptions and HTTP responses. Implementing IExceptionHandler, it is registered in the DI container via `AddExceptionHandler`() and wired into the HTTP pipeline with app.UseExceptionHandler() so that all unhandled exceptions flowing through the pipeline are captured in one place. The handler maps a set of domain-specific exceptions to appropriate HTTP status codes and ProblemDetails, logs the outcome, and returns a structured JSON payload to clients. + +When an exception occurs, the handler categorizes it into one of several outcomes: 404 for NotFoundException, 400 for DomainException or ArgumentException, 401 for UnauthorizedAccessException, and 500 for anything else. For non-500 errors, it logs an informational message including the exception type and message; for 500 errors, it logs the exception as an error. It then constructs a ProblemDetails object containing the HTTP status, a short title, a detailed message (the exception message for non-500s, or a generic "An unexpected error occurred." for 500s), and the request path as the Instance. This ProblemDetails object is serialized to JSON and written to the response body with the corresponding status code, and the method returns true to indicate the exception has been handled. + +This approach provides a consistent, API-friendly error surface across the application, ensuring clients receive structured error information while the server maintains a centralized, predictable error-handling policy. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs.md new file mode 100644 index 0000000..c07bda2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs.md @@ -0,0 +1,19 @@ +# RequireNonNullablePropertiesSchemaFilter + +> **File:** `src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs` +> **Kind:** class + +```csharp +public class RequireNonNullablePropertiesSchemaFilter : ISchemaFilter +``` + + +Adds non-nullable reference-type properties from C# into OpenAPI's required set, so generated TypeScript clients emit them as non-optional fields. The filter examines CLR properties of the target type, uses NullabilityInfoContext to determine which properties are non-nullable, and, when a matching OpenAPI property exists (by camel-cased name), marks it as required in the schema. It only mutates the concrete OpenApiSchema instance (the interface’s Required is read-only). + +## Remarks +Sits in the Swagger/OpenAPI generation pipeline to align C# nullability with OpenAPI semantics. By mutating the concrete schema's Required collection based on CLR nullability, it ensures non-nullable properties surface as required in generated clients. The ToCamel naming ensures compatibility with common OpenAPI naming conventions. + +## Notes +- Only affects properties that have a corresponding key in concrete.Properties; if no such property exists in the OpenAPI schema, nothing is changed. +- Initializes the Required collection if it is null before adding names, enabling the mutation on the concrete schema. +- Relies on a shared NullContext to inspect property nullability via reflection; the operation is read-only for the interface and mutates the concrete schema when applicable. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.API/Program.cs.md b/docs/auriondocs/Code/src/api/Gabriel.API/Program.cs.md new file mode 100644 index 0000000..112fa1e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.API/Program.cs.md @@ -0,0 +1,15 @@ +# Program + +> **File:** `src/api/Gabriel.API/Program.cs` +> **Kind:** file + + +Program is the application's entry point that bootstraps and runs the ASP.NET Core host. It centralizes startup concerns—logging, configuration (including Infisical-sourced secrets), service registration, and middleware wiring—so developers modify startup behavior in one place rather than scattering it across the app. + +## Remarks +This symbol acts as the bootstrapping nucleus, ensuring the host is properly configured before any application code runs. By loading Infisical secrets early, it guarantees configuration bindings have live values when services are constructed, and by wiring Serilog early, it ensures startup logs are captured through the full pipeline. It also centralizes the ordering of infrastructure concerns (controllers, OpenAPI/Swagger, problem details, exception handling, and CORS), keeping startup logic isolated from business logic. + +## Notes +- The bootstrap logger runs before the host's logging pipeline is built; environments without a console or proper stdout redirection may not capture these logs. +- Secrets loaded from Infisical at startup mean a misconfiguration or network issue can prevent the app from starting or supply incorrect values. +- Startup changes require restarting the application; this file is the single point to adjust how the host is configured and started. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentOptions.cs.md new file mode 100644 index 0000000..ef8454b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentOptions.cs.md @@ -0,0 +1,11 @@ +# AgentOptions + +> **File:** `src/api/Gabriel.Core/Configuration/AgentOptions.cs` +> **Kind:** class + +```csharp +public class AgentOptions : IConfigSection +``` + + +AgentOptions defines the configuration section for the Agent component, encapsulating tunable runtime policies that influence how the agent interacts with tools during a turn. It implements [`IConfigSection`](IConfigSection.cs.md), and its SectionName is "Agent", signaling it can be loaded from a configuration source. The class exposes three configurable properties with sensible defaults: MaxIterations limits how many tool-call iterations are allowed per user turn (default 8) to prevent runaway loops and excessive spend; CompactThreshold expresses the fraction of the model's ContextWindowTokens at which a rolling-summary compact is triggered (default 0.8); and CompactKeepLast specifies how many of the most recent messages should be retained verbatim when producing a compacted summary (default 6). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md new file mode 100644 index 0000000..8846058 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md @@ -0,0 +1,21 @@ +# AgentToolsOptions + +> **File:** `src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs` +> **Kind:** class + +```csharp +public sealed class AgentToolsOptions : IConfigSection +``` + + +AgentToolsOptions is a sealed configuration container for the agent's host-mode filesystem tooling. It implements [`IConfigSection`](IConfigSection.cs.md) and is surfaced under the AgentTools configuration section to group the knobs that govern host-root confinement, file previews, and directory listings. + +## Remarks + +This abstraction centralizes governance over host-mode operations, providing a single place to opt in to host-root confinement and to tune interactive behaviors (previews and listings). By tying host-mode to an anchored root (HostRoot), it ensures filesystem operations stay within a trusted boundary and prevents accidental access to the host system outside that boundary. The defaults are chosen to balance responsiveness with safety while remaining easy to override when a workspace requires it. + +## Notes + +- HostRoot is the opt-in switch for host-mode: leaving it null or empty disables host-mode; project-sandbox-style operations remain available. The operator must deliberately opt in by configuring a non-empty HostRoot. +- Absolute paths must canonicalize under HostRoot; relative paths resolve against HostRoot. Any attempt to operate outside the anchored root should be rejected by design. +- MaxPreviewBytes, DefaultPreviewLines, MaxListEntries, and DefaultListEntries establish safe, predictable defaults for previews and listings. Adjust these with care to match workspace size and performance needs; aggressive settings can impact responsiveness or memory usage. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md new file mode 100644 index 0000000..0782d4d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md @@ -0,0 +1,55 @@ +# AuthOptions.cs + +> **Source:** `src/api/Gabriel.Core/Configuration/AuthOptions.cs` + +## Contents + +- [AuthOptions](#authoptions) +- [SeedUserOptions](#seeduseroptions) + +--- + +## AuthOptions +> **File:** `src/api/Gabriel.Core/Configuration/AuthOptions.cs` +> **Kind:** class + +```csharp +public class AuthOptions : IConfigSection +``` + + +AuthOptions is a compact configuration surface for authentication-related behavior that sits alongside token issuance. Implementing [`IConfigSection`](IConfigSection.cs.md) enables binding from the application's configuration system under the Auth section, so operators can override behavior without code changes. It exposes two knobs: RegistrationEnabled, a kill-switch for the public registration endpoint, and Seed, a bootstrap user created at startup. Seed is idempotent: it will be skipped if a user with the configured UserName already exists. In short, this type consolidates deployment-time controls over who can register and which initial account exists, separate from how tokens are issued. + +--- + +## SeedUserOptions +> **File:** `src/api/Gabriel.Core/Configuration/AuthOptions.cs` +> **Kind:** class + +```csharp +public class SeedUserOptions +``` + + +SeedUserOptions is a configuration object that controls the optional seeding of a default Identity user for deployments. It is opt-in via the Enabled flag to prevent silent account creation in misconfigured environments; when Enabled is true, seeding uses UserName, Email, and Password to construct the seed user. + +## Remarks +SeedUserOptions centralizes seed behavior so deployments can opt into a repeatable, auditable seed path without coupling to runtime user creation logic. It also clarifies how UserName, Email, and ResolvedEmail interact and why Password is accepted in plaintext only at input time (to be hashed by PasswordHasher). + +## Example +```csharp +var options = new SeedUserOptions +{ + Enabled = true, + UserName = "admin", + Email = "admin@example.com", + Password = "P@ssw0rd!" +}; +``` + +## Notes +- Default Enabled is false to avoid silent minting of accounts. +- ResolvedEmail uses Email when provided; if Email is blank, it falls back to UserName. +- Password is provided in plaintext to the seeding path and is hashed by the configured PasswordHasher; never log the plaintext password. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md new file mode 100644 index 0000000..8dad833 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md @@ -0,0 +1,12 @@ +# BraveSearchOptions + +> **File:** `src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs` +> **Kind:** class + +```csharp +public class BraveSearchOptions : IConfigSection +``` + + +BraveSearchOptions is a strongly-typed configuration container for Brave Web Search integration. It implements IConfigSection<BraveSearchOptions> and exposes the Brave Search API base URL (BaseUrl), the subscription key (ApiKey), and the request timeout (TimeoutSeconds) used by the Brave Web Search client. The SectionName is the configuration key "Tools:Web:Brave" that identifies this section in the application's configuration. ApiKey is optional; when left empty Brave Search is effectively disabled and will report an unconfigured state rather than failing. The IsConfigured property returns true when ApiKey contains a non-whitespace value. The default BaseUrl ends with a trailing slash to ensure correct URL composition when concatenating with relative paths (the relative path lands at /res/v1/web/search). + diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs.md new file mode 100644 index 0000000..26c3e66 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs.md @@ -0,0 +1,19 @@ +# GitHubDocsOptions + +> **File:** `src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs` +> **Kind:** class + +```csharp +public class GitHubDocsOptions : IConfigSection +``` + + +GitHubDocsOptions is a strongly-typed configuration section that drives how the docs tool fetches Markdown files from GitHub. It encapsulates the repository identity (Owner, Repo), the target Branch, the path within the repo where docs live, an optional authentication token, and a cache duration for directory listings, enabling consistent, configurable access instead of scattered literals. + +## Remarks +As a configuration anchor, it implements [`IConfigSection`](IConfigSection.cs.md) to integrate with the application's configuration pipeline. The static SectionName identifies the key under which these settings are defined, allowing consumers to bind a GitHubDocsOptions instance from configuration sources. The defaults point at the canonical upstream repository, which makes the docs tool usable out of the box while still permitting project-specific overrides. + +## Notes +- SectionName is the config key used by the configuration system to locate this section (Tools:Docs:GitHub). +- Token is optional; when provided, authenticated GitHub API requests bypass the unauthenticated rate-limit and can support higher request throughput. +- ListCacheMinutes controls how long the repository's directory listings are cached; reads are not cached and may reflect in-flight edits to docs. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GrokOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GrokOptions.cs.md new file mode 100644 index 0000000..131b3c3 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/GrokOptions.cs.md @@ -0,0 +1,18 @@ +# GrokOptions + +> **File:** `src/api/Gabriel.Core/Configuration/GrokOptions.cs` +> **Kind:** class + +```csharp +public class GrokOptions : LLMProviderOptions, IConfigSection +``` + + +GrokOptions provides the configuration contract for the Grok LLM provider. It inherits the standard transport surface from LLMProviderOptions (BaseUrl, ApiKey, TimeoutSeconds, Temperature, TopP, Models) and currently adds no Grok-specific properties; its primary purpose is to enable binding to the Providers:Grok configuration section via the Options pattern so the application can configure this provider independently from others. + +## Remarks +By keeping GrokOptions separate from LLMProviderOptions, the codebase can evolve provider-specific settings without affecting other providers. The SectionName property ties the settings to the Providers:Grok key, allowing services.`ConfigureSection`(config) to wire the correct slice of configuration through the standard Options pipeline. If Grok later introduces fields unique to this provider, GrokOptions is the natural place to house them without mutating the shared base class. + +## Notes +- Be careful: GrokOptions currently does not declare new properties; any provider-specific settings belong here and should be mirrored in appsettings. +- Ensure that the SectionName matches the actual configuration path (Providers:Grok) used by the host to bind this section. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/IConfigSection.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/IConfigSection.cs.md new file mode 100644 index 0000000..57bfaf5 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/IConfigSection.cs.md @@ -0,0 +1,52 @@ +# IConfigSection.cs + +> **Source:** `src/api/Gabriel.Core/Configuration/IConfigSection.cs` + +## Contents + +- [IConfigSection](#iconfigsection) +- [ConfigSectionExtensions](#configsectionextensions) + +--- + +## IConfigSection +> **File:** `src/api/Gabriel.Core/Configuration/IConfigSection.cs` +> **Kind:** interface + +```csharp +public interface IConfigSection where TSelf : class, IConfigSection +``` + + +Defines a contract that any configuration POCO bound from appsettings must implement so the configuration section name is stored on the type itself. By using a static abstract member SectionName, the implementing type provides the section name without requiring an instance. This enables generic code (for example, a configuration binder or registrar using a TSelf generic parameter) to read the section name directly from the type (TSelf.SectionName) and bind the corresponding configuration section to the POCO in a type-safe way. The pattern keeps a single source of truth for the section name next to the type and simplifies refactoring because renaming the type and its section name happens in one place. + +## Remarks +Static abstract members on interfaces are a C# 11 feature. To use this symbol, your project must be compiled with a language version that supports static abstract interface members. The generic constraint `TSelf : class, IConfigSection` ensures you can access SectionName from the type argument without instantiating it, which is ideal for startup-time binding scenarios. This design ties the section name to the type, reducing drift between type names and configuration keys. + +## Notes +- Access SectionName on the concrete type in a generic context (e.g., `TSelf.SectionName`); do not rely on an instance. +- The compiler enforces that each implementing type provides a SectionName; a mismatch will fail compilation. +- Requires C# 11 or newer and a compiler that supports static abstract interface members. + +--- + +## ConfigSectionExtensions +> **File:** `src/api/Gabriel.Core/Configuration/IConfigSection.cs` +> **Kind:** class + +```csharp +public static class ConfigSectionExtensions +``` + + +ConfigSectionExtensions.`ConfigureSection` wires an app configuration section to a strongly-typed options class that implements `IConfigSection`, using the static SectionName defined on TOptions to locate the section. It returns an `OptionsBuilder` so you can continue to compose per-options validators (e.g. .Validate(...)) without losing the fluent Options pattern. + +## Remarks +This abstraction centralizes the mapping between a configuration section and its options type, ensuring a consistent naming convention across the codebase. By requiring TOptions to declare a SectionName (via `IConfigSection`), the binding remains decoupled from a particular configuration source and remains easy to test and reuse. + +## Notes +- This method relies on TOptions implementing `IConfigSection` and providing a static SectionName; otherwise the call will not compile. +- If the configuration does not contain the specified section, binding will yield default values; consider adding validators or fallbacks to enforce required settings. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md new file mode 100644 index 0000000..484d0a8 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md @@ -0,0 +1,37 @@ +# InfisicalOptions + +> **File:** `src/api/Gabriel.Core/Configuration/InfisicalOptions.cs` +> **Kind:** class + +```csharp +public class InfisicalOptions : IConfigSection +``` + + +InfisicalOptions represents the configuration section for Infisical integration. It groups the host, project, environment, secret path, and credentials needed to initialize the Infisical provider, and exposes a guard (IsConfigured) to indicate readiness. + +## Remarks +InfisicalOptions acts as a centralized holder that binds application configuration to the Infisical client. It separates non-sensitive defaults (ClientId lives in appsettings.json) from sensitive data (ClientSecret) that should come from secure channels like user-secrets or environment variables. The SectionName Infisical is the configuration key used by the hosting framework to bind values from configuration sources. This abstraction allows the app to boot cleanly in environments without Infisical access by omitting any of the four essentials. + +## Example +```csharp +var options = new InfisicalOptions +{ + Host = "https://infisical.example", + ProjectId = "proj-123", + Environment = "dev", + SecretPath = "/", + ClientId = "client-xyz", + ClientSecret = "super-secret" +}; + +if (options.IsConfigured) +{ + // Initialize and use the Infisical provider with options +} +``` + +## Notes +- The provider is activated only when IsConfigured is true (Host, ProjectId, ClientId, and ClientSecret must all be non-empty). If any are missing, Infisical integration is skipped to keep local development unblocked. +- ClientSecret is sensitive; avoid storing it in source control. Use user-secrets or environment variables (e.g. INFISICAL__CLIENTSECRET). +- Defaults: Environment defaults to "dev"; SecretPath defaults to "/"; TimeoutSeconds defaults to 15. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/JwtOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/JwtOptions.cs.md new file mode 100644 index 0000000..f8cd861 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/JwtOptions.cs.md @@ -0,0 +1,31 @@ +# JwtOptions + +> **File:** `src/api/Gabriel.Core/Configuration/JwtOptions.cs` +> **Kind:** class + +```csharp +public class JwtOptions : IConfigSection +``` + + +JwtOptions is a configuration model for JWT-related settings. It groups issuer, audience, signing key, and token lifetimes so token generation and inbound validation can be driven from a single typed source; use IsConfigured to guard against accidentally using an unset or too-short signing key. + +## Remarks +This abstraction encapsulates the JWT configuration so the signing/validation flow can rely on a typed object rather than scattered literals. The SectionName drives binding to the "Jwt" configuration section; the defaults for AccessTokenMinutes (15) and RefreshTokenDays (30) reflect a conservative, short-lived token strategy. IsConfigured performs a lightweight sanity check that a sufficiently long SigningKey has been provided, but it does not validate cryptographic correctness. + +## Example +```csharp +var options = new JwtOptions +{ + SigningKey = "abcdefghijklmnopqrstuvwxyz0123456789abcdef" // 32+ chars +}; + +if (options.IsConfigured) +{ + // proceed with token generation/validation +} +``` + +## Notes +- IsConfigured is a simple flag based solely on SigningKey length; it does not validate actual cryptographic material. +- Store SigningKey securely (e.g., user secrets or environment variables) and rotate it as part of your security hygiene. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMModel.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMModel.cs.md new file mode 100644 index 0000000..da8e75e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMModel.cs.md @@ -0,0 +1,19 @@ +# LLMModel + +> **File:** `src/api/Gabriel.Core/Configuration/LLMModel.cs` +> **Kind:** class + +```csharp +public class LLMModel +``` + + +Per-model metadata used by the application to represent a specific hosted LLM (identifier, context size, pricing and tool handling). Reach for this type when adding or configuring a provider model (different sizes / price points / capabilities) so the system can switch models via configuration rather than code and perform token accounting and cost estimates. + +## Remarks +This class centralizes the model-specific settings that other parts of the system rely on: token-budgeting (ContextWindowTokens and CompactThreshold), cost accounting (per‑million-token prices), activation/selection (IsActive as the default bootstrap model) and tool-call behavior (ToolMode). Pricing values are stored as USD per million tokens to match vendor price-page units; callers multiply by usage/1_000_000 to compute costs. CompactThreshold is nullable so a global AgentOptions.CompactThreshold can provide a fallback when a model-specific override isn't supplied. + +## Notes +- Prices (InputPricePerMTokens / OutputPricePerMTokens / CacheReadPricePerMTokens / CacheWritePricePerMTokens) are denominated per million tokens; a value of zero is treated as "free or unknown" by accounting code — validate non‑zero for active models if accurate cost-tracking is required. +- CompactThreshold is optional (double?). When null, code should fall back to AgentOptions.CompactThreshold; use the model override only when you need behavior different from the global agent setting. +- IsActive is a bootstrap/fallback default; user-level preferences (e.g. a per-user PreferredModel) override it at runtime. ToolMode defaults to Native; Emulated wraps providers via the GabrielToolBridge approach described in comments, and None disables tools for models that can't handle them. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs.md new file mode 100644 index 0000000..63577f1 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs.md @@ -0,0 +1,20 @@ +# LLMProviderOptions + +> **File:** `src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs` +> **Kind:** class + +```csharp +public abstract class LLMProviderOptions +``` + + +An abstract base class that provides the provider-wide configuration used by all LLM integrations. It consolidates transport, authentication, resilience, sampling, and the model catalog so concrete provider option classes can remain thin and focus on binding to their configuration section (SectionName). + +GetDefaultModel and FindModel(string) offer quick access to the active/default model and to locate a model by name from the Models collection. + +## Remarks +This abstraction isolates provider-level concerns from per-model details, enabling easy provider swapping without duplicating common settings. The Models catalog serves both the UI (model picker) and runtime model resolution; GetDefaultModel() supplies a sensible fallback by selecting the first active model, while FindModel(string) enables targeted retrieval by name. Concrete provider options typically derive from this base and simply set SectionName to guide configuration binding. + +## Notes +- Models is a mutable list initialized to an empty collection; changes after configuration binding may affect default-model resolution unless performed during initialization-time setup. +- ApiKey and BaseUrl are sensitive configuration values; supply them via secure sources (environment variables, user secrets, or protected vaults) rather than hard-coding defaults. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs.md new file mode 100644 index 0000000..9d66ad7 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs.md @@ -0,0 +1,19 @@ +# LocalDocsOptions + +> **File:** `src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs` +> **Kind:** class + +```csharp +public class LocalDocsOptions : IConfigSection +``` + + +LocalDocsOptions provides the configuration for Gabriel's on-disk self-docs source used by docs_list/docs_read. It exposes a toggle (Enabled) and a Path to locate markdown entries; the resolver tries an absolute existing path first, then searches upward from the application directory, and if nothing is found, falls back to the GitHub-backed source. + +## Remarks +LocalDocsOptions serves as a focused abstraction that decouples the docs resolution from the rest of the system and centralizes local-doc resolution behind a single configuration point. It enables easy toggling of the local source for development or testing while allowing the production pipeline to rely on GitHub content when local docs are unavailable. The SectionName binding (Tools:Docs:Local) ties this configuration into the broader options framework. + +## Notes +- Disabling local docs via Enabled=false skips the local path resolution entirely; the docs pipeline will consult GitHub sources only. +- Path can be absolute or relative; relative paths are resolved using the same lookup strategy and are normalized to the platform's path separator. +- If no matching path is found, the local source is treated as empty and a one-time warning is logged; the composite lookup then uses the GitHub-backed docs. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ModelSelection.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ModelSelection.cs.md new file mode 100644 index 0000000..ee4fe15 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ModelSelection.cs.md @@ -0,0 +1,42 @@ +# ModelSelection + +> **File:** `src/api/Gabriel.Core/Configuration/ModelSelection.cs` +> **Kind:** record + +```csharp +public sealed record ModelSelection( + string Provider, + string Name, + int ContextWindowTokens, + double? CompactThreshold, + ToolMode ToolMode) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Provider` | `string` | — | +| [`Name`](../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `ContextWindowTokens` | `int` | — | +| `CompactThreshold` | `double?` | — | +| [`ToolMode`](ToolMode.cs.md) | [`ToolMode`](ToolMode.cs.md) | — | + + +ModelSelection is an immutable record that encapsulates the active model context for a workflow. It carries the provider name, model identifier, contextual token budget, an optional compact-override threshold, and the tool-handling mode, enabling downstream components to operate with a consistent, shared understanding of which model is in use. + +It is produced by IModelCatalog.Resolve from the user's preferred provider and model (or a configuration-driven fallback) and threaded through the agent loop so that provider calls, the compact heuristic, the metrics endpoint, and the tool-emulation wrapper all agree on the active model. + +## Remarks +ModelSelection provides a clear boundary between decision and execution. By bundling all aspects of the chosen model into a single immutable value, it reduces drift between components and simplifies reasoning about which model is active. The record nature of the type also supports safe sharing across threads in concurrent agent workflows. + +## Example +```csharp +IModelCatalog catalog = GetCatalog(); +ModelSelection selection = catalog.Resolve("OpenAI", "gpt-4-32k"); +``` + +## Notes +- CompactThreshold is nullable; null indicates no explicit override and defers to the system's default heuristic. +- ContextWindowTokens conveys the per-model token budget for the active model and should be interpreted as the maximum tokens available for the model's context, not a strict usage limit. +- ToolMode influences how the tool-emulation wrapper behaves; mismatches between components can lead to inconsistent tool usage unless the same ModelSelection instance is propagated everywhere. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/PersonalityOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/PersonalityOptions.cs.md new file mode 100644 index 0000000..2db486e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/PersonalityOptions.cs.md @@ -0,0 +1,31 @@ +# PersonalityOptions + +> **File:** `src/api/Gabriel.Core/Configuration/PersonalityOptions.cs` +> **Kind:** class + +```csharp +public class PersonalityOptions : IConfigSection +``` + + +PersonalityOptions is a strongly-typed configuration class that defines the system persona and its streaming tempo. It controls the persona display name (Name) injected into prompts and few-shot blocks, and it tunes the simulated typing rhythm used when forwarding delta text through the stream (MinThinkingDelayMs/MaxThinkingDelayMs and MinCharsPerSecond/MaxCharsPerSecond). + +## Remarks +Implemented as [`IConfigSection`](IConfigSection.cs.md), it binds under the 'Personality' section to centralize persona-related settings. The default values provide a sensible out-of-the-box persona ('Gabriel') and modest streaming cadence, while allowing runtime tweaks without changing code. This abstraction keeps concerns about identity and user experience separate from composition logic, enabling consistent behavior across components that render or stream messages. + +## Example +```csharp +// Example: customize the persona and typing behavior +var options = new PersonalityOptions +{ + Name = "Nova", + MinThinkingDelayMs = 200, + MaxThinkingDelayMs = 800, + MinCharsPerSecond = 60, + MaxCharsPerSecond = 90 +}; +``` + +## Notes +- MinThinkingDelayMs should not exceed MaxThinkingDelayMs to avoid negative delays. +- MinCharsPerSecond should not exceed MaxCharsPerSecond to avoid unrealistic speeds. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs.md new file mode 100644 index 0000000..35526d7 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs.md @@ -0,0 +1,19 @@ +# ProjectFilesOptions + +> **File:** `src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs` +> **Kind:** class + +```csharp +public class ProjectFilesOptions : IConfigSection +``` + + +ProjectFilesOptions is a configuration container that defines how uploaded project files are stored and consumed. It binds to the SectionName property for the Projects:Files configuration section and exposes defaults for the root storage path, a hard file-size cap, a whitelist of allowed file extensions, and the set of content-type prefixes that ReadTextAsync will treat as text. Developers reference this type to customize per-environment behavior—such as changing the storage root, tightening or relaxing upload limits, expanding or narrowing supported file types, or adjusting which content should be opened as text for in-app previews—without scattering constants across the codebase. + +## Remarks +By encapsulating these settings in a single, strongly-typed options object, the system ensures consistent defaults and a single source of truth for project-file handling. It decouples file-system layout, validation rules, and read behavior from business logic, enabling easier testing and deployment-time overrides via configuration sources. + +## Notes +- The MaxFileBytes boundary prevents giant uploads from filling disk by rejecting larger files at the boundary, helping protect storage. +- The AllowedExtensions whitelist should reflect deployment needs; add or remove extensions as required and ensure the uploader enforces the same policy. +- The TextContentTypePrefixes determine which content types ReadTextAsync will treat as text; content outside this set can still be downloaded but will not be read as text for in-app previews. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs.md new file mode 100644 index 0000000..53ed25b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs.md @@ -0,0 +1,19 @@ +# TavilySearchOptions + +> **File:** `src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs` +> **Kind:** class + +```csharp +public class TavilySearchOptions : IConfigSection +``` + + +TavilySearchOptions represents a configuration section for the Tavily web search integration. Implemented as [`IConfigSection`](IConfigSection.cs.md), it exposes tunable settings such as the API base URL, the API key, the desired search depth, and the per-request timeout, plus a convenience IsConfigured flag that reports whether the API key is provided. + +## Remarks +By encapsulating Tavily settings behind the [`IConfigSection`](IConfigSection.cs.md), the application can bind this section from configuration sources (environment variables, secret stores, or appsettings) and inject a ready-to-use options instance wherever needed. The trailing slash requirement for BaseUrl ensures the relative "search" path resolves to the correct /search endpoint, avoiding URL-concatenation mistakes. The default "basic" value for SearchDepth keeps usage inexpensive for common scenarios, while "advanced" is available for more demanding queries. + +## Notes +- BaseUrl must end with a trailing slash to ensure the /search endpoint is resolved correctly when combining with relative paths. +- ApiKey defaults to an empty string, which disables the Tavily tool; callers should check IsConfigured before attempting API calls. +- SectionName is the configuration binding key; ensure your configuration sources use the same key to bind values properly. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ToolMode.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ToolMode.cs.md new file mode 100644 index 0000000..d1753ab --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Configuration/ToolMode.cs.md @@ -0,0 +1,43 @@ +# ToolMode + +> **File:** `src/api/Gabriel.Core/Configuration/ToolMode.cs` +> **Kind:** enum + +```csharp +public enum ToolMode +{ + Native = 0, + + Emulated = 1, + + None = 2, +} +``` + + +ToolMode controls how tool calls are delivered from the model to the agent/provider. It is configured per-model to enable a mixed catalog of backends and to let the system select the correct transport without exposing the underlying mechanism to the agent loop. The three values map to: Native—OpenAI/xAI-style structured tool_calls; Emulated—text with inline markers parsed into native events; None—no tool capability. + +## Remarks +By centralizing these transport strategies into a single enum, the architecture can support diverse providers while keeping the agent loop uniform. It communicates at runtime which tooling capabilities are available and isolates transport concerns from high-level behavior. Changes to ToolMode affect the transport path rather than how the model is invoked. + +## Example +```csharp +var mode = ToolMode.Native; +switch (mode) +{ + case ToolMode.Native: + // use native tool_calls path + break; + case ToolMode.Emulated: + // use emulated path + break; + case ToolMode.None: + // disable tool usage + break; +} +``` + +## Notes +- Native vs Emulated requires corresponding support on the provider side. +- Emulated mode relies on the GabrielToolBridge to reconstruct native events; without the bridge, tooling won't execute. +- None disables tool usage; ensure UI/flows reflect that to avoid user confusion. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/DependencyInjection.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/DependencyInjection.cs.md new file mode 100644 index 0000000..85a259b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/DependencyInjection.cs.md @@ -0,0 +1,24 @@ +# DependencyInjection + +> **File:** `src/api/Gabriel.Core/DependencyInjection.cs` +> **Kind:** class + +```csharp +public static class DependencyInjection +``` + + +Registers the core Gabriel.Core domain services into the application's dependency injection container. AddCoreServices wires the concrete implementations behind the core interfaces IChatService, IProjectService, and IMemoryService (ChatService, ProjectService, MemoryService) so they resolve with a scoped lifetime. Returning the IServiceCollection enables fluent composition during startup and keeps domain wiring separate from engine-level registrations. + +## Remarks +This extension centralizes the core domain registrations, reducing duplication and making it easier to swap implementations for testing. It ensures the core services are available within the request scope, promoting consistent lifetimes and predictable resolution across the application. By separating this wiring from the engine's AddEngineServices, the domain layer remains decoupled from engine concerns, allowing independent evolution and clearer layering. + +## Example +```csharp +// Typical startup wiring +services.AddCoreServices(); +``` + +## Notes +- All registrations are scoped; avoid storing scoped services in singleton components. +- Calling AddCoreServices multiple times is unnecessary; prefer a single, centralized startup call to configure core domain services. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Conversation.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Conversation.cs.md new file mode 100644 index 0000000..9f42754 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Conversation.cs.md @@ -0,0 +1,36 @@ +# Conversation + +> **File:** `src/api/Gabriel.Core/Entities/Conversation.cs` +> **Kind:** class + +```csharp +public class Conversation +``` + + +Represents a single chat thread owned by a user: it contains metadata (title, timestamps, avatar seed, optional pattern/palette overrides), a collection of Message entities, optional project association, a rolling summary and summarized-through pointer, and a serialized ConversationState. Use this class when creating, reading or manipulating conversations at the domain level (repositories and services work with Conversation instances rather than raw DTOs or primitive tuples). + +## Remarks +Conversation is the aggregate root for a chat thread. It owns the messages collection (exposed as an IReadOnlyList) and stores per-conversation behavioral and presentation state that the rest of the system uses to assemble provider context, visual identity, and conversational heuristics. The serialized StateJson holds evolving, non-queryable state (turn counts, mood flags, etc.) so the shape can change without schema migrations. Project-level overrides (PatternOverride/PaletteOverride) exist so project-shared sequences can use the project's skin while standalone/default-project chats fall back to the conversation's overrides. + +The class is designed for use with EF Core: it has a private parameterless constructor for materialization and exposes a factory (Create) that enforces required invariants (non-empty UserId and ProjectId) and initializes stable defaults (Id, AvatarSeed, Title). Repository code is expected to filter by UserId so users only see their own conversations. + +## Example +```csharp +// create a new conversation for a user and project; if title is null the Id is used +var userId = Guid.NewGuid(); +var projectId = Guid.NewGuid(); +var conv = Conversation.Create(userId, projectId); + +Console.WriteLine(conv.Id); // unique conversation Id +Console.WriteLine(conv.Title); // either supplied title or conv.Id.ToString() +Console.WriteLine(conv.AvatarSeed); // numeric seed used by the client to generate an avatar +Console.WriteLine(conv.Messages.Count); // empty at creation +``` + +## Notes +- Conversation.Create enforces that userId and projectId are non-empty Guids; callers must supply valid ids or catch ArgumentException. +- The ProjectId property is nullable on the entity for migration/backfill reasons, but newly created conversations (via Create) will have a non-null ProjectId. +- EF Core materialization requires the private parameterless constructor; do not remove it if using EF mapping. +- AvatarSeed is stored as a long in the database but is chosen so it stays within a JavaScript-safe uint32 range and round-trips cleanly through JSON. +- Conversation state is stored as JSON in StateJson and must be read/written through the provided helpers (GetState/SetState) to preserve evolving shape and compatibility. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/GabrielMode.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/GabrielMode.cs.md new file mode 100644 index 0000000..af1ab3c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/GabrielMode.cs.md @@ -0,0 +1,26 @@ +# GabrielMode + +> **File:** `src/api/Gabriel.Core/Entities/GabrielMode.cs` +> **Kind:** enum + +```csharp +public enum GabrielMode +{ + Chatty = 0, + Elaborative = 1, + Concise = 2, + Tutor = 3, + Critic = 4, +} +``` + + +GabrielMode is an enum that represents the behavioral bias applied to a single Conversation. It determines which Fragments.Mode* snippet is spliced into the per-turn system prompt, effectively reweighting the persona without rewriting it. The value is stored on Conversation.Mode as a nullable int, with null meaning the default Chatty mode. + +## Remarks +GabrielMode serves as a central, typed flag that coordinates how the system builds prompts for a conversation. By isolating the persona mode in this enum, the system can swap between modes (Chatty, Elaborative, Concise, Tutor, Critic) without altering the underlying prompt construction logic. The mapping from GabrielMode to the actual prompt fragments and keys is kept in the GabrielSystemPromptBuilder and related constants, so adding a new mode requires updating the builder, the Fragment set, and the prompt keys in concert. + +## Notes +- The numeric values must align with the Fragment and PromptKey mappings; a mismatch can yield incorrect prompt fragments. +- Null on Conversation.Mode yields Chatty default; explicit values override that. +- Adding a new mode requires coordinated edits across Gabriel.Core, Fragments, PromptKey, and GabrielSystemPromptBuilder. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntry.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntry.cs.md new file mode 100644 index 0000000..b5417be --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntry.cs.md @@ -0,0 +1,38 @@ +# MemoryEntry + +> **File:** `src/api/Gabriel.Core/Entities/MemoryEntry.cs` +> **Kind:** class + +```csharp +public class MemoryEntry +``` + + +Represents a single remembered item (a short named memory) owned by a user and optionally scoped to a project. Use MemoryEntry.Create to construct a valid instance and MemoryEntry.Update to perform idempotent edits; the type, name, description and body are required and normalized (trimmed) by the class. + +## Remarks +MemoryEntry is an immutable-from-the-outside data entity with private setters and a private constructor so callers must use the Create factory. It encodes the two-level scope model (global-to-user when ProjectId is null, or project-specific when ProjectId is set) and keeps CreatedAt and UpdatedAt timestamps so the UI or callers can show genesis and modification times. Validation for required fields (non-empty name/description/body and non-empty UserId) is enforced here; uniqueness of the Name within a (UserId, ProjectId) scope and kebab-case slug conventions are documented intentions but are not enforced by this type itself. + +## Example +```csharp +// Constructing a new memory entry (choose an appropriate MemoryEntryType in real code) +var userId = Guid.NewGuid(); +Guid? projectId = null; // or a specific project GUID +MemoryEntryType type = default; // replace with a real enum value + +var entry = MemoryEntry.Create( + userId: userId, + projectId: projectId, + type: type, + name: "save-password-guideline", + description: "Prefer passphrases over short passwords", + body: "Use long, memorable passphrases. **Why:** more entropy. **How to apply:** require at least 4 words."); + +// Update an existing entry (idempotent update of the body/description/type) +entry.Update(type, "Prefer passphrases over short passwords (updated)", "Updated guidance body..."); +``` + +## Notes +- Create will throw ArgumentException if userId is Guid.Empty or any of name/description/body are null/whitespace. +- Name/Description/Body are trimmed on assignment; the class does not validate kebab-case formatting or enforce uniqueness — that responsibility belongs to callers or the persistence layer. +- Update preserves CreatedAt and sets UpdatedAt to the current UTC time. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md new file mode 100644 index 0000000..4a1de5c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md @@ -0,0 +1,45 @@ +# MemoryEntryType + +> **File:** `src/api/Gabriel.Core/Entities/MemoryEntryType.cs` +> **Kind:** enum + +```csharp +public enum MemoryEntryType +{ + User = 0, + Feedback = 1, + Project = 2, + Reference = 3, +} +``` + + +MemoryEntryType is a simple enum used to tag memory entries with their semantic category—User, Feedback, Project, or Reference—so you can classify stored facts about people, guidance or corrections, ongoing work context, or external pointers in a strongly-typed way instead of using ad-hoc strings. Use it when recording a memory entry to drive tailored explanations, filtering, or routing in downstream components. + +## Remarks +MemoryEntryType centralizes labeling for the memory system, enabling consistent handling across presentation, analytics, and policy decisions. It keeps memory storage decoupled from how it's consumed; components can switch on type to decide tone, visibility, or retention rules. The explicit numeric values ensure stable persistence and interoperability with any existing data; avoid reordering values without migration. + +## Example +```csharp +MemoryEntryType t = MemoryEntryType.Project; + +switch (t) +{ + case MemoryEntryType.User: + // tailor explanations for a user persona + break; + case MemoryEntryType.Feedback: + // fold user feedback into behavior updates + break; + case MemoryEntryType.Project: + // associate with a project or work item + break; + case MemoryEntryType.Reference: + // link to external documentation or dashboards + break; +} +``` + +## Notes +- Changing the underlying numeric values would break persisted data; prefer adding new values and migrating data rather than reordering existing ones. +- If introducing a new category, ensure downstream filters, UI labels, and documentation are updated accordingly. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Message.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Message.cs.md new file mode 100644 index 0000000..687a4fc --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Message.cs.md @@ -0,0 +1,50 @@ +# Message + +> **File:** `src/api/Gabriel.Core/Entities/Message.cs` +> **Kind:** class + +```csharp +public class Message +``` + + +Represents a single turn in a conversation, carrying the role (user, assistant, system, tool), payload and any tool-call or reasoning metadata. Construction and mutation are intentionally assembly-restricted: the constructor is private and the factory/mutators are internal, so callers outside the defining assembly can read message data but not create or change messages directly. Use Message.Create(...) (from the same assembly) to build a message where per-role validation is enforced. + +## Remarks +This type centralizes all data the system needs to persist and replay a conversation turn: the core text content, references to tool calls (ToolCallId and the raw ToolCallsJson), an optional separate reasoning stream, timestamps, and variant-grouping semantics used for regenerated assistant responses. VariantGroupId groups messages that are different candidate responses for the same assistant turn; for non-regenerated messages the group id equals the message id, and exactly one message in a group has IsActiveVariant = true. + +ReasoningContent is stored separately from Content so UI and persistence can show an assistant's "thinking" stream without polluting the canonical answer body. ToolCallsJson is stored verbatim (the raw JSON array) so the system can replay the assistant's requested tool calls exactly as they appeared on the wire. + +## Example +```csharp +var conversationId = Guid.NewGuid(); + +// Create a user message (requires non-whitespace content) +var userMessage = Message.Create(conversationId, MessageRole.User, "What's the weather?"); + +// Create an assistant message that contains content +var assistantMessage = Message.Create( + conversationId, + MessageRole.Assistant, + "The weather is sunny."); + +// Create an assistant message that issued tool calls (ToolCallsJson is the raw JSON array) +var assistantWithToolCalls = Message.Create( + conversationId, + MessageRole.Assistant, + content: null, + toolCallsJson: "[{\"name\":\"search\",\"arguments\":{}}]" +); + +// Mark variants active/inactive (internal; shown here for callers in the same assembly) +assistantMessage.MarkInactiveVariant(); +assistantWithToolCalls.MarkActiveVariant(); + +// Attach a captured reasoning stream (internal setter) +assistantMessage.SetReasoningContent("Step 1: check cache; Step 2: query API..."); +``` + +## Notes +- Per-role validation is subtle: User/System messages reject content that is null/empty/whitespace, while Assistant messages accept either non-whitespace content or a non-empty ToolCallsJson. Tool messages require a non-empty ToolCallId and require content to be non-null (empty string is allowed). +- VariantGroupId defaults to the message's own Id when no variant group is supplied; pass a specific variantGroupId to group regenerated assistant messages together. +- SetReasoningContent normalizes empty strings to null (empty or null reasoning becomes null in the stored property). diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MessageRole.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MessageRole.cs.md new file mode 100644 index 0000000..b8ebd8d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MessageRole.cs.md @@ -0,0 +1,47 @@ +# MessageRole + +> **File:** `src/api/Gabriel.Core/Entities/MessageRole.cs` +> **Kind:** enum + +```csharp +public enum MessageRole +{ + System = 0, + User = 1, + Assistant = 2, + Tool = 3, +} +``` + + +Represents the role of a chat message within the conversation workflow. It enables downstream components to distinguish system prompts, user inputs, assistant replies, and tool-observation messages that carry the results of tool invocations. + +## Remarks + +This enumeration provides a stable categorization of message origins, which simplifies routing, rendering, and audit trails across the chat pipeline. The Tool member specifically denotes an observation message that carries the outcome of a tool invocation; the related ToolCallId on the message links this observation to the corresponding assistant tool_call, preserving traceability across turns. Because the numeric values are used for serialization, be cautious when evolving the enum (adding, removing, or reordering members) to avoid breaking persisted data or cross-version compatibility. + +## Example + +```csharp +// Example usage: dispatching handling by message role +switch (role) +{ + case MessageRole.System: + // system-level instructions + break; + case MessageRole.User: + // user-provided input + break; + case MessageRole.Assistant: + // assistant response + break; + case MessageRole.Tool: + // observation derived from a tool invocation + break; +} +``` + +## Notes + +- If the enum is serialized as an integer, changing member order or values can break existing stored data; treat the numeric mapping as part of a stable contract. +- The Tool branch is meaningful only when you actually generate tool observations; otherwise, this value may remain unused. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MetricEntry.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MetricEntry.cs.md new file mode 100644 index 0000000..7de0151 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/MetricEntry.cs.md @@ -0,0 +1,24 @@ +# MetricEntry + +> **File:** `src/api/Gabriel.Core/Entities/MetricEntry.cs` +> **Kind:** class + +```csharp +public class MetricEntry +``` + + +Represents a single persisted telemetry entry used by all time-series subsystems. It stores a stable System identifier and a free-form Metric JSON payload, enabling new subsystems to emit data without migrations. CreatedAt is auto-stamped on creation and Id is a Guid; construction is private to enforce invariants via the Create factory. + +## Remarks +MetricEntry isolates the event source (System) from its payload (Metric), supporting a schema-less telemetry store while preserving a traceable identity. The Create factory enforces basic invariants (non-empty system and metric) and trims whitespace to ensure consistent data at write time. Public getters with private setters make instances effectively immutable after creation, simplifying correctness in concurrent or persisted contexts. + +## Example +```csharp +var entry = MetricEntry.Create("web_search.tavily", "{\"latencyMs\":42}"); +``` + +## Notes +- The Metric field holds arbitrary JSON as a string; downstream code must parse it as needed, since there is no enforced schema here. +- The Create method validates inputs and trims the System name; invalid calls throw ArgumentException. +- CreatedAt is fixed at creation time and Id is generated once; these fields are immutable after construction. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Project.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Project.cs.md new file mode 100644 index 0000000..4eb6216 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/Project.cs.md @@ -0,0 +1,36 @@ +# Project + +> **File:** `src/api/Gabriel.Core/Entities/Project.cs` +> **Kind:** class + +```csharp +public class Project +``` + + +Represents a user-owned container that groups conversations, files, and an optional per-project system prompt (a persona override). Reach for Project when you need a stable project scope — for grouping conversations, sharing files and a shared avatar/visual identity — or to create the special lazily-created Default bucket for a user's standalone conversations. + +## Remarks +Project encapsulates the project-level identity used by clients and the system: it holds the stable AvatarSeed that drives the project-wide Gabriel Sequence, optional Pattern/Palette overrides that pin a visual skin, and a SystemPrompt that is prepended to an agent's per-turn history. The Default project is a special, lazily-created bucket (IsDefault) that still carries a seed but is treated by clients as per-conversation/standalone. Project owns a collection of ProjectFile instances (exposed as a read-only list) and is referenced by Conversation.ProjectId when conversations belong to a project. + +## Example +```csharp +// Create a new project for a user +var project = Project.Create(ownerUserId: userId, name: "Research Notes", description: "Notes and files for Q3" ); + +// Pin a specific skin (catalog identifiers should already be validated by the caller) +project.SetSkin(pattern: "plasma", palette: "heat"); + +// If you want a new random avatar seed for the project +project.RerollAvatar(); + +// Read files (immutable view) +var files = project.Files; +``` + +## Notes +- SetSkin does not validate pattern/palette against any catalog; callers must pass known, lower-case identifiers or null. Empty or whitespace strings are treated as null (clears the override). +- The Default project (IsDefault == true) still has an AvatarSeed but clients typically ignore it and render per-conversation sequences instead. +- AvatarSeed values are generated in the range 1..2^32-1 so they round-trip safely through JSON Number types. +- Files is exposed as an IReadOnlyList backed by a private list; modify via the entity's methods (not directly) if such methods exist elsewhere. +- Project.Create validates that ownerUserId is not Guid.Empty and that name is non-empty; it throws ArgumentException for invalid inputs. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/ProjectFile.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/ProjectFile.cs.md new file mode 100644 index 0000000..0815ea3 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Entities/ProjectFile.cs.md @@ -0,0 +1,32 @@ +# ProjectFile + +> **File:** `src/api/Gabriel.Core/Entities/ProjectFile.cs` +> **Kind:** class + +```csharp +public class ProjectFile +``` + + +ProjectFile is the metadata record for a file uploaded to a project, capturing its identity, project affiliation, display name, relative path, size, content type, and upload timestamp. Its static Create factory validates inputs, trims and normalizes values, and defaults the content type when omitted to ensure consistently constructed, safe metadata before persistence. + +## Remarks + +These properties establish a stable contract between a file's binary data and its descriptive metadata, enabling services to link files to their projects without leaking path or normalization concerns. The class uses a private setter pattern, so metadata remains immutable from outside code after creation, with changes routed through controlled construction. RelativePath normalization to forward slashes helps maintain a uniform repository layout, while the service layer enforces any deeper path traversal protections. In typical usage, ProjectFile instances are produced when a file is uploaded and then stored alongside its ProjectId reference. + +## Example + +```csharp +// Common usage +var projectId = Guid.NewGuid(); +var file = ProjectFile.Create(projectId, "Report.pdf", "reports/2026/Report.pdf", 2048, "application/pdf"); + +// Accessing metadata (ids are generated on creation) +Console.WriteLine(file.Id); // generated unique id +Console.WriteLine(file.ProjectId); // ties the file to its project +``` + +## Notes + +- Create enforces invariants by throwing ArgumentException for invalid inputs (empty project id, empty name, empty relative path, negative size). +- RelativePath normalization only replaces separators; path traversal protection is enforced at the service layer. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/DomainException.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/DomainException.cs.md new file mode 100644 index 0000000..a627bf7 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/DomainException.cs.md @@ -0,0 +1,18 @@ +# DomainException + +> **File:** `src/api/Gabriel.Core/Exceptions/DomainException.cs` +> **Kind:** class + +```csharp +public class DomainException : Exception +``` + + +DomainException is the base class for domain-rule violations. Use it to represent business-rule violations that result from a client's request so the API can respond with a 400 Bad Request instead of a generic server error; when thrown, it is caught by the global exception handler and translated into the 400 response, providing a consistent, client-friendly error surface for domain errors. + +## Remarks +DomainException creates a clean boundary between domain failures and transport concerns. By deriving from Exception and offering a simple message-based constructor, it can be thrown from domain logic and caught at the API boundary to produce consistent feedback to clients. It helps distinguish domain violations from other failures and keeps domain rules centralized in one exception type. + +## Notes +- This base class currently provides only a message constructor; if you need inner exceptions or error codes, add additional constructors or properties. +- Ensure your global exception handler maps DomainException to HTTP 400; without the mapping, clients may receive a 500 Internal Server Error. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/NotFoundException.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/NotFoundException.cs.md new file mode 100644 index 0000000..db2ff50 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Exceptions/NotFoundException.cs.md @@ -0,0 +1,25 @@ +# NotFoundException + +> **File:** `src/api/Gabriel.Core/Exceptions/NotFoundException.cs` +> **Kind:** class + +```csharp +public class NotFoundException : Exception +``` + + +NotFoundException signals that a requested aggregate could not be located. It carries the name of the missing resource and the key used to locate it, enabling precise diagnostics and consistent not-found handling across the domain and API layers (for example, mapping to HTTP 404 responses). + +## Remarks +NotFoundException encapsulates a not-found scenario as a concrete domain exception rather than a loose error. The Resource and Key properties provide actionable context to error handlers and user-facing responses without exposing implementation details. This pattern supports centralized not-found handling and consistent user messages across services. + +## Example +```csharp +Guid id = Guid.NewGuid(); +throw new NotFoundException("ResourceName", id); +``` + +## Notes +- Be mindful that Key is of type object; its ToString() is used in the message, so ensure it yields meaningful identifiers. +- Catch NotFoundException at boundaries where a missing resource should translate to a 404, rather than catching a generic Exception. +- The exception is intended for domain-level not-found scenarios; avoid misusing for unrelated missing data. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/ICurrentUser.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/ICurrentUser.cs.md new file mode 100644 index 0000000..4629f41 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/ICurrentUser.cs.md @@ -0,0 +1,19 @@ +# ICurrentUser + +> **File:** `src/api/Gabriel.Core/Identity/ICurrentUser.cs` +> **Kind:** interface + +```csharp +public interface ICurrentUser +``` + + +ICurrentUser provides a per-request view of the active user that core components can rely on without touching HTTP plumbing. It exposes three pieces of information: an optional UserId, a boolean IsAuthenticated, and an optional Email, enabling services to make authorization and personalization decisions in a testable, framework-agnostic way. + +## Remarks +Abstracting current user data behind ICurrentUser decouples business logic from HttpContext and claims plumbing. The API layer is responsible for populating this interface from HttpContext.User, while the core remains agnostic of web concerns, making it easier to test and reuse in non-HTTP contexts. It sits alongside other identity concerns and is commonly consumed by services that enforce access checks, auditing, or personalization. + +## Notes +- Always verify IsAuthenticated and UserId.HasValue before using UserId. +- Treat Email as optional; don't rely on it for authentication decisions. +- Ensure the ICurrentUser implementation is registered with a per-request lifetime so it reflects the current HTTP context. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md new file mode 100644 index 0000000..0b8d175 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md @@ -0,0 +1,75 @@ +# IJwtTokenService.cs + +> **Source:** `src/api/Gabriel.Core/Identity/IJwtTokenService.cs` + +## Contents + +- [IJwtTokenService](#ijwttokenservice) +- [TokenPair](#tokenpair) + +--- + +## IJwtTokenService +> **File:** `src/api/Gabriel.Core/Identity/IJwtTokenService.cs` +> **Kind:** interface + +```csharp +public interface IJwtTokenService +``` + + +IJwtTokenService provides a contract for issuing and managing JWT-based tokens: access tokens are stateless and validated by signature, while refresh tokens are server-side state that can be revoked. It issues an initial TokenPair for authenticated users, refreshes the pair with rotation and theft detection, and supports revoking single or all tokens across devices. + +## Remarks +This abstraction separates stateless access tokens from stateful refresh tokens, enabling revocation and response to credential compromise without regenerating access keys. The refresh workflow rotates the refresh token on every use and, upon detecting token reuse, revokes the entire token family for the user to mitigate token theft. The design supports per-user session control across devices, including signing out a single device or signing out everywhere. + +## Notes +- Token rotation relies on durable storage and atomic operations to prevent races when multiple clients refresh concurrently. +- RefreshAsync invalidates previously issued refresh tokens; treat old tokens as unusable after a successful refresh. +- IssueAsync requires the caller to be authenticated; userId and email are embedded into the issued TokenPair for binding to the user. + +--- + +## TokenPair +> **File:** `src/api/Gabriel.Core/Identity/IJwtTokenService.cs` +> **Kind:** record + +```csharp +public record TokenPair( + string AccessToken, + DateTimeOffset AccessExpiresAt, + string RefreshToken, + DateTimeOffset RefreshExpiresAt) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `AccessToken` | `string` | — | +| `AccessExpiresAt` | `DateTimeOffset` | — | +| [`RefreshToken`](RefreshToken.cs.md) | `string` | — | +| `RefreshExpiresAt` | `DateTimeOffset` | — | + + +TokenPair is a lightweight value object that bundles an access token together with its expiration and a refresh token with its expiration. It serves as a single, immutable carrier for the complete token set commonly returned by authentication flows, enabling consumers to work with both tokens and their lifetimes in one place instead of juggling separate values. + +## Remarks +TokenPair is declared as a record, which provides value-based equality and immutability. This makes it safe to pass through layers, cache, or reuse as a key without worrying about accidental mutation. By pairing the access and refresh tokens with their corresponding expiration times, it centralizes expiry logic and simplifies refresh scenarios: you always know when each token expires and can coordinate renewal accordingly. In practice, this type often represents the payload returned by a token service during login or token refresh operations, and is consumed by clients and services that need both tokens together. + +## Example +```csharp +var tokenPair = new TokenPair( + AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + AccessExpiresAt: DateTimeOffset.UtcNow.AddMinutes(15), + RefreshToken: "def50200ab34...", + RefreshExpiresAt: DateTimeOffset.UtcNow.AddDays(7) +); +``` + +## Notes +- DateTimeOffset is used for expiry values to preserve offset information; ensure time sources are consistent (UTC is recommended) when comparing or validating expirations. +- TokenPair being a record means it is immutable; to derive a modified instance, use the with-expression (e.g., tokenPair with { AccessExpiresAt = newTime }). +- When serializing to JSON, DateTimeOffset values include offset information, so consumers should handle offset-aware timestamps accordingly. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs.md new file mode 100644 index 0000000..561f328 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs.md @@ -0,0 +1,19 @@ +# IRefreshTokenStore + +> **File:** `src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs` +> **Kind:** interface + +```csharp +public interface IRefreshTokenStore +``` + + +Persistence boundary for refresh tokens; all writes go through IUnitOfWork to guarantee atomic token rotation (mark-old-replaced plus insert-new). Reach for this interface when you need to locate, add, or bulk-revoke tokens for a user as part of issuance, refresh, or theft-detection workflows. + +## Remarks +This interface isolates token persistence from the business logic of token issuance and rotation, enabling swap-in persistence implementations without changing callers. It collaborates with JwtTokenService to support issuing, refreshing, and revoking tokens, and relies on IUnitOfWork to ensure that refresh-token rotation remains atomic during updates. + +## Notes +- FindByHashAsync returns null if the token hash is not found; callers must handle the null case. +- Cancellation tokens are accepted; ensure you propagate ct through to the underlying data store. +- Bulk revocation affects all active tokens for a user; use in theft-detection flows and on logout. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IUserPreferences.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IUserPreferences.cs.md new file mode 100644 index 0000000..626fb4e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/IUserPreferences.cs.md @@ -0,0 +1,84 @@ +# IUserPreferences.cs + +> **Source:** `src/api/Gabriel.Core/Identity/IUserPreferences.cs` + +## Contents + +- [IUserPreferences](#iuserpreferences) +- [UserPreferences](#userpreferences) + +--- + +## IUserPreferences +> **File:** `src/api/Gabriel.Core/Identity/IUserPreferences.cs` +> **Kind:** interface + +```csharp +public interface IUserPreferences +``` + + +IUserPreferences exposes the per-user preferences surface used by the Gabriel core infrastructure. It provides two operations: GetAsync to read the current user's preferences and SetPreferredModelAsync to update the user's preferred provider and model — with null values clearing to configured defaults. The interface is designed to be consumed via Dependency Injection by per-request services (such as AgentService) so it can leverage the active ICurrentUser and DbContext within a single request. + +## Remarks +Purpose-built to isolate user-level configuration, this interface decouples business logic from the specifics of how preferences are stored or retrieved. Implementations can hydrate defaults from config, persist changes to a DbContext-backed store, and expose a consistent API for reading and updating the active user's settings. The per-request scope ensures that preferences reflect the current user context during a single request. + +## Example +```csharp +// Fetch current preferences for the active user +var prefs = await userPreferences.GetAsync(ct); + +// Update the preferred model/provider for the user +await userPreferences.SetPreferredModelAsync("providerX", "modelY", ct); + +// Reset to configuration defaults +await userPreferences.SetPreferredModelAsync(null, null, ct); +``` + +## Notes +- Pass null for provider or model to clear that field and revert to the config default. +- GetAsync returns the current user's preferences for the active request and will reflect defaults if no preferences have been set yet. + +--- + +## UserPreferences +> **File:** `src/api/Gabriel.Core/Identity/IUserPreferences.cs` +> **Kind:** record + +```csharp +public sealed record UserPreferences(string? PreferredProvider, string? PreferredModel) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `PreferredProvider` | `string?` | — | +| `PreferredModel` | `string?` | — | + + +UserPreferences is an immutable, per-user value object that carries two optional choices: PreferredProvider and PreferredModel. It is read and written outside the JWT to avoid bloating claims; the current scope is model selection, but the record shape makes it straightforward to add additional preferences later with a single-line change. + +## Remarks + +- This abstraction separates per-user preferences from identity claims and token payloads, reducing token size and decoupling concerns around authentication from user configuration. +- The use of a C# record provides value-based equality and immutability, and the nullable string fields (string?) allow representing an unspecified preference while preserving explicit values when provided. +- Extending the set of preferences is additive and low-risk; future fields can be added with minimal disruption to existing consumers. + +## Example + +```csharp +// Typical usage with both preferences provided +var prefs = new UserPreferences("OpenAI", "gpt-4"); + +// Example with a missing provider, but with a model preference +var prefsPartial = new UserPreferences(null, "gpt-3.5-turbo"); +``` + +## Notes + +- Nullability semantics: null in either field means the preference is not specified; callers should handle nulls explicitly. +- Because this is a positional record, adding new fields in the future requires updating constructor call sites or introducing defaulted overloads; plan for evolution when expanding the shape. +- These preferences are outside the JWT, so ensure proper access control and secure persistence wherever they are stored. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md new file mode 100644 index 0000000..bb9c049 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md @@ -0,0 +1,37 @@ +# RefreshToken + +> **File:** `src/api/Gabriel.Core/Identity/RefreshToken.cs` +> **Kind:** class + +```csharp +public class RefreshToken +``` + + +Represents a server-side refresh token record that stores only the SHA‑256 hash of the token (the plaintext is returned to the client only at issuance). Use this type to persist token metadata, evaluate token state (active / revoked / expired), and perform revocation or rotation operations via Revoke and MarkReplacedBy. Create(...) is the intended factory to ensure required fields are provided and the expiry is set. + +## Remarks +This class encapsulates the minimal state and behaviors needed for refresh-token rotation and revocation. It is designed so that tokens are rotated on each refresh: the previous token can be marked as replaced (ReplacedByTokenId) and revoked; presenting a replaced token is treated as a theft signal by higher-level services. Storing TokenHash (rather than plaintext) reduces risk from a database leak; the object tracks creation, expiry and revocation timestamps to make revocation decisions deterministic. + +## Example +```csharp +var userId = Guid.NewGuid(); +var tokenHash = "sha256-hash-of-the-token"; +// Create a token that lives 30 days +var refreshToken = RefreshToken.Create(userId, tokenHash, TimeSpan.FromDays(30)); + +// Check state +var active = refreshToken.IsActive; // true immediately after creation (assuming clock) + +// Revoke explicitly (idempotent) +refreshToken.Revoke(); + +// Mark as replaced by a newly issued token (also sets RevokedAt if not already set) +var replacementId = Guid.NewGuid(); +refreshToken.MarkReplacedBy(replacementId); +``` + +## Notes +- Create throws ArgumentException if userId is Guid.Empty or tokenHash is null/whitespace; callers must validate or handle the exception. +- Revoke is idempotent: calling it multiple times only sets RevokedAt once. MarkReplacedBy also sets RevokedAt when marking a replacement. +- Time checks (IsActive, IsExpired) use DateTimeOffset.UtcNow; tests or distributed callers should account for UTC-based timing and potential clock skew. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Personality/ConversationState.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Personality/ConversationState.cs.md new file mode 100644 index 0000000..7fab9da --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Personality/ConversationState.cs.md @@ -0,0 +1,51 @@ +# ConversationState + +> **File:** `src/api/Gabriel.Core/Personality/ConversationState.cs` +> **Kind:** record + +```csharp +public sealed record ConversationState +{ + public int TurnCount { get; init; } + public Mood Mood { get; init; } = Mood.Neutral; + + public float AvgUserTokenCount { get; init; } + public int LastUserTokenCount { get; init; } + + public IReadOnlyList RecentTopics { get; init; } = Array.Empty(); + public DateTimeOffset LastMessageAt { get; init; } + public int ConsecutiveShortMessages { get; init; } + + public bool UserUsesEmoji { get; init; } + public bool UserUsesLowercase { get; init; } + + public bool UserAskedForDetail { get; init; } + + public static ConversationState Initial() => new() + { + LastMessageAt = DateTimeOffset.UtcNow, + }; +} +``` + + +ConversationState is an immutable, per-conversation snapshot of behavioral state used by the chat engine to influence how prompts are built and how responses are post-processed. It is maintained by IConversationStateUpdater and read by ISystemPromptBuilder and IResponsePostProcessor, and persisted as JSON on Conversation.StateJson to avoid a separate EF table. This record underpins the planned emotion engine (Phase 10) by exposing Mood and user-style flags that will feed avatar reactions as the system evolves. + +## Remarks +ConversationState serves as a centralized, serializable context that links turn-by-turn interaction concerns (token usage, topics, and user signals) with the system's prompting and post-processing decisions. Its immutable design and a dedicated updater keep behavioral changes explicit and thread-safe, while JSON persistence keeps the storage surface minimal and forward-compatible with evolving persona controls (Mood and flags). + +## Example +```csharp +var s = ConversationState.Initial(); +var next = s with +{ + TurnCount = s.TurnCount + 1, + UserAskedForDetail = true, + RecentTopics = new[] { "greetings", "getting-started" } +}; +``` + +## Notes +- The type is immutable; updates must produce a new instance (e.g., via the with-expression) or be applied through a dedicated updater. +- StateJson persistence means changes to the conversation state need to be serialized back into Conversation.StateJson to remain in sync with the rest of the system. +- Default values (Mood.Neutral, Empty RecentTopics) apply when a new state is created; deserialization may restore previously saved values, so be mindful of potential nulls or missing fields if JSON evolves. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Personality/Mood.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Personality/Mood.cs.md new file mode 100644 index 0000000..9a8c2b7 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Personality/Mood.cs.md @@ -0,0 +1,27 @@ +# Mood + +> **File:** `src/api/Gabriel.Core/Personality/Mood.cs` +> **Kind:** enum + +```csharp +public enum Mood +{ + Neutral, + Playful, + Venting, + Serious, + Curious, + LowEnergy, +} +``` + + +Mood is an enumeration of discrete engagement styles that the heuristic state updater can assign and that the ISystemPromptBuilder uses to steer the tone of each system prompt. Use Mood to select a conversational mood (Neutral, Playful, Venting, Serious, Curious, LowEnergy) instead of embedding tone logic directly in prompts; the builder will normalize the names to lowercase during injection. + +## Remarks +Mood serves as a lightweight, typed vocabulary for the system's conversational tone. It decouples mood selection from prompt construction, letting the prompt pipeline apply a consistent style by mapping each enum value to a lowercased token. Since the injection step lowercases the names, rely on the Mood value rather than a hard-coded string if you interact with the prompt directly. + +## Notes +- Do not rely on the enum's numeric discriminants; use the named constants. +- The prompt engine lowercases mood names when injecting into prompts, so casing here doesn't dictate the token seen in the prompt. +- Adding new moods should be reflected across all consumers that enumerate or switch on Mood. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md new file mode 100644 index 0000000..f8cc537 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md @@ -0,0 +1,19 @@ +# IConversationRepository + +> **File:** `src/api/Gabriel.Core/Repositories/IConversationRepository.cs` +> **Kind:** interface + +```csharp +public interface IConversationRepository +``` + + +IConversationRepository provides a tenant-aware data access contract for Conversation aggregates and their associated Message entities. It exposes read methods that require a userId to ensure a user can only retrieve their own conversations, and write methods that persist changes while respecting the ownership stored on Conversation. Use this interface when you need to fetch a conversation (with or without its messages), list a user’s conversations with an optional project filter, or perform create/update/delete operations on conversations and their messages. The explicit separation between AddMessage and RemoveMessages reflects deliberate control over message lifecycle and EF change-tracking, ensuring deletions are clearly expressed rather than inferred from navigation state. + +## Remarks +This interface acts as an abstraction over the persistence layer (likely EF Core) and enforces per-user isolation by requiring userId for reads. It coordinates the Conversation and Message entities, anchoring ownership to the Conversation’s UserId and ensuring that write operations surface intent clearly (e.g., removing messages explicitly rather than relying on cascade behavior). By providing both single-conversation reads and a bulk-list capability, it supports both targeted queries and user-scoped overviews while remaining decoupled from any particular data-access implementation. + +## Notes +- Be mindful that GetByIdAsync may return null; always handle the possibility of a missing conversation for the given user. +- AddAsync assumes the Conversation entity contains its ownership (UserId) prior to persistence; ensure this before calling AddAsync. +- RemoveMessages is explicit to guarantee EF’s change tracker marks deletions reliably, avoiding fragile orphan-removal logic across EF configurations. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md new file mode 100644 index 0000000..94fdcc0 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md @@ -0,0 +1,19 @@ +# IMemoryRepository + +> **File:** `src/api/Gabriel.Core/Repositories/IMemoryRepository.cs` +> **Kind:** interface + +```csharp +public interface IMemoryRepository +``` + + +An abstraction for storing and querying MemoryEntry objects with user- and project-scoped visibility. Use this interface when code needs to list the memories visible to a user or agent, retrieve a specific memory, or create/update/remove memories without tying callers to a particular persistence technology. + +## Remarks +This repository surface is scope-aware: methods accept a userId plus an optional projectId to target either user-scope (projectId == null) or a project-specific scope. ListForAgentAsync is a convenience that returns the set of entries an agent should "see" for a conversation — the user's global memories plus, when a projectId is supplied, that project's memories. FindByNameAsync looks up a memory by (userId, projectId, name); the implementation and callers treat the name/slug as unique within the chosen scope, which is why callers use it to decide between creating a new entry or updating an existing one. + +## Notes +- Pass projectId = null to target the user scope; ListForAgentAsync merges user-scope and project-scope entries when a projectId is provided. +- FindByNameAsync relies on the name being unique within (userId, projectId) so callers (for example, an agent's memory-save logic) can choose create vs. update based on its result. +- AddAsync is asynchronous while Update and Remove are synchronous and the interface does not expose an explicit Save/Commit method; implementations will commonly rely on an ambient unit-of-work or change-tracking context to persist Update/Remove changes — make sure your implementation’s lifecycle persists those modifications as expected. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md new file mode 100644 index 0000000..ddc5811 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md @@ -0,0 +1,20 @@ +# IMetricRepository + +> **File:** `src/api/Gabriel.Core/Repositories/IMetricRepository.cs` +> **Kind:** interface + +```csharp +public interface IMetricRepository +``` + + +IMetricRepository defines the storage contract for the generic metric event log. It exposes persistence and read operations used by diagnostics surfaces, while the writer side is wired through IMetricRecorder in Engine so subsystems don't talk to EF directly. Implementations persist metric entries serialized to JSON into a schema-less store and offer targeted read paths for diagnostics: per-system recent events via RecentAsync, and cross-system queries via RecentByPrefixAsync. A maintenance operation is provided to purge old data via DeleteOlderThanAsync. + +Use AddAsync to persist a single metric entry after serializing the payload to JSON; use RecentAsync to retrieve the most recent entries for a specific system, and use RecentByPrefixAsync to fetch recent events across systems that share a common prefix; use DeleteOlderThanAsync in background maintenance to remove stale data. This interface abstracts storage concerns away from consumers, allowing the diagnostic endpoints and tooling to evolve without tying them to a particular ORM or data-store. + +## Remarks +IMetricRepository abstracts persistence from the rest of the telemetry pipeline, enabling the read paths used by diagnostics to be backed by any suitable storage. It decouples the web/diagnostics endpoints from the writer logic (MetricRecorder) and from EF concerns, promoting testability and flexibility in choosing or evolving the data store. + +## Notes +- The limit semantics in RecentByPrefixAsync apply to the total result set across all matched systems, not per-system; UI layers should group entries by system after retrieval. +- DeleteOlderThanAsync is a hard delete intended for maintenance tasks and is not exposed via HTTP; use with care to avoid unintended data loss. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md new file mode 100644 index 0000000..049de45 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md @@ -0,0 +1,31 @@ +# IProjectRepository + +> **File:** `src/api/Gabriel.Core/Repositories/IProjectRepository.cs` +> **Kind:** interface + +```csharp +public interface IProjectRepository +``` + + +IProjectRepository defines the data-access contract for Project aggregates. All operations are scoped by owner to enforce per-user data isolation, and most methods are asynchronous to reflect I/O-bound persistence. The repository exposes retrieval methods to fetch by id (with or without related files), list all projects for an owner, and locate a first project by name, alongside mutation methods to add, update, or remove projects, and a bulk operation to reassign orphan conversations to a specified project. This abstraction lets domain logic interact with Project data without depending on a particular storage implementation. + +## Remarks +Repository abstractions like this separate domain logic from storage concerns and enforce per-owner data boundaries, supporting multi-tenant scenarios. The GetByIdWithFilesAsync variant demonstrates the ability to eagerly load related data when needed, while the AssignOrphanConversationsAsync method coordinates a cross-entity operation to ensure conversations are properly associated with a project during onboarding or maintenance. + +## Example +```csharp +// Get a project for an owner +var project = await repository.GetByIdAsync(projectId, ownerId, ct); + +// List all projects for an owner +var all = await repository.ListAsync(ownerId, ct); + +// Reassign orphan conversations to a specific project +int updated = await repository.AssignOrphanConversationsAsync(ownerId, projectId, ct); +``` + +## Notes +- Update(Project) and Remove(Project) do not accept a CancellationToken, so cancellation must be handled by the implementation or surrounding unit-of-work/pipeline. +- GetByIdAsync and GetByIdWithFilesAsync differ in how much related data is loaded; choose based on whether you need the associated Files collection. +- All read operations require ownerUserId to maintain proper data isolation; passing the correct ownerId is essential to avoid cross-tenant access. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md new file mode 100644 index 0000000..a21d8de --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md @@ -0,0 +1,25 @@ +# IUnitOfWork + +> **File:** `src/api/Gabriel.Core/Repositories/IUnitOfWork.cs` +> **Kind:** interface + +```csharp +public interface IUnitOfWork +``` + + +IUnitOfWork defines a contract to coordinate persistence across repositories, enabling a single commit for all in-flight changes. Implementations are invoked by services once per use-case to persist changes as one transactional unit, improving consistency and reducing the risk of partial updates when multiple repositories are involved. The SaveChangesAsync method accepts an optional CancellationToken and returns the number of state entries written to the underlying data store. + +## Remarks +By encapsulating transaction boundaries, this abstraction decouples repositories from the specifics of the persistence technology and promotes testability through mock or faked implementations. It clarifies the orchestration point where multiple aggregates are persisted together, helping maintain a consistent domain state across operations. + +## Example +```csharp +// Example usage within a service that has performed repository updates +int persisted = await _unitOfWork.SaveChangesAsync(ct); +``` + +## Notes +- The return value represents the number of state entries written to the database; depending on the provider, this may not map one-to-one with logical operations. +- Pass a meaningful CancellationToken to support cooperative cancellation of the commit operation. +- The exact transactional guarantees depend on the concrete implementation and data provider; some implementations wrap changes in a transaction, others rely on provider-specific semantics.} \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Services/ChatService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/ChatService.cs.md new file mode 100644 index 0000000..7f6a443 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/ChatService.cs.md @@ -0,0 +1,60 @@ +# ChatService + +> **File:** `src/api/Gabriel.Core/Services/ChatService.cs` +> **Kind:** class + +*Figure: How ChatService works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +start["Start CreateConversationAsync"] --> req["Call RequireUserId() via ICurrentUser"] +req --> checkIsPid{"Is projectId provided?"} +checkIsPid -- Yes --> getProject["IProjectRepository.GetByIdAsync(pid, userId)"] +getProject --> projectFound{"Project found?"} +projectFound -- No --> notFound["Throw NotFoundException(nameof(Project), pid)"] +notFound --> endErr["End (NotFoundException)"] +projectFound -- Yes --> setResolvedFromProject["resolvedProjectId = Project.Id"] +checkIsPid -- No --> ensureDefault["IProjectService.EnsureDefaultProjectIdAsync()"] +ensureDefault --> setResolvedDefault["resolvedProjectId = returned default id"] +setResolvedFromProject --> create["Conversation.Create(userId, resolvedProjectId, title)"] +setResolvedDefault --> create +create --> add["IConversationRepository.AddAsync(conversation)"] +add --> save["IUnitOfWork.SaveChangesAsync()"] +save --> ret["Return Conversation"] +ret --> end["End"] +``` + +```csharp +public class ChatService : IChatService +``` + + +Provides application-level operations for managing Conversation entities on behalf of the current authenticated user. Use this service when you need to create, enumerate, retrieve or modify conversations while enforcing ownership, resolving project placement (including the user's default project), and committing changes via the unit-of-work. + +## Remarks +ChatService coordinates repository and domain operations rather than containing domain logic itself: it resolves the target project (verifying supplied project IDs belong to the current user or falling back to the user's default project), fetches or validates Conversation instances from IConversationRepository, calls domain methods on Conversation (Rename, RerollAvatar, SetSkin, SetMode, etc.), and persists changes through IUnitOfWork. It centralizes common checks (current user resolution, NotFound handling) so callers do not need to repeat ownership or persistence concerns. + +## Example +```csharp +// Typical usage from an API controller or application layer that has an IChatService: +var ct = CancellationToken.None; + +// Create a conversation in the caller's default project +var created = await chatService.CreateConversationAsync(projectId: null, title: "Ideas", ct); + +// Rename the conversation +var renamed = await chatService.RenameConversationAsync(created.Id, "New Ideas", ct); + +// Retrieve the conversation with messages +var loaded = await chatService.GetConversationAsync(renamed.Id, ct); + +// List conversations (optionally scoped to a project) +var list = await chatService.ListConversationsAsync(projectId: null, ct); +``` + +## Notes +- If a caller supplies a projectId, the service verifies that the project belongs to the current user; otherwise it throws NotFoundException for that project id. +- Domain-level validation (for example, Conversation.Rename rejecting an empty or whitespace title) is performed by the Conversation entity; such validation surfaces as exceptions (ArgumentException) and is expected to be handled by the application's global exception mapping. +- All operations require an authenticated user (the service resolves the current user id); missing authentication will prevent operations from proceeding. +- Methods persist changes by calling SaveChangesAsync on the unit-of-work; callers should not attempt an additional save for the same operation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IChatService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IChatService.cs.md new file mode 100644 index 0000000..70d2c96 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IChatService.cs.md @@ -0,0 +1,100 @@ +# IChatService + +> **File:** `src/api/Gabriel.Core/Services/IChatService.cs` +> **Kind:** interface + +*Figure: How IChatService works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB + +Start["IChatService: Receive request"] --> OpDec{ "Which operation?" } + +OpDec -->|Create| Create["CreateConversationAsync(projectId?, title)"] +Create --> ProjectCheck{ "projectId provided?" } +ProjectCheck -->|Yes| UseProject["IProjectService: Use project"] +ProjectCheck -->|No| EnsureDefault["IProjectService: Ensure Default project (auto-create)"] +UseProject --> CreateDone["Conversation: Create and return"] +EnsureDefault --> CreateDone + +OpDec -->|List| List["ListConversationsAsync(projectId?)"] +List --> ListDecision{ "projectId null?" } +ListDecision -->|Yes| ListAll["Return all Conversations across projects"] +ListDecision -->|No| ListProj["Return Conversations for project"] +ListAll --> EndList["IChatService: Return IReadOnlyList"] +ListProj --> EndList + +OpDec -->|Get/Rename/Reroll| GetOps["GetConversationAsync / RenameConversationAsync / RerollAvatarAsync"] +GetOps --> GetDone["Conversation: Fetch or update and return"] + +OpDec -->|SetSkin| SetSkin["SetSkinAsync(id, pattern?, palette?)"] +SetSkin --> SkinCheck{ "Conversation in Default project?" } +SkinCheck -->|Yes| ApplySkin["Apply skin to Conversation (catalog IDs must be validated by API)"] +SkinCheck -->|No| SkinNoop["No-op: project skin overrides (mirror of IProjectService.SetSkinAsync)"] +ApplySkin --> SkinDone["Conversation: Return"] +SkinNoop --> SkinDone + +OpDec -->|SetMode| SetMode["SetModeAsync(id, GabrielMode?)"] +SetMode --> ModeCheck{ "mode null?" } +ModeCheck -->|Yes| ClearMode["Clear per-conversation mode (reads as Chatty)"] +ModeCheck -->|No| ApplyMode["Set GabrielMode on Conversation"] +ClearMode --> ModeDone["Conversation: Return"] +ApplyMode --> ModeDone + +OpDec -->|DeleteConversation| DelConv["DeleteConversationAsync(id)"] +DelConv --> DelConvDone["Conversation: removed"] + +OpDec -->|Message-level ops| MsgOps["Message-level operations"] +MsgOps --> DeleteMsg["DeleteMessageAsync(conversationId, messageId)"] +DeleteMsg --> DeleteMsgProc["Delete target message AND everything after it (anchor at variant group's earliest sibling)"] +DeleteMsgProc --> MsgReturn["Conversation: Return"] + +MsgOps --> SetVariant["SetActiveVariantAsync(conversationId, messageId)"] +SetVariant --> VariantCheck{ "chosen message already active?" } +VariantCheck -->|Yes| VariantNoop["No-op"] +VariantCheck -->|No| VariantApply["Flip siblings inactive; chosen variant becomes active"] +VariantNoop --> MsgReturn +VariantApply --> MsgReturn + +%% end +``` + +```csharp +public interface IChatService +``` + + +An orchestration-focused abstraction for conversation lifecycle and per-message operations. Use IChatService when you need to create, list, rename, delete or otherwise manage conversations and their message-level state (delete/rewind, pick active variant, set avatar skin or per-conversation mode). The actual chat-turn execution (streaming turns, tool invocation, model interaction) is intentionally kept out of this interface and belongs to IAgentService — IChatService concerns itself with CRUD and business rules around conversation metadata and structural edits. + +## Remarks +IChatService centralizes conversation lifecycle behavior so callers do not need to manipulate Conversation entities or project-level skin logic directly. It preserves higher-level rules: a null projectId places a conversation into the user's Default project (which is auto-created on first use), SetSkinAsync is a per-conversation override but is only meaningful for conversations that are not inside a real project (project-owned chats render the project's skin instead), and message deletions are destructive rewinds (they remove the targeted message and everything that came after it to keep variant-group tails consistent). Implementations are expected to validate catalog identifiers (pattern/palette) at the API boundary before they reach SetSkinAsync. + +## Example +```csharp +// Typical usage from an application service or controller +IChatService chat = /* resolved from DI */; +CancellationToken ct = CancellationToken.None; + +// Create a conversation in the user's default project (pass null) +Conversation convo = await chat.CreateConversationAsync(null, "New chat", ct); + +// Rename and list +convo = await chat.RenameConversationAsync(convo.Id, "Rename: brainstorming", ct); +IReadOnlyList all = await chat.ListConversationsAsync(null, ct); + +// Pin a skin for a standalone conversation (pattern/palette must already be validated) +convo = await chat.SetSkinAsync(convo.Id, "stripes", "ocean", ct); + +// Rewind the conversation by deleting a message (destructive: removes message + tail) +convo = await chat.DeleteMessageAsync(convo.Id, someMessageId, ct); + +// Switch which variant is active in a variant group +convo = await chat.SetActiveVariantAsync(convo.Id, variantMessageId, ct); +``` + +## Notes +- SetSkinAsync: callers must validate catalog identifiers (pattern/palette) before calling; for conversations that belong to a real project the project's skin takes precedence and the conversation-level skin will not be used for rendering. +- DeleteMessageAsync is destructive by design: it removes the targeted message and everything that followed it (anchored at the variant group's earliest sibling) — use for "rewind to here" UX only. +- SetModeAsync accepts null to clear a custom mode back to the default; the read path treats the default as Chatty. +- SetActiveVariantAsync is a no-op if the requested message is already the active variant. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md new file mode 100644 index 0000000..d3067bb --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md @@ -0,0 +1,98 @@ +# IMemoryService.cs + +> **Source:** `src/api/Gabriel.Core/Services/IMemoryService.cs` + +## Contents + +- [IMemoryService](#imemoryservice) +- [MemoryEntrySpec](#memoryentryspec) + +--- + +## IMemoryService +> **File:** `src/api/Gabriel.Core/Services/IMemoryService.cs` +> **Kind:** interface + +```csharp +public interface IMemoryService +``` + + +IMemoryService provides a user-scoped, domain-level API for managing MemoryEntry records. It serves as a boundary between controllers/tools and the persistence layer, pulling the current UserId from the context and enforcing user-scoped access so tools cannot inadvertently read or modify memories outside the caller's scope. + +## Remarks +IMemoryService centralizes memory operations behind a user-aware facade, isolating authentication/authorization concerns from higher layers. It coordinates with the repository and MemoryEntry entities to offer upsert, retrieval, and removal semantics that respect both user and project scopes, while exposing convenient methods tailored to common use cases (e.g., displaying memories for a conversation with both user- and project-scoped memories). + +## Notes +- Be mindful of cancellation via the provided CancellationToken; propagate it through to repository calls to avoid unresponsive operations. +- RemoveAsync returns false when no entry matched, so callers can distinguish between a no-op and an actual deletion. RemoveByNameAsync similarly indicates absence of a match; handle these booleans gracefully in UI/flows. + +## Dependencies +- MemoryEntry +- IMemoryRepository +- UpdatedAt + +## Dependency APIs (verified signatures) +- class [`MemoryEntry`](../Entities/MemoryEntry.cs.md) (`src/api/Gabriel.Core/Entities/MemoryEntry.cs`) + - property `Guid Id` + - property `Guid UserId` + - property `Guid? ProjectId` + - property `MemoryEntryType Type` + - property `string Name` + - property `string Description` + - property `string Body` + - property `DateTimeOffset CreatedAt` + - property `DateTimeOffset UpdatedAt` + - `MemoryEntry()` + - `MemoryEntry Create(Guid userId, Guid? projectId, MemoryEntryType type, string name, string description, string body)` + - `void Update(MemoryEntryType type, string description, string body)` + - …and 1 more member(s) not shown +- interface [`IMemoryRepository`](../Repositories/IMemoryRepository.cs.md) (`src/api/Gabriel.Core/Repositories/IMemoryRepository.cs`) + - `Task> ListAsync(Guid userId, Guid? projectId, CancellationToken ct)` + - `Task> ListForAgentAsync(Guid userId, Guid? projectId, CancellationToken ct)` + - `Task GetByIdAsync(Guid id, Guid userId, CancellationToken ct)` + - `Task FindByNameAsync(Guid userId, Guid? projectId, string name, CancellationToken ct)` + - `Task AddAsync(MemoryEntry entry, CancellationToken ct)` + - `void Update(MemoryEntry entry)` + - `void Remove(MemoryEntry entry)` +- property `UpdatedAt` (`src/api/Gabriel.Core/Entities/Conversation.cs`) + +## Symbol To Document +- Name: `IMemoryService` +- Kind: interface +- File: `src/api/Gabriel.Core/Services/IMemoryService.cs` +- Language: csharp +- ID: fe3d8046-665f-4fb4-9056-a057b2b417d2 + +--- + +## MemoryEntrySpec +> **File:** `src/api/Gabriel.Core/Services/IMemoryService.cs` +> **Kind:** record + +```csharp +public sealed record MemoryEntrySpec( + Guid? ProjectId, + MemoryEntryType Type, + string Name, + string Description, + string Body) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `ProjectId` | `Guid?` | — | +| `Type` | [`MemoryEntryType`](../Entities/MemoryEntryType.cs.md) | — | +| [`Name`](../../Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `Description` | `string` | — | +| `Body` | `string` | — | + + +MemoryEntrySpec is an immutable data contract used to describe a single memory entry within the memory service. It carries an optional project association through ProjectId, the entry type through Type, and the essential metadata required for display or storage: Name, Description, and Body. As a record, it provides value-based equality and convenient copying semantics, making it ideal for transferring memory-entry data between layers without introducing behavior. + +## Remarks +MemoryEntrySpec serves as a boundary between API/domain concerns and persistence. It encapsulates the minimal data needed to create or update an entry and can be used to validate input before mapping to domain entities. The nullable ProjectId allows entries to be associated with a project when present, while Type distinguishes the kind of memory entry without requiring call sites to interpret the raw fields. Since it's a C# record, it benefits from value-based equality and supports non-destructive modification via with-expressions to create altered copies while preserving the original instance. The Body content represents the actual textual payload of the memory entry. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md new file mode 100644 index 0000000..168be80 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md @@ -0,0 +1,42 @@ +# IProjectFileService + +> **File:** `src/api/Gabriel.Core/Services/IProjectFileService.cs` +> **Kind:** interface + +```csharp +public interface IProjectFileService +``` + + +Defines asynchronous operations to manage files within a single project: listing file metadata, retrieving file details, opening an open content stream for downloads, reading text content up to a limit, uploading new content from a stream, deleting files, and obtaining the project's sandbox directory. It hardens against path traversal and avoids loading whole files into memory by streaming where possible; consumers such as web controllers and tooling can perform file operations without buffering large payloads. + +## Remarks +Architecturally, this interface centralizes filesystem concerns for a given project, pairing metadata with content streaming behind a single abstraction. It ensures authorization and sandbox scoping (via GetProjectDirectoryAsync) are performed in one place, enabling swap-out storage implementations without changing callers. The streaming contracts (OpenAsync returning a Content stream, and ReadTextAsync returning null for non-text) shape how consumers should interact with large or binary files. + +## Example +```csharp +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +public async Task PreviewFirstFileAsync(IProjectFileService service, Guid projectId, CancellationToken ct) +{ + var files = await service.ListAsync(projectId, ct); + var first = files.FirstOrDefault(); + if (first != null) + { + var maxBytes = 1024 * 1024; // 1 MB + var text = await service.ReadTextAsync(projectId, first.Id, maxBytes, ct); + if (text != null) + { + Console.WriteLine(text); + } + } +} +``` + +## Notes +- ReadTextAsync may return null if the file isn't text-like; handle nulls gracefully. +- OpenAsync returns a tuple (File, Content). The Content stream must be disposed by the caller after use. +- UploadAsync and ReadTextAsync operate on streams; ensure streams are properly disposed and avoid buffering entire content in memory when possible. Path handling is sandboxed to the project's directory to prevent traversal outside the project root. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectService.cs.md new file mode 100644 index 0000000..b282de3 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/IProjectService.cs.md @@ -0,0 +1,98 @@ +# IProjectService + +> **File:** `src/api/Gabriel.Core/Services/IProjectService.cs` +> **Kind:** interface + +*Figure: How IProjectService works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +IService[IProjectService] +ProjectNode[Project] + +L["ListAsync: lazily ensure Default project exists; absorb pre-Phase-8 conversations; return user's projects (IReadOnlyList)"] +E["EnsureDefaultProjectIdAsync: create Default project if missing; assign project-less (legacy) conversations; return Guid"] +G["GetAsync: return Project by id"] +GF["GetWithFilesAsync: return Project with files loaded, sorted newest-first"] +C["CreateAsync: create Project (name, description, systemPrompt); return Project"] +U["RenameAsync / UpdateDescriptionAsync / UpdateSystemPromptAsync: update Project metadata; return Project"] +R["RerollAvatarAsync: re-roll AvatarSeed (changes seed-derived pattern+palette); return Project"] +S["SetSkinAsync: pin or clear pattern & palette overrides; catalog ids validated at API layer; return Project"] +D["DeleteAsync: delete Project"] + +IService --> L +L -->|"calls EnsureDefaultProjectIdAsync lazily"| E +L -->|"returns IReadOnlyList"| ProjectNode +E -->|"returns Guid (Default project id)"| IService + +IService --> G +G -->|"returns Project"| ProjectNode + +IService --> GF +GF -->|"loads files; sorts newest-first; returns Project"| ProjectNode + +IService --> C +C -->|"creates Project and returns it"| ProjectNode + +IService --> U +U -->|"updates fields and returns Project"| ProjectNode + +IService --> R +R -->|"re-rolls AvatarSeed and returns Project"| ProjectNode + +IService --> S +S -->|"sets/clears overrides and returns Project"| ProjectNode + +IService --> D +D -->|"deletes Project"| ProjectNode +``` + +```csharp +public interface IProjectService +``` + + +Provides the high-level operations for creating, reading, updating and deleting per-user Project entities and for managing project presentation (avatar/skin). Use this interface from application or API layers when you need to list a user's projects, ensure or retrieve the user's default project, migrate legacy project-less conversations into the default, or modify project metadata and visual appearance. + +## Remarks +IProjectService is the central boundary for project lifecycle and simple presentation concerns. Implementations are expected to be multi-tenant aware (the service surface returns only the current user's projects) and to perform lazy bootstrapping: the default project for a user is created on demand and legacy, project-less conversations are assigned to it during that process. The interface intentionally blends CRUD operations (CreateAsync, GetAsync, ListAsync, DeleteAsync, RenameAsync, UpdateDescriptionAsync, UpdateSystemPromptAsync) with a small set of presentation helpers (RerollAvatarAsync, SetSkinAsync) because avatar/palette state is stored on the Project entity. + +## Example +```csharp +// Typical usage from an application service or controller +public async Task Demo(IProjectService projects, CancellationToken ct) +{ + // Ensure the current user has a default project and get its id + Guid defaultId = await projects.EnsureDefaultProjectIdAsync(ct); + + // List all projects for the current user (this call will also lazily create + // the default project on first invocation if it wasn't present) + var all = await projects.ListAsync(ct); + + // Create a new project + var newProj = await projects.CreateAsync("Research", "Notes for research", null, ct); + + // Load a project and its files (files are returned newest-first) + var withFiles = await projects.GetWithFilesAsync(newProj.Id, ct); + + // Update presentation: pin a palette and pattern, or pass null/empty to clear + await projects.SetSkinAsync(newProj.Id, pattern: "grid", palette: "muted", ct: ct); + + // Reroll the avatar seed so seed-derived pattern/palette change (unless pinned) + await projects.RerollAvatarAsync(newProj.Id, ct); + + // Rename and update metadata + await projects.RenameAsync(newProj.Id, "Research 2026", ct); + await projects.UpdateDescriptionAsync(newProj.Id, "Updated notes", ct); + + // Delete when no longer needed + await projects.DeleteAsync(newProj.Id, ct); +} +``` + +## Notes +- ListAsync and EnsureDefaultProjectIdAsync have side effects: they lazily create the user's "Default" project and migrate legacy project-less conversations into it on first use. Calling them can modify persistent state. +- GetWithFilesAsync returns the project's Files ordered newest-first; callers relying on a different ordering should reorder explicitly. +- SetSkinAsync treats null or empty strings as a request to clear an override and revert that dimension to seed-derived behavior; higher-level API layers validate catalog identifiers before calling into the service. +- All methods accept a CancellationToken; implementations should respect cancellation to avoid long-running or blocking calls under request cancellation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Services/MemoryService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/MemoryService.cs.md new file mode 100644 index 0000000..31f6e35 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/MemoryService.cs.md @@ -0,0 +1,49 @@ +# MemoryService + +> **File:** `src/api/Gabriel.Core/Services/MemoryService.cs` +> **Kind:** class + +*Figure: How MemoryService works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +IMemoryService["IMemoryService: public API (List/Get/Save/Remove)"] +MemoryService["MemoryService: method invoked"] +ICurrentUser["ICurrentUser: UserId or null"] +IMemoryRepository["IMemoryRepository: List/Find/Get/Add/Update/Remove"] +MemoryEntrySpec["MemoryEntrySpec: input for SaveAsync"] +MemoryEntry["MemoryEntry: Create or Update entity"] +IUnitOfWork["IUnitOfWork: SaveChangesAsync (commit)"] + +IMemoryService --> MemoryService +MemoryService -->|"RequireUserId()"| ICurrentUser +MemoryService -->|"calls repository"| IMemoryRepository +MemoryService -->|"SaveAsync(spec)"| MemoryEntrySpec + +IMemoryRepository -->|"FindByNameAsync(userId, projectId, name)"| MemoryEntry +IMemoryRepository -->|"existing == null: AddAsync(new MemoryEntry)"| MemoryEntry +IMemoryRepository -->|"existing != null: Update(existing)"| MemoryEntry + +MemoryService -->|"GetByIdAsync(id, userId) / FindByNameAsync(userId, projectId, name)"| IMemoryRepository + +MemoryEntry -->|"added or updated"| IUnitOfWork +MemoryService -->|"SaveChangesAsync()"| IUnitOfWork +IUnitOfWork -->|"commit/return result"| IMemoryService +``` + +```csharp +public class MemoryService : IMemoryService +``` + + +A service that implements IMemoryService by delegating memory operations to a repository and committing changes through a unit-of-work. Use MemoryService when you need an application-layer façade that enforces the current authenticated user, performs simple upsert logic for memories (by name + project), and coordinates persistence (add/update/remove) with transaction semantics. + +## Remarks +MemoryService centralizes user-scoped memory operations: it requires an authenticated user (via ICurrentUser) and then forwards list, retrieve, save, and delete operations to an underlying IMemoryRepository. SaveAsync implements an upsert by searching for an existing memory with the same name and project for the current user — creating a new MemoryEntry when none exists or calling Update on the existing entry otherwise — and always calls IUnitOfWork.SaveChangesAsync to persist modifications. + +## Notes +- All operations require an authenticated user; if ICurrentUser.UserId is null the service throws UnauthorizedAccessException. +- SaveAsync matches existing entries by (userId, projectId, name) and will update the first match rather than creating a duplicate. +- RemoveAsync and RemoveByNameAsync return false when the targeted memory cannot be found; they return true only after the repository removal and unit-of-work save complete. +- ListForConversationAsync delegates to the repository's agent-specific listing method (ListForAgentAsync) — use ListAsync for the standard listing behavior and ListForConversationAsync when data tailored for agent/conversation use is needed. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Core/Services/ProjectService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/ProjectService.cs.md new file mode 100644 index 0000000..1abd18c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Core/Services/ProjectService.cs.md @@ -0,0 +1,66 @@ +# ProjectService + +> **File:** `src/api/Gabriel.Core/Services/ProjectService.cs` +> **Kind:** class + +*Figure: How ProjectService works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +ProjectService["ProjectService - entry for ListAsync, GetAsync, GetWithFilesAsync, CreateAsync, RenameAsync, UpdateDescriptionAsync, UpdateSystemPromptAsync, RerollAvatarAsync, SetSkinAsync"] +IProjectService["IProjectService - interface implemented by ProjectService"] +ICurrentUser["ICurrentUser - provides current user id via RequireUserId"] +IProjectRepository["IProjectRepository - repository methods: GetByIdAsync, GetByIdWithFilesAsync, ListAsync, AddAsync, Update"] +IUnitOfWork["IUnitOfWork - SaveChangesAsync invoked after mutations"] +Project["Project - factory Create and mutating methods Rename, UpdateDescription, UpdateSystemPrompt, RerollAvatar, SetSkin"] +NotFoundException["NotFoundException - thrown when repository returns null for Get operations"] + +IProjectService --> ProjectService +ProjectService --|"RequireUserId used before operations"| ICurrentUser +ProjectService --|"Ensure default exists then call ListAsync"| IProjectRepository +ProjectService --|"Call GetByIdAsync or GetByIdWithFilesAsync"| IProjectRepository +IProjectRepository --|"returns null"| NotFoundException +IProjectRepository --|"returns project entity"| Project +ProjectService --|"On create or update, construct or mutate Project and call repository"| Project +ProjectService --|"AddAsync or Update then call SaveChangesAsync on unit of work"| IUnitOfWork +``` + +```csharp +public class ProjectService : IProjectService +``` + + +Provides high-level project management operations for the current user and coordinates repository and unit-of-work persistence. Use this service from application layers when you need to list, create, rename, update, or delete Projects for the authenticated user; it wraps domain operations on Project and ensures changes are saved through the IUnitOfWork. + +## Remarks +ProjectService enforces per-user scoping (operations act on the currently authenticated user) and guarantees a "Default" project exists for a user before returning a project list. Mutating operations (create, rename, update description/system prompt, reroll avatar, set skin, delete) apply domain logic on the Project entity, update the repository, and call the unit-of-work to persist changes. Read operations throw NotFoundException when the requested project does not exist for the current user. + +## Example +```csharp +// 'projectService' is an instance of ProjectService (IProjectService). +// Ensure the default project exists and get its id. +var defaultId = await projectService.EnsureDefaultProjectIdAsync(ct); + +// List projects for the current user (this will create the Default project if needed). +var projects = await projectService.ListAsync(ct); + +// Create a new project and then rename it. +var newProject = await projectService.CreateAsync("My Project", "notes", null, ct); +var renamed = await projectService.RenameAsync(newProject.Id, "Renamed Project", ct); + +// Handle missing project when trying to get it by id. +try +{ + var project = await projectService.GetAsync(Guid.NewGuid(), ct); +} +catch (NotFoundException ex) +{ + // project not found for current user +} +``` + +## Notes +- All methods operate on the currently authenticated user; calls will fail with an UnauthorizedAccessException if there is no current user context. +- Create/modify/delete methods persist changes by calling IUnitOfWork.SaveChangesAsync — the effects are durable only after the returned task completes. +- GetAsync and GetWithFilesAsync throw NotFoundException when the project id does not exist for the current user. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/DependencyInjection.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/DependencyInjection.cs.md new file mode 100644 index 0000000..b66edb0 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/DependencyInjection.cs.md @@ -0,0 +1,19 @@ +# DependencyInjection + +> **File:** `src/api/Gabriel.Engine/DependencyInjection.cs` +> **Kind:** class + +```csharp +public static class DependencyInjection +``` + + +Configures and wires the engine’s runtime DI graph by binding configuration options and registering core Gabriel.Engine services, tools, and per-request contexts into the application's service container. Use this extension during startup to prepare the engine for operation so controllers and services can request IAgentService, ITool, IToolRegistry, IMetricRecorder, and related collaborators via constructor injection. + +## Remarks +This abstraction centralizes concerns across infrastructure and engine boundaries, ensuring a clear separation between per-request state (e.g., ToolExecutionContext) and singleton services (e.g., MetricRecorder, PromptRegistry). It also anchors the availability of the engine's tool suite by registering every built-in ITool implementation, while deferring concrete providers' wiring to Gabriel.Infrastructure. + +## Notes +- Be mindful of lifetimes: most tools are registered as Scoped to align with per-turn processing. +- This extension relies on AddInfrastructure to register its provider bindings; ensure Gabriel.Infrastructure is wired up in the host application. +- Config sections AgentOptions, PersonalityOptions, and AgentToolsOptions must exist (or have sensible defaults) to avoid misconfiguration. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md new file mode 100644 index 0000000..93d0e11 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md @@ -0,0 +1,19 @@ +# GabrielSystemPromptBuilder + +> **File:** `src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs` +> **Kind:** class + +```csharp +public sealed class GabrielSystemPromptBuilder : ISystemPromptBuilder +``` + + +GabrielSystemPromptBuilder orchestrates Gabriel's system prompt by assembling three sources from the prompt registry: a static persona block, a formatting guideline, and a per-mode snippet, then layering in per-conversation metadata. It substitutes the agent's name into the static block once and caches the results to avoid repeated substitutions on every turn. The Build method accepts an optional ConversationState and an optional GabrielMode and returns the complete string used to drive the model's system prompt for that turn. This symbol is the central point where identity, mode, and runtime context merge into a single, stable prompt that can adapt across modes without duplicating logic. + +## Remarks +GabrielSystemPromptBuilder is the central prompt orchestration point that keeps the base persona constant while allowing mode- or state-driven variation. It achieves this by layering a static persona, a formatting guidance block, and a mode-specific snippet fetched from the registry; by caching the substituted static and few-shot fragments, it minimizes per-turn processing. The Build method tolerates null state and mode, defaulting to sane values, reducing caller friction; this ensures a robust prompt even in edge cases. + +## Notes +- Caching of static blocks means prompts update require recreate or reinitialize when the prompt registry changes or when options change, otherwise the prompt may reflect stale data. +- The class caches _staticBlock, _formattingBlock, and _fewShotBlock at construction; changes to the underlying prompts after construction won't be reflected until a new instance is created. +- Missing registry keys (PersonaStatic, PersonaFormatting, PersonaFewShot) may yield incomplete prompts; ensure the prompt registry provides them to avoid empty segments. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs.md new file mode 100644 index 0000000..84bb5dd --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs.md @@ -0,0 +1,32 @@ +# HeuristicConversationStateUpdater + +> **File:** `src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs` +> **Kind:** class + +```csharp +public sealed class HeuristicConversationStateUpdater : IConversationStateUpdater +``` + + +A deterministic, microsecond-cost implementation of IConversationStateUpdater that derives simple conversation signals from a single user message using compiled regular expressions and a token estimator. Reach for this class when you want predictable, zero-LLM per-turn state updates (turn counts, token-based length statistics, lightweight mood cues, topic hints, and simple stylistic signals such as emoji or lowercase usage) instead of a costlier LLM-backed analysis. + +## Remarks +This updater is intentionally heuristic and conservative: it trades some classification accuracy for predictability and near-zero runtime overhead. It uses a token estimator to compute per-message token counts and an exponential-moving-average to update the user's average token length, and it applies a set of compiled, case-insensitive regexes (detail/task cues, polite suffixes, playful/serious markers, and a small negative-affect lexicon) plus a simple emoji check to derive surface signals. The class exists as a lightweight default/updater replacement where deterministic behavior and cost-control are more important than deep intent understanding; an LLM-based updater with the same interface can be swapped in later when higher fidelity is required. + +## Example +```csharp +// Typical, minimal usage +ITokenEstimator tokenEstimator = /* your token estimator */; +var updater = new HeuristicConversationStateUpdater(tokenEstimator); +var state = ConversationState.Initial(); + +state = updater.Update(state, "write a sorting algorithm please"); +// state now has TurnCount incremented, LastUserTokenCount and AvgUserTokenCount updated, +// Mood adjusted by classification, and simple stylistic/topic signals set. +``` + +## Notes +- The ShortTokenThreshold and other tunings are heuristic (comments indicate ~4 chars/token assumptions) and may need adjustment for different tokenizers or languages. +- Detail/task detection relies on a word-boundary-anchored, compiled regex so common substrings won't usually trigger false positives; the regexes are case-insensitive and compiled for performance. +- Emoji detection is a lightweight check (high-surrogate characters plus the U+2600–U+27BF range) and is not a comprehensive Unicode emoji detector. +- This implementation is intentionally conservative and may misclassify mood or miss nuanced intent; use an LLM-backed updater if you require deeper semantic understanding. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs.md new file mode 100644 index 0000000..71d2df9 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs.md @@ -0,0 +1,18 @@ +# IConversationStateUpdater + +> **File:** `src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs` +> **Kind:** interface + +```csharp +public interface IConversationStateUpdater +``` + + +IConversationStateUpdater provides a contract to compute the next ConversationState given the current state and the latest user message. The Update method takes a possibly null current state and the user's message and returns the updated ConversationState, enabling stateless implementations that can be registered as singletons in DI. + +## Remarks +By decoupling the state evolution from storage or orchestration, this interface makes the update policy swappable and testable. Implementations are expected to be stateless, which enables safe singleton usage and predictable behavior under concurrent requests. The single Update method concentrates the business rules for transitioning a conversation into one place, making it easier to experiment with different strategies while keeping the rest of the system stable. + +## Notes +- Handle null current state gracefully; define an initial state as needed by your application. +- Treat ConversationState as immutable from the updater's perspective; do not mutate the provided instance—return a new state or a fresh copy. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs.md new file mode 100644 index 0000000..d624210 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs.md @@ -0,0 +1,19 @@ +# IResponsePostProcessor + +> **File:** `src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs` +> **Kind:** interface + +```csharp +public interface IResponsePostProcessor +``` + + +IResponsePostProcessor defines a contract for post-processing a model's raw response before it is persisted to the database. It currently strips residual AI-era opener/closer phrases and applies a length cap derived from ConversationState, while preserving Markdown formatting (Discord-style). The Clean method accepts a raw string and an optional ConversationState; when state is null implementers should apply a sensible default and still produce a clean, non-destructive result. + +## Remarks +This abstraction centralizes content-cleaning logic at the persistence boundary, allowing different implementations to be swapped without changing controllers. By taking an optional ConversationState, it can tailor the length cap and other heuristics to the current turn context, improving consistency across messages and tests. + +## Notes +- Implementations must gracefully handle a null ConversationState (use a safe default). +- The cleaning should not alter Markdown syntax beyond removing unwanted opener/closer phrases and trimming length. +- This post-processing is intended for persisted content; avoid altering content during streaming UI interactions. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md new file mode 100644 index 0000000..c18d493 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md @@ -0,0 +1,19 @@ +# ISystemPromptBuilder + +> **File:** `src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs` +> **Kind:** interface + +```csharp +public interface ISystemPromptBuilder +``` + + +Builds the complete per-turn system prompt by combining a static persona block, a mode-specific bias fragment, and dynamic guidance derived from the current ConversationState. It is modeled as a stateless service: implementors should compute and return the prompt on demand without mutating internal state. The mode parameter selects which fragment from Fragments is spliced into the prompt; when mode is null, GabrielMode.Chatty is used as the baseline behavior. + +## Remarks +By centralizing system-prompt construction behind ISystemPromptBuilder, the engine ensures consistent onboarding for different Gabriel modes while still adapting to runtime context. It decouples the prompt assembly from the caller, enabling easier testing and replacement of the prompt strategy. The collaboration with ConversationState, Fragments, and GabrielMode defines a clean contract: state drives dynamic guidance, mode selects bias, and the static persona anchors tone. + +## Notes +- Null mode is treated as GabrielMode.Chatty; callers relying on a default should either omit mode or pass null. +- The interface is stateless; any per-turn variation must come from the provided ConversationState and mode argument. +- Changes in Fragments or GabrielMode may alter the produced prompts; callers should design for potential prompt evolution and avoid brittle assumptions about exact wording. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs.md new file mode 100644 index 0000000..6f20e69 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs.md @@ -0,0 +1,24 @@ +# Fragments + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs` +> **Kind:** class + +```csharp +public static partial class Fragments +``` + + +Fragments serves as a container for prompt fragments that seed the model's behavior. PersonaFewShot provides a curated set of exchanges illustrating register-mirroring and length-matching in practice, with {name} replaced at runtime to personalize the persona. + +## Remarks +These anchors centralize how the system demonstrates a talking style to the model, ensuring consistent tone and back-and-forth rhythm across prompts. By hosting them in a single, reusable constant, the engine can swap personas or adjust the style without changing prompt-building logic. They also demonstrate how to preserve lowercasing, abbreviations, and verbiage that the persona uses, which helps guide the model's responses toward a natural, human-like conversation. + +## Example +```csharp +// Example: incorporate the small few-shot persona into a live prompt +string persona = "Nova"; +string prompt = Fragments.PersonaFewShot.Replace("{name}", persona) + "\nUser: can you help me with this task?\nNova: "; +``` + +## Notes +- The PersonaFewShot block is large; including it increases prompt size; only include when necessary to set tone. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs.md new file mode 100644 index 0000000..714e499 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs.md @@ -0,0 +1,23 @@ +# Fragments + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs` +> **Kind:** class + +```csharp +public static partial class Fragments +``` + + +The Fragments class serves as a centralized repository for rendering guidance used by the chat UI. It exposes PersonaFormatting, a compile-time constant string that documents the formats the web app supports (GitHub-flavored Markdown with Mermaid diagrams, LaTeX math, and code highlighting) and the rules for when to apply each. Keeping this medium concern separate from persona or behavior logic ensures consistent rendering guidance across prompts. + +## Remarks + +- It isolates medium concerns (rendering capabilities) from persona/behavior logic, allowing the UI to evolve formatting support independently. +- The static partial Fragments class pattern implies there are additional related fragments across the codebase, enabling a single source of truth for formatting documentation that prompts can reference. +- It acts as a contract between prompt generation and rendering components, guiding when to emit diagrams, math, or code blocks. + +## Notes + +- The content is a compile-time constant; editing it requires code changes and redeployment. +- The documented features are Mermaid, LaTeX, and code highlighting, with guidance on when to use each (e.g., diagrams for architecture, real equations for math; avoid overusing tables or graphs where prose suffices). +- This documentation does not implement rendering behavior; it merely describes what the UI supports. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs.md new file mode 100644 index 0000000..5b50514 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs.md @@ -0,0 +1,25 @@ +# Fragments + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs` +> **Kind:** class + +```csharp +public static partial class Fragments +``` + + +Fragments.PersonaMemory is a publicly accessible string constant that contains the memory-system guidance appended after the static persona block, so the model treats it as an independent capability rather than identity. It provides a centralized, reusable policy for how memory-related information should be saved, referenced, and used by the AI. + +## Remarks +Fragments is designed to decouple memory guidance from the runtime persona, enabling prompting to consistently convey memory behavior without altering identity. Exposing the guidance as a static constant in a partial class allows modular extension across the codebase while keeping the policy in a single source of truth. + +## Example +```csharp +// Retrieve the memory guidance for inclusion in a system prompt +string guidance = Fragments.PersonaMemory; +``` + +## Notes +- The content is a verbatim multi-line raw string literal; ensure formatting remains intact when injected into prompts. +- Because PersonaMemory is a const, changing it requires recompilation of the consuming assembly. +- The string documents memory tool names (memory_save, memory_list, memory_remove) and the conditions for saving or referencing information as described in the content. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs.md new file mode 100644 index 0000000..9b8b847 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs.md @@ -0,0 +1,27 @@ +# Fragments + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs` +> **Kind:** class + +```csharp +public static partial class Fragments +``` + + +Fragments provides per-mode bias blocks that are appended to the static persona block on each turn, selected according to Conversation.Mode. The base persona remains the core driver; the mode block re-weights tone and depth to tailor outcomes without rewriting the underlying behavior. The default ModeChatty serves as baseline, and other modes (ModeElaborative, ModeConcise, ModeTutor) adjust verbosity and guidance. + +## Remarks +Fragments centralizes mode-driven nudges, keeping the prompt assembly uniform and making it easy to introduce new modes by adding a constant rather than changing the builder. Because these blocks are raw strings, keep them short and focused on bias (the 'what' of the mode) rather than detailed policy or explanations. They operate on both Task and Chat halves; the mode-specific text should be mindful of the context to avoid contradicting the base instructions. + +## Example +```csharp +// Example usage: apply elaborative bias for the next turn +string modeBlock = Fragments.ModeElaborative; +string basePrompt = "Base persona prompt"; +string fullPrompt = modeBlock + " " + basePrompt; +``` + +## Notes +- The default ModeChatty baseline is intentionally minimal to preserve the existing persona unless a mode is selected. +- Mode blocks are parsed at runtime by the prompt builder and compiled into the final prompt; changes require code recompilation. +- Use concise blocks for modes not to overwhelm the base persona with excessive verbosity. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs.md new file mode 100644 index 0000000..e33b1f2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs.md @@ -0,0 +1,19 @@ +# Fragments + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs` +> **Kind:** class + +```csharp +public static partial class Fragments +``` + + +Fragments is a static partial container for the Gabriel engine’s persona fragments. It defines the core static header used to initialize every system prompt via the PersonaStatic constant, which encodes identity and initial behavioral guidance for the assistant. The {name} placeholder is substituted at runtime by GabrielSystemPromptBuilder from PersonalityOptions.Name (default \"Gabriel\"), enabling per-project personas to swap the name without touching this file. Memory guidance is intentionally kept separate (in Fragments.PersonaMemory) and may be omitted when memory tools aren’t registered. The static block ends with the phrase \"Hard prohibitions\" so a per-turn fragment (mode / memory) can append cleanly to this base header. + +## Remarks +Fragments serves as the architectural anchor for the initial system prompt, separating identity/header content from memory and per-turn behavior. By making the container static and partial, the codebase can extend or override persona-related fragments without mutating the core header, supporting flexible customization across projects while maintaining a consistent starting prompt. + +## Notes +- PersonaStatic is a const string; it cannot be mutated at runtime. Customization should occur via additional fragments or by changing the runtime name used in substitution. +- The {name} token must be replaced before the header is used by the system prompt (the comments indicate this happens via GabrielSystemPromptBuilder). +- Memory guidance belongs in Fragments.PersonaMemory and is appended separately, ensuring a clean separation of concerns between identity/header content and memory/mode behavior. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs.md new file mode 100644 index 0000000..f13fac6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs.md @@ -0,0 +1,25 @@ +# IPromptRegistry + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs` +> **Kind:** interface + +```csharp +public interface IPromptRegistry +``` + + +IPromptRegistry exposes a read-only contract for retrieving named prompt fragments that form the system prompt. It abstracts how prompt content is stored so callers can fetch fragments by key without coupling to a particular storage strategy. Today, the fragments live as const strings in Fragments.* partials, but the interface is intentionally storage-agnostic to accommodate future backends (embedded resources, external markdown, etc.) without forcing changes at call sites. Retrieved fragments may contain placeholder tokens (e.g. {name}); substitution is the caller's responsibility, keeping the registry free of rendering logic. + +## Remarks +This interface serves as a lightweight registry abstraction that decouples prompt data from the code that consumes it. By offering a single Get method, it standardizes retrieval while allowing the backing store to evolve—from in-code constants to alternative storage backends—without altering consumers. Token substitution is intentionally left to the caller, promoting a simple, predictable usage model and enabling flexible prompt assembly at consumption time. + +## Example +```csharp +// Example usage of IPromptRegistry +string raw = registry.Get("PersonaFewShot"); +string prompt = raw.Replace("{name}", "Ada"); +``` + +## Notes +- Missing keys: The contract does not specify behavior for absent keys; callers should handle potential null or empty results. +- Read-only contract: The interface exposes only retrieval; mutation, if needed, would be handled by the backing storage implementation or a separate layer. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs.md new file mode 100644 index 0000000..e064f0e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs.md @@ -0,0 +1,20 @@ +# PromptKey + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs` +> **Kind:** class + +```csharp +public static class PromptKey +``` + + +PromptKey is a static class that stores the canonical, compile-time keys for named prompt fragments used by the prompt registry. The keys are grouped by topic (persona, memory, formatting, and per-mode blocks) and exposed as const strings so callers rely on a single source of truth instead of ad hoc literals when assembling prompts or selecting fragments. When building a prompt, developers reach for PromptKey.* constants to refer to the appropriate fragment group or mode rather than hard-coding strings. + +## Remarks + +Centralizing these keys decouples the fragment registry from its consumers, enabling safer refactoring and consistent vocabulary across the system without inspecting raw strings at each call site. The constants point to corresponding entries in the Fragments.* definitions, which keeps the wording of fragment keys aligned across registration and retrieval paths. This design minimizes human error (typos) and makes evolution (adding new modes or sections) a matter of updating a single canonical set. + +## Notes + +- Keep PromptKey and Fragments.* in sync; renaming or moving a key requires updating both sides to avoid mismatches. +- Because these are compile-time constants, references to non-existent keys are caught at build time, but you must ensure the corresponding Fragments.* entry exists and mirrors PromptKey's value for runtime lookups. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs.md new file mode 100644 index 0000000..2cc7489 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs.md @@ -0,0 +1,28 @@ +# PromptRegistry + +> **File:** `src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs` +> **Kind:** class + +```csharp +public sealed class PromptRegistry : IPromptRegistry +``` + + +PromptRegistry serves as the default mapping from PromptKey values to their corresponding prompt fragments. It constructs a single dictionary at construction time that wires each key to the exact Fragments.* text, providing a centralized, consistent source of fragments for all prompt generation. + +## Remarks +By isolating the key-to-fragment wiring, the registry decouples callers from fragment content and enables simple evolution of prompts. The three coordinated edits described in the class comments (adding a Fragments constant, a PromptKey constant, and a dictionary entry) ensure the wiring remains in sync and produce a verifiable contract enforced by the compiler. + +## Example +```csharp +var registry = new PromptRegistry(); +string staticFragment = registry.Get(PromptKey.PersonaStatic); +``` + +```csharp +string tutorFragment = registry.Get(PromptKey.ModeTutor); +``` + +## Notes +- The registry uses ordinal string comparison for keys, so lookups are case-sensitive and must match the exact constants. +- If Get is called with a key that isn’t registered, a KeyNotFoundException is thrown with a message guiding how to wire the fragment (via the constructor and Fragments.* constant). diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs.md new file mode 100644 index 0000000..eed4953 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs.md @@ -0,0 +1,19 @@ +# ResponsePostProcessor + +> **File:** `src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs` +> **Kind:** class + +```csharp +public sealed class ResponsePostProcessor : IResponsePostProcessor +``` + + +ResponsePostProcessor cleans AI-generated content by stripping predefined opener phrases at the start and closing phrases at the end. This normalization yields concise, substantive content suitable for persistence or user-facing rendering. + +## Remarks +Centralizes post-processing of assistant messages to ensure a consistent user experience across UIs and storage layers. It relies on two static, compiled regex collections to detect and strip edge boilerplate, keeping per-call cost low and allocations minimal. The design is easily extensible: you can add more opener/closer patterns or adjust behavior without changing call sites. The class is sealed, providing a single, well-defined normalization point. + +## Notes +- Returns the input unchanged if the input is null or whitespace. +- Only matches opener phrases at the very start and closer phrases at the very end; inline content remains untouched. +- Patterns are case-insensitive and compiled for performance; concurrent calls are safe to use the same instance. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/AvailableModel.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/AvailableModel.cs.md new file mode 100644 index 0000000..82d7bfb --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/AvailableModel.cs.md @@ -0,0 +1,44 @@ +# AvailableModel + +> **File:** `src/api/Gabriel.Engine/Providers/AvailableModel.cs` +> **Kind:** record + +```csharp +public sealed record AvailableModel( + string Provider, + string Name, + int ContextWindowTokens, + double? CompactThreshold, + decimal InputPricePerMTokens, + decimal OutputPricePerMTokens, + decimal CacheReadPricePerMTokens, + decimal CacheWritePricePerMTokens, + bool IsDefault, + ToolMode ToolMode) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Provider` | `string` | — | +| [`Name`](ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `ContextWindowTokens` | `int` | — | +| `CompactThreshold` | `double?` | — | +| `InputPricePerMTokens` | `decimal` | — | +| `OutputPricePerMTokens` | `decimal` | — | +| `CacheReadPricePerMTokens` | `decimal` | — | +| `CacheWritePricePerMTokens` | `decimal` | — | +| `IsDefault` | `bool` | — | +| [`ToolMode`](../../Gabriel.Core/Configuration/ToolMode.cs.md) | [`ToolMode`](../../Gabriel.Core/Configuration/ToolMode.cs.md) | — | + + +AvailableModel is an immutable record that encapsulates a single model available from any registered provider in the UI's models dropdown. It is constructed by flattening each provider's Models via IModelCatalog to produce a unified, cross-provider view. The entry carries identity (Provider, Name) and operational characteristics (ContextWindowTokens and the optional CompactThreshold), pricing metadata (InputPricePerMTokens, OutputPricePerMTokens, CacheReadPricePerMTokens, CacheWritePricePerMTokens), a bootstrap indicator (IsDefault) that marks the catalog-wide default, and a ToolMode describing how tooling should interact with the model. + +## Remarks +AvailableModel plays the role of a catalog-agnostic descriptor for a model. It lets the UI present a unified dropdown across providers without depending on provider-specific types, while the immutable record ensures thread-safety and predictable sharing across components. The pricing fields and the context/window-related fields keep policy decisions centralized, so callers can estimate cost and capability without querying individual providers. The IsDefault flag marks the bootstrap choice for the entire catalog, making it straightforward to bootstrap the initial user experience. + +## Notes +- CompactThreshold is nullable; check HasValue before using it. +- Prices use decimal to preserve precision for token-cost calculations. +- IsDefault is catalog-scoped; ensure a single entry is designated as the bootstrap default to avoid startup ambiguity. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md new file mode 100644 index 0000000..358290e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md @@ -0,0 +1,195 @@ +# ChatProviderEvent.cs + +> **Source:** `src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs` + +## Contents + +- [ChatProviderEvent](#chatproviderevent) +- [FinishEvent](#finishevent) +- [ReasoningDeltaEvent](#reasoningdeltaevent) +- [TextDeltaEvent](#textdeltaevent) +- [ToolCallReadyEvent](#toolcallreadyevent) +- [FinishReason](#finishreason) + +--- + +## ChatProviderEvent +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs` +> **Kind:** record + +```csharp +public abstract record ChatProviderEvent +``` + + +ChatProviderEvent is the abstract base type for streaming events emitted by IChatProvider.StreamAsync. It represents a single event in the streaming lifecycle, while concrete event types carry the actual payload. The provider buffers partial tool-call deltas internally and only emits ToolCallReady once a call is fully assembled, so consumers do not need to reassemble JSON fragments. + +## Remarks + +By exposing a single base type, this abstraction enables consumers to handle diverse event shapes via pattern matching against derived types. It decouples event generation from consumption, allowing different IChatProvider implementations to emit their own concrete event kinds while exposing a uniform stream interface. The emphasis on emitting ToolCallReady only when a call is complete reduces the risk of consumers processing partial data and simplifies downstream logic. + +## Notes +- ChatProviderEvent is abstract; it cannot be instantiated directly. Derive concrete events to represent specific streaming scenarios. +- Derived events carry the actual payloads; ChatProviderEvent serves as a discriminated union for all possible stream events. + +--- + +## FinishEvent +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs` +> **Kind:** record + +```csharp +public sealed record FinishEvent(FinishReason Reason) : ChatProviderEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Reason` | `FinishReason` | — | + + +FinishEvent is a terminal chat-provider event that signals there is no more data to emit for the current turn. It carries a FinishReason and derives from ChatProviderEvent, enabling consumers to gracefully conclude a provider's emission path for the turn and react to the reason of termination when appropriate. + +## Remarks +FinishEvent serves as an explicit end-of-turn signal in the chat provider's event stream. Using a dedicated type simplifies downstream logic, allowing consumers to pattern-match on FinishEvent to finalize UI state or orchestration without inspecting payloads of ordinary events. As a sealed record, FinishEvent is immutable and identity-driven, which helps ensure deterministic behavior in the event pipeline. + +## Notes +- FinishEvent is a terminal event; once observed, it indicates the provider will emit nothing more for this turn. +- FinishReason describes why the provider finished; downstream code should rely on this to adjust flow or UI. +- Because FinishEvent is a record type, two instances with the same Reason compare as equal. + +--- + +## ReasoningDeltaEvent +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs` +> **Kind:** record + +```csharp +public sealed record ReasoningDeltaEvent(string Delta) : ChatProviderEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Delta` | `string` | — | + + +ReasoningDeltaEvent is a specialized event that carries an incremental reasoning token emitted by chat providers that expose a separate thinking stream. It wraps a single Delta string and derives from ChatProviderEvent, enabling clients to handle model thoughts distinctly from the final assistant reply. Use this when a provider supports reasoning streaming and you want to surface or log the model's intermediate steps in real time; if no reasoning channel is available, this event will not be emitted, and you should rely on the final message instead. + +## Remarks +ReasoningDeltaEvent exists to separate the model's ongoing thinking stream from its final reply. It helps UI layers and debugging tools surface intermediate tokens without conflating them with the polished output. Being a concrete subclass of ChatProviderEvent, it fits neatly into the provider event ecosystem while remaining optional for providers that don't emit reasoning data. + +## Notes +- Streaming tokens may arrive incrementally and represent partial thoughts; do not treat them as a complete transcript or a guaranteed representation of the model's final reasoning. +- Do not persist or display ReasoningDeltaEvent content as part of the final assistant transcript; keep it separate from the final output data. + + +--- + +## TextDeltaEvent +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs` +> **Kind:** record + +```csharp +public sealed record TextDeltaEvent(string Delta) : ChatProviderEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Delta` | `string` | — | + + +TextDeltaEvent is an immutable record that carries a single incremental piece of assistant text as it arrives in a streaming chat flow. Use it when chat output is delivered in chunks (deltas) rather than as a single complete string, so a consumer can append each Delta to build the full message. + +## Remarks +TextDeltaEvent participates in the ChatProviderEvent hierarchy and represents a granular unit of streamed text. Making it a sealed record preserves a stable, non-derivable signal for downstream consumers and enables safe pass-through, serialization, or queuing of discrete text fragments. Consumers typically accumulate deltas in order to present the complete message to the user while handling delivery asynchronously. + +## Notes +- Delta is immutable; do not mutate Delta after construction. +- The final message is formed by concatenating deltas in their emission order. +- This type is a ChatProviderEvent subtype; handle it via pattern matching or type checks to process text fragments specifically. + +--- + +## ToolCallReadyEvent +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs` +> **Kind:** record + +```csharp +public sealed record ToolCallReadyEvent(string Id, string Name, string ArgumentsJson) : ChatProviderEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `string` | — | +| [`Name`](ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `ArgumentsJson` | `string` | — | + + +ToolCallReadyEvent represents a prepared tool invocation within the chat-provider workflow. It carries the invocation's Id, the tool Name to run, and a JSON payload (ArgumentsJson) with the parameters required by the tool. Being a sealed record, it's immutable and participates in value-based equality, which makes it ideal for routing and deduplication in event pipelines. + +## Remarks +ToolCallReadyEvent exists to decouple the decision to run a tool from the mechanics of the chat provider. Handlers can pattern-match on this type to route to the correct tool runner, using Id for correlation and ArgumentsJson for runtime deserialization. Because it inherits from ChatProviderEvent, it fits into a family of provider-related events and can be consumed by generic pipelines alongside other events. + +## Notes +- Ensure the ArgumentsJson payload is valid JSON before dispatching to the tool runner. +- The Id should be unique for each invocation to avoid collisions in logs and traces. +- The type is immutable; avoid attempting to modify properties after construction. + +--- + +## FinishReason +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs` +> **Kind:** enum + +```csharp +public enum FinishReason +{ + Stop, + ToolCalls, + Length, + Error, +} +``` + + +FinishReason describes the termination condition of a generation cycle signaled by the ChatProviderEvent. It enumerates the four principal causes a provider may report when it finishes processing a request: Stop indicates natural, completed content generation; ToolCalls signals that the agent should perform additional tool invocations and resume the flow; Length means the model hit the token limit and may require continuation or adjustment; Error flags a provider-side failure that requires error handling. Consumers can switch on this value to route execution to the appropriate follow-up action (finalize and return content for Stop, invoke tools for ToolCalls, consider continuation or budget adjustments for Length, or handle/propagate the error for Error). + +## Remarks +FinishReason abstracts termination semantics from the caller, allowing the provider to communicate exact next steps without exposing internal state details. It supports both streaming and non-streaming flows by clearly signaling whether more work is required (ToolCalls) or if processing has concluded (Stop). By centralizing termination signals, it helps maintain consistent handling across the chat provider and its consumers. + +## Example +```csharp +// Example usage demonstrating common branching on finish reason +void HandleFinish(FinishReason finishReason) +{ + switch (finishReason) + { + case FinishReason.Stop: + // deliver final content to the user + break; + case FinishReason.ToolCalls: + // invoke required tools and resume generation + break; + case FinishReason.Length: + // token limit reached; decide on truncation or continuation + break; + case FinishReason.Error: + // surface or log the error and abort or retry as appropriate + break; + } +} +``` + +## Notes +- Length should trigger a well-defined strategy (e.g., request more tokens, adjust prompt, or gracefully truncate) rather than silent truncation. +- ToolCalls implies additional tool-invocation infrastructure is available; without it, this path cannot be exercised meaningfully. +- The enum represents cross-cutting termination semantics; ensure all layers consuming ChatProviderEvent map to these cases consistently. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs.md new file mode 100644 index 0000000..c960e43 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs.md @@ -0,0 +1,33 @@ +# ChatProviderMessage + +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs` +> **Kind:** record + +```csharp +public record ChatProviderMessage( + MessageRole Role, + string? Content = null, + string? ToolCallId = null, + IReadOnlyList? ToolCalls = null +) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Role` | [`MessageRole`](../../Gabriel.Core/Entities/MessageRole.cs.md) | — | +| `Content` | `string?` | `null` | +| `ToolCallId` | `string?` | `null` | +| `ToolCalls` | `IReadOnlyList?` | `null` | + + +Transport DTO for the IChatProvider boundary. Decoupled from the Message entity so providers don't depend on persistence concerns. It encodes all four message shapes supported by the OpenAI/xAI wire protocol: user/system with Content set and ToolCallId/ToolCalls null; assistant (text) with Content set; assistant (tool calls) with Content optional and ToolCalls set; and tool (observation) with Content set and ToolCallId set. This record exposes Role, Content, ToolCallId, and ToolCalls to transport the message across layers while preserving the semantics required by the protocol. + +## Remarks +By isolating transport data from domain entities, this type enables providers to implement cross-boundary communication without leaking persistence concerns. It acts as a serialization-friendly carrier that all IChatProvider implementations can agree on, regardless of how messages are stored or retrieved. The four shapes map directly to the OpenAI/xAI wire protocol, and ToolCalls are represented by ChatProviderToolCall, enabling tool invocation details to flow when present. + +## Notes +- The shapes rely on nullability to distinguish wire formats; populate only the fields relevant to the chosen shape. +- When using the assistant-with-tool-calls shape, Content may be omitted and ToolCalls must be non-null. +- ToolCallId is used only for the tool (observation) shape; it should not be set for user/system or assistant-with-tool-calls shapes. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs.md new file mode 100644 index 0000000..6f8c5dd --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs.md @@ -0,0 +1,22 @@ +# ChatProviderToolCall + +> **File:** `src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs` +> **Kind:** record + +```csharp +public record ChatProviderToolCall(string Id, string Name, string ArgumentsJson) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `string` | — | +| [`Name`](ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `ArgumentsJson` | `string` | — | + + +Represents an immutable data container for a chat provider's tool invocation. It captures the call's identity (Id), the target tool's name (Name), and the JSON-encoded arguments for that tool (ArgumentsJson). This is a lightweight value object intended for passing tool-call metadata through the chat/provider pipeline in a strongly-typed, copy-friendly manner. + +## Remarks +This type serves as a small, dedicated payload that decouples the notion of a tool call from the surrounding orchestration logic. By virtue of being a record, it provides value-based equality and convenient deconstruction, which simplifies comparisons, storage, and signaling of identical tool invocations across components. It centralizes the shape of a tool call, making it easy to serialize, log, or route without exposing internal dispatch details elsewhere in the system. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md new file mode 100644 index 0000000..4613502 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md @@ -0,0 +1,19 @@ +# IChatProvider + +> **File:** `src/api/Gabriel.Engine/Providers/IChatProvider.cs` +> **Kind:** interface + +```csharp +public interface IChatProvider +``` + + +IChatProvider defines a streaming, pluggable backend for chatting with LLM providers. It yields a sequence of ChatProviderEvent values incrementally through StreamAsync, enabling the agent loop to react in real time (e.g., for ReAct-style workflows) and surface partial results to the UI. Model selection is per-call: the agent resolves the user's PreferredModel (or the config default) into a modelName and passes it to StreamAsync. The provider itself remains stateless with respect to that choice, allowing interchangeable backends without embedding per-call state. + +## Remarks +IChatProvider serves as a clean abstraction boundary between the agent orchestration and the concrete chat backends. Implementations can range from mocks to production providers (e.g., Grok/xAI, OpenAI) without requiring callers to know specifics. The interface also enables testability and easier swapping via IChatProviderRegistry and the model/catalog infrastructure: a provider is chosen by name, models are offered by the catalog, and the agent supplies the history and tools for streaming. + +## Notes +- The StreamAsync method returns an asynchronous stream; callers should observe the CancellationToken to cancel streaming promptly. +- Providers are expected to be stateless with respect to the chosen model/provider; any per-call state should live in the history/tools passed in. +- The Name property must be a stable, unique identifier that IChatProviderRegistry.Resolve can use to select the provider. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md new file mode 100644 index 0000000..bdb81c2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md @@ -0,0 +1,68 @@ +# IChatProviderRegistry.cs + +> **Source:** `src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs` + +## Contents + +- [IChatProviderRegistry](#ichatproviderregistry) +- [ChatProviderRegistry](#chatproviderregistry) + +--- + +## IChatProviderRegistry +> **File:** `src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs` +> **Kind:** interface + +```csharp +public interface IChatProviderRegistry +``` + + +Maps a provider name to the concrete IChatProvider instance and is intended to be used when a chat provider must be selected at runtime by name rather than wired statically. The registry enables AgentService to resolve the appropriate IChatProvider (e.g., the one named "grok") at request time, while all providers are registered as singleton IChatProvider instances. + +## Remarks + +This abstraction decouples consumers from concrete provider implementations and centralizes the discovery and lifetime management of chat providers. It supports pluggability: you can add, remove, or swap providers without changing the consumers that request them, improving testability and configurability within the chat subsystem. + +## Example + +```csharp +// Enumerate available providers +foreach (string name in registry.AvailableProviders) +{ + Console.WriteLine(name); +} + +// Resolve a provider by name at runtime +IChatProvider grok = registry.Resolve("grok"); +// Use `grok` to perform chat-related operations (call-site depends on IChatProvider API) +``` + +## Notes + +- Lookup by provider name is exact; mismatches or unknown names may result in runtime failure when resolving. Ensure names are accurate and registered up-front. +- Providers are singletons; the implementation-backed IChatProvider should be thread-safe if shared across requests. +- Adding or removing providers should be coordinated with the application's startup configuration to ensure AvailableProviders and Resolve mappings remain consistent. + +--- + +## ChatProviderRegistry +> **File:** `src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs` +> **Kind:** class + +```csharp +public sealed class ChatProviderRegistry : IChatProviderRegistry +``` + + +The class provides a centralized registry for IChatProvider implementations, enabling runtime resolution of a provider by name. It builds a case-insensitive map of providers from the provided collection and exposes the available provider names for discovery and diagnostics. + +## Remarks +By centralizing provider lookup, ChatProviderRegistry decouples callers from concrete implementations and supports swapping providers at runtime without changing call sites. Using a case-insensitive key for provider names makes lookups forgiving of name casing, while the registry remains a simple, readonly map after construction. + +## Notes +- Duplicate provider names differing only by case will cause an exception during construction due to duplicate keys. +- Resolve(string) throws InvalidOperationException when there is no registered provider with the requested name; the exception message lists available providers to aid debugging. +- AvailableProviders is a read-only collection view of the registered provider names and is effectively immutable after construction. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IModelCatalog.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IModelCatalog.cs.md new file mode 100644 index 0000000..60c8f91 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/IModelCatalog.cs.md @@ -0,0 +1,18 @@ +# IModelCatalog + +> **File:** `src/api/Gabriel.Engine/Providers/IModelCatalog.cs` +> **Kind:** interface + +```csharp +public interface IModelCatalog +``` + + +The IModelCatalog interface provides a read-only catalog of every model exposed by all registered IChatProvider implementations and exposes the resolver the agent uses to map a user’s preferences into a concrete ModelSelection. It remains an interface to enable test doubles and future discovery strategies (such as dynamic model discovery) to be swapped in without changing callers. + +## Remarks +This abstraction centralizes model discovery and user-preference resolution, decoupling the agent's selection logic from concrete provider implementations. It exposes an immutable list of available models and a single Resolve entry point that enforces the precedence rules: an exact match on the provided preferences wins; if there is no match, the default is the config-declared IsActive model; if that also does not exist, the first registered model is used as a last resort. This design makes it straightforward to mock or substitute the catalog in tests and to evolve discovery strategies over time without impacting callers. + +## Notes +- Fallback semantics depend on the IsActive flag on AvailableModel; if none exist, the first registered model is used as a last resort. +- Null or unmatched preferences are treated as non-matches and trigger the defaulting behavior. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ModelCatalog.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ModelCatalog.cs.md new file mode 100644 index 0000000..a4da3aa --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ModelCatalog.cs.md @@ -0,0 +1,19 @@ +# ModelCatalog + +> **File:** `src/api/Gabriel.Engine/Providers/ModelCatalog.cs` +> **Kind:** class + +```csharp +public sealed class ModelCatalog : IModelCatalog +``` + + +ModelCatalog collects all AvailableModel entries from registered IChatProvider instances into a single, cached catalog and resolves to a ModelSelection. It builds the catalog once during construction (leveraging singleton providers), so subsequent Resolve calls are cheap. Use Resolve with optional preferredProvider and preferredModel to select a specific model; if no exact match exists, the precomputed default is returned. + +## Remarks +ModelCatalog centralizes model discovery across providers and caches the result to avoid repeated walking of models. It establishes a deterministic bootstrap default by selecting the first active model (IsActive) it encounters; if none are active, it falls back to the first registered model. If there are no models at all, construction throws InvalidOperationException to fail fast in misconfigured deployments. The resolver is side-effect-free from the caller's perspective: Resolve returns a fresh ModelSelection that reflects either the explicit match or the default, without mutating internal state. + +## Notes +- If no models are registered, the constructor throws InvalidOperationException with a message indicating misconfiguration (e.g., no chat models registered; suggest registering Mock or at least one provider). +- Provider-name matching in Resolve is case-insensitive, but model-name matching is exact (Ordinal), which means case differences in the model name will prevent a match even if the provider name matches. +- The catalog is built once at construction time and remains a read-only snapshot for the lifetime of the ModelCatalog instance; changes to providers after construction will not be reflected unless a new ModelCatalog is created. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md new file mode 100644 index 0000000..07818d7 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md @@ -0,0 +1,366 @@ +# GabrielToolBridge.cs + +> **Source:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` + +## Contents + +- [GabrielToolBridge](#gabrieltoolbridge) +- [GabrielToolBridge (constructor)](#gabrieltoolbridge-constructor) +- [Models](#models) +- [Name](#name) +- [AppendFixupMessage](#appendfixupmessage) +- [BuildToolDocs](#buildtooldocs) +- [EscapeJsonString](#escapejsonstring) +- [InjectToolDocs](#injecttooldocs) +- [SerializeCallInline](#serializecallinline) +- [StreamAsync](#streamasync) +- [TranslateHistory](#translatehistory) +- [MaxParseRetries](#maxparseretries) + +--- + +## GabrielToolBridge +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** class + +```csharp +public sealed class GabrielToolBridge : IChatProvider +``` + + +Bridges a tool-incapable chat provider (for example, a local Ollama model or a commercial model without function-calling) to the IChatProvider shape expected by the rest of the agent stack. Use this when you need the agent and UI to receive the same event stream and tool-call events from a provider that cannot natively emit tool-call frames — the bridge rewrites history, injects tool documentation, and converts the provider's text output into ToolCall events. + +## Remarks +GabrielToolBridge adapts a non-tooling wire protocol so the agent loop (RunStreamAsync, AgentContext, UI) can operate unchanged. It does this by: translating prior structured tool interactions into inline markers in the assistant text and labeled user messages; appending a system message that describes available tools and the expected inline wire format; streaming the inner provider's deltas until a marker appears and buffering the remainder; and finally parsing buffered output into ToolCallReady events and ToolCalls. The bridge does not execute tools itself — tool execution remains the responsibility of AgentService/ITool — and the inner provider is invoked with an empty tools list because the tool descriptors are embedded in the system prompt. The implementation also performs a small retry loop (MaxParseRetries = 2) to attempt to repair parse failures, with a design tradeoff that only the first attempt is live-streamed. + +## Notes +- Live (typewriter) streaming only occurs on the first parse attempt; retries suppress re-streaming of corrected text and emit only the corrected tool calls. +- Tool descriptors must be consumable from the injected system message because the inner provider receives an empty tools list. +- If the inner provider unexpectedly emits native tool-call events, the bridge will pass them through rather than blocking them. + +--- + +## GabrielToolBridge (constructor) +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** constructor + +```csharp +public GabrielToolBridge(IChatProvider inner, ILogger logger) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `inner` | [`IChatProvider`](../IChatProvider.cs.md) | — | +| `logger` | `ILogger` | — | + + +GabrielToolBridge’s constructor wires the bridge to a chat provider and a logger. By accepting an IChatProvider and an `ILogger` and storing them internally, it prepares the bridge to forward chat events and log its activity as part of its tool-bridging responsibilities. This pattern supports dependency injection and makes the bridge easier to test by supplying test doubles for its collaborators. + +## Remarks +GabrielToolBridge acts as a thin adapter around a chat-providing component and a logging channel. The constructor’s two dependencies establish the bridge’s collaboration with the chat layer and the logging infrastructure, allowing the rest of the bridge to focus on bridging tool interactions rather than sourcing messages or emitting logs. This design favors testability and configurability via DI, since the behavior can be varied by swapping the IChatProvider and logger. + +## Notes +- The constructor does not perform null-check validation; ensure non-null values are passed to avoid later NullReferenceException when the bridge uses its collaborators. +- When used in a dependency injection setup, configure the container to supply concrete implementations for IChatProvider and `ILogger` to avoid runtime surprises. + +--- + +## Models +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** property + +```csharp +public IReadOnlyList Models => _inner.Models +``` + + +Exposes a read-only collection of LLMModel instances that are available through the underlying bridge. This property simply forwards to the inner provider’s Models collection, offering a stable surface for enumerating model metadata (such as name, activity, context window, pricing, and tool mode) without allowing callers to mutate the collection. + +## Remarks +This property acts as a thin abstraction over the inner provider, decoupling consumers from the concrete implementation of the model source. By returning `IReadOnlyList`, it supports discovery and inspection of available models while preserving encapsulation—mutations must occur through the inner provider itself or its configuration surface. + +## Notes +- The returned collection represents a live view of the inner provider; updates to _inner.Models will be visible on subsequent reads. +- The collection is read-only; you cannot add or remove models via this property. To change the set of models, modify the inner provider and re-query. +- The LLMModel items may themselves be mutable. If you need a stable snapshot, copy the list (and/or its items) to your own structure before performing long-running operations or cross-thread work. + +--- + +## Name +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** property + +```csharp +public string Name => _inner.Name +``` + + +The Name property simply returns the wrapped provider’s Name, acting as a transparent passthrough. By delegating to the inner provider, the decorator preserves the underlying provider’s identity, ensuring the agent registry continues to resolve by the base provider’s name and models remain owned by the wrapped provider. + +## Remarks +This decorator pattern is identity-preserving: it allows augmentation of behavior in front of a provider without altering how the provider is identified. Callers can rely on the underlying Name for routing, logging, and diagnostics, while the wrapper itself can add cross-cutting concerns elsewhere. The separation keeps decoration concerns orthogonal to identity resolution. + +## Notes +- Null-safety: _inner must be non-null; accessing Name will throw if the inner provider is not initialized. +- Immutability expectation: after construction, the inner provider should not be swapped if stability of identity is required; changing it could change the reported Name unexpectedly. + +--- + +## AppendFixupMessage +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** method + +```csharp +private static IReadOnlyList AppendFixupMessage( + IReadOnlyList history, + string fullModelOutput, + ToolCallParseException ex) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `history` | `IReadOnlyList` | — | +| `fullModelOutput` | `string` | — | +| `ex` | [`ToolCallParseException`](ToolCallBlockParser.cs.md) | — | + +**Returns:** `IReadOnlyList` + + +AppendFixupMessage creates a retriable conversation state after a parse failure by appending the model’s last output and a fix-up prompt that instructs re-issuing from scratch with a valid tool_call block. + +## Remarks +AppendFixupMessage acts as a focused retry mechanism within the ToolBridge’s fix-up flow. It preserves the original conversation context by keeping the existing history, then appends the model’s previous assistant message followed by a user-facing instruction that requests a clean retry with a correctly formatted block and a JSON-encoded arguments object. By explicitly forbidding code fences and requiring a JSON object for arguments, it enforces the contract expected by the downstream parsing/dispatch logic and reduces the chance of repeated parse errors. + +## Notes +- The method returns a new IReadOnlyList containing the original history, the assistant’s latest output, and the fix-up user message; it does not mutate the input history. +- It relies on ex.OffendingBlock and ex.Message to surface concrete diagnostics to guide the retry; if the offending block is absent, the fix-up content may be less informative. +- The behavior hinges on the ChatProviderMessage and MessageRole constructs to classify and carry the multi-turn dialogue through the retry step. + + +--- + +## BuildToolDocs +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** method + +```csharp +private static string BuildToolDocs(IReadOnlyList tools) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `tools` | `IReadOnlyList` | — | + +**Returns:** `string` + + +BuildToolDocs synthesizes a Markdown guide from a collection of ToolDescriptor objects, turning tool metadata into a consistent, user-facing reference for interactive tool usage. It outputs a block that begins with a [Tool calling] section, explains how to emit inline tool invocation blocks using the exact {"name":"","arguments":{...}} syntax, and enumerates each available tool by name, description, and a JSON schema of its parameters. This function is intended for scenarios where a user or agent needs a precise, copy-pasteable template to discover and invoke tools at runtime, rather than performing tool calls directly in code. + +## Remarks +By centralizing tool metadata formatting in BuildToolDocs, the system ensures consistent guidance across different UI surfaces. It decouples the presentation from the tool definitions, so adding or updating tools only requires updating ToolDescriptor instances. The function also formalizes the interaction contract for tool invocation, reducing ambiguity for end users and downstream tooling. + +## Notes +- The input must be non-null; passing null will throw a NullReferenceException. +- If ToolDescriptor.Name, Description, or ParametersJsonSchema are null or empty, the generated output may contain blanks; ensure these fields are populated. + +--- + +## EscapeJsonString +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** method + +```csharp +private static string EscapeJsonString(string s) => + s.Replace("\\", "\\\\").Replace("\"", "\\\"") +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `s` | `string` | — | + +**Returns:** `string` + + +Escapes backslashes and double quotes in a string so it can be embedded safely inside a JSON string literal. Use this when manually constructing JSON fragments to ensure literal backslashes and quotation marks do not terminate the string or corrupt syntax. + +## Remarks +This method centralizes the escaping logic, so callers don't repeat Replace calls in multiple places. It assumes the input is raw text to be placed inside a JSON string; if you pass already escaped content, you may end up double-escaping. It does not perform a full JSON escaping pass; for complete safety, prefer a proper JSON serializer. + +## Example +```csharp +string raw = "A \"quote\" and a \\ backslash"; +string escaped = EscapeJsonString(raw); +// escaped == "A \\\"quote\\\" and a \\\\ backslash" +``` + +## Notes +- Applying EscapeJsonString more than once can double-escape quotes/backslashes; ensure you escape only raw input once or rely on a structured JSON serializer. + +--- + +## InjectToolDocs +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** method + +```csharp +private static IReadOnlyList InjectToolDocs( + IReadOnlyList history, + IReadOnlyList tools) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `history` | `IReadOnlyList` | — | +| `tools` | `IReadOnlyList` | — | + +**Returns:** `IReadOnlyList` + + +InjectToolDocs augments a chat history with a system-level description of the tools available to the model by inserting a dedicated system message. When tools are provided, it builds the documentation payload with BuildToolDocs and inserts it immediately after the initial block of system messages; if no tools are provided, the history is returned unchanged. + +## Remarks +By placing the tool-descriptions under the System role, this helper centralizes how tool capabilities are conveyed (via BuildToolDocs) and keeps the normal user/assistant history intact. It computes a stable insertion point based on the first non-system message, ensuring tool docs appear early without reordering existing messages more than necessary. The approach minimizes coupling between history construction and the tool-descriptor formatting. + +## Notes +- If the history contains no non-system messages, the docs message is appended after all system messages. +- The method returns a new IReadOnlyList and does not mutate the input history. + +--- + +## SerializeCallInline +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** method + +```csharp +private static string SerializeCallInline(string name, string argumentsJson) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `name` | `string` | — | +| `argumentsJson` | `string` | — | + +**Returns:** `string` + + +SerializeCallInline is a private helper that builds a JSON payload representing a single tool invocation for the bridge. It accepts a tool name and a JSON string of arguments, validates the arguments as well-formed JSON (falling back to {} if invalid or empty), and returns a string of the form {"name":"","arguments":} suitable for transmission. + +## Remarks +It centralizes the encoding and validation of tool-call data inside Gabriel.ToolBridge. The method escapes the tool name to prevent JSON-escaping issues and uses JsonDocument.Parse to ensure the provided arguments are well-formed JSON; if not, it safely substitutes {} rather than failing. Being private, this is an internal implementation detail rather than part of the public API. + +--- + +## StreamAsync +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** method + +```csharp +public async IAsyncEnumerable StreamAsync( + IReadOnlyList history, + IReadOnlyList tools, + string modelName, + [EnumeratorCancellation] CancellationToken ct = default) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `history` | `IReadOnlyList` | — | +| `tools` | `IReadOnlyList` | — | +| `modelName` | `string` | — | +| `ct` | `CancellationToken` | `default` | + +**Returns:** `IAsyncEnumerable` + + +StreamAsync wraps an inner chat-provider to deliver a stream of ChatProviderEvent events while transparently handling tool invocations and tool-descriptor context. + +It accepts a history of chat messages, a set of tool descriptors, and a model identifier, then yields events such as textual deltas, reasoning deltas, tool-call readiness, and finish signals. The wrapper injects tool documentation into the system prompt, then streams from the inner provider in two phases: a live, first pass and a fallback retry pass. In live mode, deltas are parsed by a ToolCallStreamSplitter and emitted as they arrive, exposing tool-call candidates only when complete markers are detected. If no marker is observed, the partial text is buffered and later emitted if a retry occurs. In retry mode, deltas are accumulated until a candidate tool-call marker is found; text is emitted in bulk when appropriate, ensuring tool calls are surfaced even when the underlying model doesn’t reliably expose them in real time. + +The method forwards non-tool events (ReasoningDeltaEvent) unchanged and propagates any ToolCallReadyEvent produced by the inner provider. It also preserves the inner finish reason for the turn, and, if a native tool-call appears despite an empty tool list, forwards it to the caller for safety. The overall effect is a robust, tool-aware streaming surface that coordinates between the inner model, tool documentation, and tool-execution flow. + +## Remarks +StreamAsync acts as a decorator around the raw inner provider, decoupling the model’s streaming quirks from the caller’s expectations about tool invocation. By injecting tool-descriptor context and by implementing a live-vs-retry strategy, it guarantees that tool usage is discoverable and correctly surfaced, even when the underlying model emits text without explicit tool-call markers. The splitting logic (ToolCallStreamSplitter) and the handling of various ChatProviderEvent subtypes work together to present a coherent, tool-aware stream to UI or orchestrator code. + +## Example +```csharp +// Example usage: consume a tool-aware stream of events +await foreach (var evt in gabrielBridge.StreamAsync(history, tools, modelName, ct)) +{ + switch (evt) + { + case TextDeltaEvent td: + // render or accumulate textual content + break; + case ReasoningDeltaEvent rd: + // display model reasoning if desired + break; + case ToolCallReadyEvent tcre: + // a tool invocation has been detected and is ready to execute + break; + case FinishEvent fe: + // end of this turn/stream segment + break; + } +} +``` + +## Notes +- The stream hinges on a concrete marker () to identify tool-candidate text; changes to this format would require corresponding adjustments. +- If the inner provider unexpectedly emits native tool calls while the tools list is empty, those calls are forwarded verbatim (the implementation errs on safety by honoring such calls). +- The parameter MaxParseRetries controls how many retry attempts are attempted; higher values increase resilience to parsing quirks but add latency. + +--- + +## TranslateHistory +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** method + +```csharp +private static IReadOnlyList TranslateHistory(IReadOnlyList history) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `history` | `IReadOnlyList` | — | + +**Returns:** `IReadOnlyList` + + +Translates a chat history into a wire-format representation that a non-tool-capable model can understand by embedding tool invocations inline and labeling tool results for human-readable consumption. It first builds a map from tool call IDs to tool names by scanning assistant messages that include tool calls, then constructs a translated history. For assistant messages containing tool calls, it appends inline blocks (serialized via SerializeCallInline) after the original content and returns a single transformed assistant message. For tool messages, it emits a user-facing line in the form "[Tool result: ] " using the resolved tool name or "unknown" if the mapping isn’t available. All other messages are passed through unchanged. The result is an `IReadOnlyList` that represents the history in a format suitable for non-tool-capable models. + +--- + +## MaxParseRetries +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs` +> **Kind:** field + +```csharp +private const int MaxParseRetries = 2 +``` + + +MaxParseRetries fixes the maximum number of parsing attempts to three total (the initial parse plus two retries). It follows the EmptyStopMaxRetries pattern used elsewhere in the agent loop, ensuring consistent retry semantics for parsing operations within GabrielToolBridge. + +## Remarks +By encapsulating the limit in a private constant, this value expresses a fixed retry policy that cannot be changed at runtime. It avoids sprinkling literal numbers across parsing code and makes the relationship to other retry patterns explicit. This centralization supports predictable behavior and easier auditing of the agent's parsing flow. + +## Notes +- This is a compile-time constant; it cannot be changed at runtime. If you need runtime configurability, consider making it configurable or injectable and wired through to the parsing logic. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md new file mode 100644 index 0000000..3d2a0fd --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md @@ -0,0 +1,106 @@ +# ToolCallBlockParser.cs + +> **Source:** `src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs` + +## Contents + +- [ToolCallBlockParser](#toolcallblockparser) +- [ToolCallParseException](#toolcallparseexception) +- [ParsedToolCall](#parsedtoolcall) + +--- + +## ToolCallBlockParser +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs` +> **Kind:** class + +```csharp +internal static class ToolCallBlockParser +``` + + +ToolCallBlockParser extracts all blocks from a buffered text tail and converts each into a ParsedToolCall, assigning a deterministic synthetic id to preserve cross-turn correlation. Use it when the model's output interleaves narrative with tool invocations so you can validate, summarize, and dispatch each call to the tool layer. + +## Remarks +Its parsing is centralized and strict: it requires a closing tag for every opening and validates that the body is valid JSON with a string 'name' field and an object 'arguments' (which may be omitted, defaulting to {})). Any parse failure raises ToolCallParseException, which triggers the surrounding retry loop. The synthetic id (prefixed with emu_) helps correlate results across the turn, making tool_call_id matching reliable in transport layers. The implementation also normalizes missing arguments to an empty object, so tools can be invoked without requiring explicit arguments. + +## Example +```csharp +string text = "{\"name\":\"Echo\",\"arguments\":{\"text\":\"hello\"}}"; +var calls = ToolCallBlockParser.Extract(text); +foreach (var call in calls) +{ + Console.WriteLine($"{call.Id} - {call.Name} - {call.ArgumentsJson}"); +} +``` + +## Notes +- Unterminated blocks throw ToolCallParseException with a descriptive message. +- Missing or non-string 'name' results in a JsonException describing the requirement. +- The generated id is synthetic and unique per turn, including an index suffix to avoid collisions. + +--- + +## ToolCallParseException +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs` +> **Kind:** class + +```csharp +internal sealed class ToolCallParseException : Exception +``` + + +Thrown when at least one block in the buffered turn text was malformed (missing close tag, invalid JSON, wrong shape). GabrielToolBridge catches this to ask the model to fix its previous response and retries up to GabrielToolBridge.MaxParseRetries times before giving up. The exception carries the offendingBlock via the OffendingBlock property so the repair process can quote the exact fragment back to the model and guide the repair. + +## Remarks +The ToolCallParseException represents a focused failure mode for tool invocation parsing. It isolates parsing errors from the normal control flow, enabling a targeted repair/retry cycle around tool blocks. By preserving the exact offending fragment, it provides precise context for the repair step while keeping surrounding state intact. The internal sealed nature confines this error pathway to the Gabriel.Engine tooling surface, ensuring a single, consistent handling strategy. + +## Notes +- The OffendingBlock content may be large or sensitive; avoid exposing it in user-facing logs or messages beyond what is necessary for repair. +- If an inner exception is supplied, preserve it to retain the full debugging trail. +- This exception is specific to the tool-block parser and is not intended for general-purpose parsing errors in other subsystems. + +--- + +## ParsedToolCall +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs` +> **Kind:** record + +```csharp +internal sealed record ParsedToolCall(string Id, string Name, string ArgumentsJson) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Id` | `string` | — | +| [`Name`](GabrielToolBridge.cs.md) | `string` | — | +| `ArgumentsJson` | `string` | — | + + +ParsedToolCall is an immutable, value-based carrier that captures a single parsed tool invocation. It stores an identifier, the tool name, and a JSON string of arguments produced by the parsing stage. + +## Remarks +Because it is a sealed record, it provides structural equality and convenient deconstruction, making it easy to compare calls or extract the three components in a pattern-friendly way. It serves as a stable data contract between the ToolCallBlockParser and downstream components that execute or log tool invocations, ensuring callers aren’t coupled to parsing internals. + +## Example +```csharp +// Example: constructing and inspecting a parsed tool call +var call = new ParsedToolCall("call-42", "BuildProject", "{\"project\":\"src\"}"); + +// Access members directly +string id = call.Id; +string name = call.Name; +string args = call.ArgumentsJson; + +// Or deconstruct +var (deconstructedId, deconstructedName, deconstructedArgs) = call; +``` + +## Notes +- ArgumentsJson is a JSON-encoded string; no validation is performed by this type. +- The type is internal to its assembly, so usage is intended within the ToolBridge parsing/execution flow. +- Equality on ParsedToolCall is value-based (by Id, Name, and ArgumentsJson) due to it being a record. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs.md new file mode 100644 index 0000000..5caf2ba --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs.md @@ -0,0 +1,35 @@ +# ToolCallStreamSplitter + +> **File:** `src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs` +> **Kind:** class + +```csharp +internal sealed class ToolCallStreamSplitter +``` + + +ToolCallStreamSplitter is a small, stateful helper that splits a continuous text delta stream into live, emit-ready text and a buffered tail starting at the first marker. It preserves a typewriter-like flow for ordinary text while ensuring no partial tool-markers are emitted; once the marker is detected, the remainder is buffered for downstream parsing of complete blocks. The splitter tracks the next possible marker prefix in a candidate buffer (limited in length to the marker) to minimize re-emission and correctly handle delta boundaries. + +## Remarks +This abstraction separates streaming text from the parsing of embedded tool-calls. It solves the problem of markers potentially being split across deltas by buffering the open tag and all subsequent data, enabling a downstream parser to reliably extract blocks without corrupting the live text stream. It exposes simple state information (InBufferMode and BufferedText) so callers can inspect progress and the buffered tail without threading through internal state. + +## Example +```csharp +var splitter = new ToolCallStreamSplitter(); + +// normal text arrives and is emitted immediately +string live1 = splitter.Process("Intro: "); +Console.Write(live1); // Intro: + +// a marker start arrives in the same delta +string live2 = splitter.Process("doSomething done"); +Console.Write(live2); // (empty) + +bool inBuffer = splitter.InBufferMode; // true +string buf = splitter.BufferedText; // "doSomething" +``` + +## Notes +- Not thread-safe; the instance maintains mutable state and should be used from a single thread unless external synchronization is applied. +- The marker value is a fixed constant (""); the behavior depends on recognizing this exact string, so changes to the marker would require corresponding adjustments in usage. +- If end-of-stream is reached without encountering a full marker, call Flush to emit any trailing text that did not form a marker; otherwise that text remains un-emitted. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolDescriptor.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolDescriptor.cs.md new file mode 100644 index 0000000..ab6c595 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Providers/ToolDescriptor.cs.md @@ -0,0 +1,27 @@ +# ToolDescriptor + +> **File:** `src/api/Gabriel.Engine/Providers/ToolDescriptor.cs` +> **Kind:** record + +```csharp +public record ToolDescriptor(string Name, string Description, string ParametersJsonSchema) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| [`Name`](ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `Description` | `string` | — | +| `ParametersJsonSchema` | `string` | — | + + +ToolDescriptor is a provider-facing, serializable descriptor for a tool exposed by the engine. It combines a human-friendly Name, a Description of the tool, and a ParametersJsonSchema that defines the tool's argument shape as a JSON Schema. Because ToolDescriptor is a C# record, it is immutable and participates in value-based equality, making it ideal for use as metadata across discovery, UI rendering, and request construction without coupling to the tool's implementation. + +## Remarks +ToolDescriptor serves as the contract between tool implementations and the surfaces that present or invoke them. It decouples metadata from behavior: consumers can enumerate tools, show labels, and validate input purely from this descriptor, while the actual invocation logic remains in separate components. The ParametersJsonSchema acts as a lightweight, language-agnostic description of accepted parameters, enabling dynamic forms and input validation by models without inspecting tool internals. The immutability of ToolDescriptor ensures safe sharing across threads and boundaries. + +## Notes +- The ParametersJsonSchema must be a valid JSON Schema text; invalid content is a runtime error for tooling components. +- This descriptor is purely metadata; do not embed executable code or environment-sensitive data in Name/Description; keep sensitive details out. +- As a record, equality is structural; to update metadata, create a new instance rather than mutating. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Frame.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Frame.cs.md new file mode 100644 index 0000000..4696975 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Frame.cs.md @@ -0,0 +1,44 @@ +# Frame + +> **File:** `src/api/Gabriel.Engine/Sequence/Frame.cs` +> **Kind:** record + +```csharp +public sealed record Frame(byte[] Pixels) +{ + public const int Width = 16; + public const int Height = 16; + public const int PixelCount = Width * Height; + + public byte At(int x, int y) => Pixels[y * Width + x]; +} +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Pixels` | `byte[]` | — | + + +Frame is an immutable, 16×16 frame that stores pixel data as palette indices. The Pixels array holds 256 bytes, each representing a index into a Palette, and the At(x, y) method retrieves the index for a given coordinate using row-major ordering (y * Width + x). This abstraction separates pixel data from color data and provides a straightforward way to render a frame by looking up colors in a Palette. + +## Remarks +Frame encapsulates a fixed-size, palette-indexed pixel block and exposes a minimal API (At) for access. It serves as the primitive unit in a sequence of frames, allowing rendering code to translate indices into colors via a Palette without duplicating color data or logic. + +## Example +```csharp +// Create a 16×16 frame with all indices set to 0 +var frame = new Frame(new byte[Frame.PixelCount]); + +// Read the palette index for pixel at (5, 7) +byte index = frame.At(5, 7); + +// Render that pixel by looking up the color in a Palette +int color = palette[index]; +``` + +## Notes +- At does not perform explicit bounds checking; callers must ensure 0 <= x < Width and 0 <= y < Height. +- The Pixels length is expected to be 256 (Frame.PixelCount); the constructor does not validate length, so mis-sized arrays may lead to incorrect results or runtime exceptions. +- Frame is a sealed record, emphasizing immutability and value-based equality, making it a stable unit for rendering pipelines and caching. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md new file mode 100644 index 0000000..53b1d8e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md @@ -0,0 +1,62 @@ +# FrameLayer.cs + +> **Source:** `src/api/Gabriel.Engine/Sequence/FrameLayer.cs` + +## Contents + +- [FrameLayers](#framelayers) +- [FrameLayer](#framelayer) + +--- + +## FrameLayers +> **File:** `src/api/Gabriel.Engine/Sequence/FrameLayer.cs` +> **Kind:** class + +```csharp +public static class FrameLayers +``` + + +FrameLayers is a compact static helper that defines the partitioning of frames into layers and exposes two helpers to work with that partitioning. Use Of(frameIndex) to map a frame index to its corresponding FrameLayer, and Range(layer) to obtain the inclusive start and end indices for a given layer. + +## Remarks +FrameLayers centralizes the framing logic, tying the concept of layers to the FrameLayer enum and ensuring consistent calculations across the codebase. By consolidating constants (FramesPerLayer, LayerCount) and simple arithmetic into a single place, it reduces duplication and the risk of off-by-one errors when iterating or validating layer ranges. The static nature of the class makes it a readily accessible utility for any component that processes frames in groups, enabling straightforward, batched operations over each layer. + +## Notes +- Range uses an inclusive end bound; if you need an exclusive upper bound, adjust accordingly. +- Of relies on integer division; negative frameIndex maps to a lower layer due to truncation, and there is no input validation performed. + +--- + +## FrameLayer +> **File:** `src/api/Gabriel.Engine/Sequence/FrameLayer.cs` +> **Kind:** enum + +```csharp +public enum FrameLayer +{ + DnaCore = 0, + StableTraits = 1, + Context = 2, + LiveState = 3, +} +``` + + +FrameLayer encodes the logical tier of the 64-frame timeline used by the engine to separate immutable identity, slow-changing traits, contextual reactions, and the live state. It provides a fixed, easily navigable partition of frames into four layers: DnaCore (0), StableTraits (1), Context (2), and LiveState (3). The layer for any given frame index is obtained by dividing the index by 16 and casting the result to FrameLayer, ensuring quick, centralized categorization without scattered index arithmetic. + +## Remarks +FrameLayer serves as an architectural boundary that clarifies how data should be treated across the frame lifecycle. Identity data stored in DnaCore is immutable after generation, while StableTraits drift very slowly, Context captures medium-term reactions, and LiveState is recomputed each turn. This separation reduces cross-cutting concerns and makes it simpler to apply appropriate update strategies, since components can reason about identity, trait drift, context, and live state in isolation before integrating results. + +## Example +```csharp +int frameIndex = 37; +FrameLayer layer = (FrameLayer)(frameIndex / 16); // 37 / 16 == 2 -> FrameLayer.Context +``` + +## Notes +- Validate that the frame index is within 0..63; otherwise, (frameIndex / 16) may yield 4 or higher, which does not correspond to any FrameLayer value. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md new file mode 100644 index 0000000..4109f29 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md @@ -0,0 +1,92 @@ +# GabrielSequence.cs + +> **Source:** `src/api/Gabriel.Engine/Sequence/GabrielSequence.cs` + +## Contents + +- [GabrielSequence](#gabrielsequence) +- [SequenceMetadata](#sequencemetadata) + +--- + +## GabrielSequence +> **File:** `src/api/Gabriel.Engine/Sequence/GabrielSequence.cs` +> **Kind:** record + +```csharp +public sealed record GabrielSequence( + int Version, + Palette Palette, + IReadOnlyList Frames, + SequenceMetadata Metadata) +{ + public const int FrameCount = 64; + public const int SchemaVersion = 1; + + public Frame this[int index] => Frames[index]; + public Frame this[FrameLayer layer, int frameInLayer] + => Frames[(int)layer * FrameLayers.FramesPerLayer + frameInLayer]; +} +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Version` | `int` | — | +| `Palette` | `Palette` | — | +| `Frames` | `IReadOnlyList` | — | +| `Metadata` | `SequenceMetadata` | — | + + +GabrielSequence is an immutable container that represents a 64-frame, palette-indexed sequence of 16×16 frames along with a shared Palette and metadata. It does not persist rendered bytes; instead, the sequence is generated on-demand from a seed and ConversationState by IGabrielSequenceGenerator, allowing rendering without storing heavy raster data. + +## Remarks +GabrielSequence provides a stable, serializable contract for a composite animation. It separates generation from storage, enabling the engine to reconstruct frames on demand from the current conversation context while keeping a lightweight in-memory record of the sequence's structure. Frames are exposed through two access patterns: a simple int indexer for linear frame order and a two-argument indexer that maps to per-layer framing using FrameLayers.FramesPerLayer. The latter relies on the shared per-layer layout to support efficient per-layer rendering workflows. + +## Notes +- The Frames collection must contain exactly FrameCount (64) frames. +- Out-of-range access will throw the standard indexing exceptions if the computed index is outside the Frames list. +- The (FrameLayer, int) indexer relies on FrameLayers.FramesPerLayer and the total layout; ensure these values align with your data to avoid misaddressing frames. + +--- + +## SequenceMetadata +> **File:** `src/api/Gabriel.Engine/Sequence/GabrielSequence.cs` +> **Kind:** record + +```csharp +public sealed record SequenceMetadata( + long Seed, + DateTimeOffset GeneratedAt, + string? StateSummary) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Seed` | `long` | — | +| `GeneratedAt` | `DateTimeOffset` | — | +| `StateSummary` | `string?` | — | + + +SequenceMetadata is an immutable value object that captures the seed used for sequence generation, when the sequence was produced, and an optional human-readable summary of the live state that drove generation. It provides a compact bundle of inputs so callers can reproduce, debug, and trace a generated sequence. + +## Remarks +It groups generation inputs into a single, immutable token that downstream components can store or compare. Being a sealed record provides value-based equality and guarantees immutability, which makes it reliable to pass around without risk of mutation. The StateSummary field is optional and intended for quick human-readable context used during debugging and observation passes. + +## Example +```csharp +var metadata = new SequenceMetadata( + Seed: 1234567890L, + GeneratedAt: DateTimeOffset.UtcNow, + StateSummary: "playful, short-message rhythm, 4 turns in" +); +``` + +## Notes +- StateSummary is nullable; handle nulls gracefully if you rely on it for logic. +- Because SequenceMetadata is a record, equality is value-based: two instances are equal when all three properties match. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md new file mode 100644 index 0000000..87d3fec --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md @@ -0,0 +1,19 @@ +# GabrielSequenceGenerator + +> **File:** `src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs` +> **Kind:** class + +```csharp +public sealed class GabrielSequenceGenerator : IGabrielSequenceGenerator +``` + + +GabrielSequenceGenerator is a sealed class that builds a deterministic GabrielSequence by combining a seed-derived palette with a chosen visual primitive and orchestrating four layered animation passes. It exposes a single Generate method that accepts a seed, an optional ConversationState, and optional overrides for pattern or palette, and returns a 64-frame sequence distributed across four layers (DNA Core, Traits, Context, Live) with layer-specific timing, palette windows, and intensity. The resulting sequence is ready-to-use for personality-driven visuals without writing per-frame rendering logic yourself. + +## Remarks +GabrielSequenceGenerator acts as the central orchestrator for Gabriel visuals, tying together PaletteTemplates, the PatternBundle, and Live-state modulation into a cohesive, repeatable asset. It encapsulates determinism (via the seed) while enabling runtime variation through state and optional overrides, avoiding ad-hoc assembly of frames in consumer code. + +## Notes +- The four-layer rendering uses distinct palette windows and a per-layer phase shift to create a sense of progression while sharing a single palette and primitive. +- Live-state modulation reads ConversationState to adjust palette bounds and intensity per frame; if state is null, a default LiveStateProfile is used. +- Unknown paletteOverride or patternOverride values will fall back to seed-derived defaults rather than throwing. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md new file mode 100644 index 0000000..2e5b974 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md @@ -0,0 +1,28 @@ +# GabrielSequenceService + +> **File:** `src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs` +> **Kind:** class + +```csharp +public sealed class GabrielSequenceService : IGabrielSequenceService +``` + + +GabrielSequenceService is a domain service that builds a GabrielSequence tailored to a specific conversation or project by loading the live domain state and styling overrides, then delegating to the sequence generator. Developers reach for GetForConversationAsync or GetForProjectAsync to produce a sequence that can be rendered or consumed by UI or downstream logic. + +## Remarks +GabrielSequenceService acts as an orchestrator between the domain model (Conversations and Projects) and the presentation/use of GabrielSequence data. It enforces authentication by requiring a current user and selects the appropriate aggregate state (including message history for conversations) before generating the sequence. For projects, it chooses the most recently updated conversation to drive the live state; if no conversations exist, it falls back to the generator\'s neutral defaults. The implementation intentionally separates concerns: repositories supply domain state, and the generator encapsulates the rules for translating that state into a GabrielSequence. + +## Example +```csharp +// For a specific conversation +GabrielSequence seqForConversation = await gabrielService.GetForConversationAsync(conversationId, ct); + +// For a project (uses the latest conversation to drive live state, if any) +GabrielSequence seqForProject = await gabrielService.GetForProjectAsync(projectId, ct); +``` + +## Notes +- Requires an authenticated user via ICurrentUser; otherwise an UnauthorizedAccessException is thrown. +- If a conversation or project cannot be found, a NotFoundException is thrown with the resource name and key to aid debugging. +- When a project has no conversations yet, latest is null and the generator\'s neutral defaults render the sequence. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md new file mode 100644 index 0000000..82f43ab --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md @@ -0,0 +1,11 @@ +# IGabrielSequenceGenerator + +> **File:** `src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs` +> **Kind:** interface + +```csharp +public interface IGabrielSequenceGenerator +``` + + +IGabrielSequenceGenerator is a stateless, deterministic factory for producing a 64-frame Gabriel Sequence from a seed. The seed encodes the personality's stable identity (Project.AvatarSeed for project-shared sequences; Conversation.AvatarSeed for standalone chats). The Generate method accepts a long seed, an optional ConversationState to drive the Live State layer (frames 48–63), and optional string overrides for patternOverride and paletteOverride that take precedence over the seed-derived selection; if an override is unrecognized, the implementation gracefully reverts to seed-based behavior. Because the interface is stateless, implementations are safe to register as a singleton and reuse across callers to preserve determinism and minimize allocations. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs.md new file mode 100644 index 0000000..7ba6cce --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs.md @@ -0,0 +1,14 @@ +# IGabrielSequenceService + +> **File:** `src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs` +> **Kind:** interface + +```csharp +public interface IGabrielSequenceService +``` + + +IGabrielSequenceService provides asynchronous access to GabrielSequence data tailored to a specific user context by assembling AvatarSeed and ConversationState and delegating to the generator. Use GetForConversationAsync to fetch a sequence for a single conversation, or GetForProjectAsync to fetch one for a project that shares an AvatarSeed and aggregates Live State from the project's most recently active conversation (defaulting to a neutral state if none exist). + +## Remarks +This interface acts as a stable bridge between the UI/controllers and the data/generator layers. It encapsulates how avatar identity (AvatarSeed) and conversational state are composed into a GabrielSequence, so callers don't need repository or generator details. For projects, it guarantees a consistent visual identity by sharing the AvatarSeed across conversations and by deriving live frames from the project's most recently active conversation, falling back to a neutral state when no activity exists. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Noise.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Noise.cs.md new file mode 100644 index 0000000..ab43d8c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Noise.cs.md @@ -0,0 +1,18 @@ +# Noise + +> **File:** `src/api/Gabriel.Engine/Sequence/Noise.cs` +> **Kind:** class + +```csharp +internal static class Noise +``` + + +Deterministic 2D noise primitives for the Gabriel Sequence generator, yielding repeatable variation that matches the web app's pulse/patterns noise language. It exposes a hashed 2D sampler (Hash2), a smooth value-noise interpolator (Value), and a multi-octave fractal noise builder (Fbm) for richer textures across scales. + +## Remarks +Hash2 is a non-cryptographic hash that maps integer coordinates and a seed to a deterministic pseudo-random value. Value performs bilinear interpolation of the surrounding lattice values with a cubic smoothstep to produce smooth noise, while Fbm stacks octaves of Value with doubling frequency and halving amplitude, normalizing the result to roughly [0,1]. This separation of concerns makes it easy to swap noise strategies or reuse these primitives in other visual generators without changing callers. + +## Notes +- Hash2's output is deterministic but not cryptographically secure; use it for noise, not security. +- Hash2 may produce 1.0 in rare cases due to normalization; clamp if you need strictly less than 1.0. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Palette.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Palette.cs.md new file mode 100644 index 0000000..a7b8256 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Palette.cs.md @@ -0,0 +1,43 @@ +# Palette + +> **File:** `src/api/Gabriel.Engine/Sequence/Palette.cs` +> **Kind:** record + +```csharp +public sealed record Palette(IReadOnlyList Colors) +{ + public int Count => Colors.Count; + public RgbColor this[int index] => Colors[index]; +} +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Colors` | `IReadOnlyList` | — | + + +Palette represents a fixed, narrow color set used by the Gabriel Sequence to convey a coherent visual identity. It wraps an `IReadOnlyList` (Colors) and exposes simple, index-based access along with a Count, enabling consumers to treat the palette as a compact collection of base and variation colors. Palette[0] is the canonical base color, and higher indices are brighter or more saturated variations; layers can shift toward different regions of the palette to give DNA / Live State a distinct visual signature while keeping the palette size within the spec (typically 8–32 entries). + +## Remarks +Palette centralizes color management for sequences that require a repeatable, descriptive color identity. By encapsulating Colors behind a dedicated Palette type, you decouple the rendering logic from concrete color values and make it easier to switch or reuse color sets across different sequences. This abstraction also clarifies intent: index 0 is the base, while subsequent positions encode progressive variations used to distinguish layers or states. + +## Notes +- The Colors collection is exposed as IReadOnlyList; ensure the underlying list is not mutated after Palette creation if you need true immutability, or wrap the source in an immutable read-only collection. + +## Dependencies +- RgbColor +- Sequence +- Colors + +## Dependency APIs (verified signatures) +- record [`RgbColor`](RgbColor.cs.md) (`src/api/Gabriel.Engine/Sequence/RgbColor.cs`) + - `RgbColor FromHsv(double hue, double saturation, double value)` + +## Symbol To Document +- Name: `Palette` +- Kind: `record` +- File: `src/api/Gabriel.Engine/Sequence/Palette.cs` +- Language: `csharp` +- ID: `023d31c1-fd23-49a5-aff7-11c5e148b1da` \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs.md new file mode 100644 index 0000000..c61651c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs.md @@ -0,0 +1,35 @@ +# PaletteTemplates + +> **File:** `src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs` +> **Kind:** class + +```csharp +internal static class PaletteTemplates +``` + + +PaletteTemplates centralizes the palette logic used to render vivid, personality-driven color palettes for avatars. It defines a set of named 2–4-stop gradients and provides utilities to deterministically pick a template from a seed and to expand that template into a full 16-color palette for rendering. The seed-based Pick ensures a given seed yields the same appearance across regenerations, while PickByName enables explicit overrides to honor PaletteOverride values. ExpandTo samples the chosen template's gradient evenly to produce an ordered palette where index 0 is the quiescent shadow and index 15 is the brightest accent, enabling consistent visuals with the Gabriel webapp's pulse/palettes.ts shape. + +## Remarks +PaletteTemplates acts as a single source of truth for avatar color decisions, decoupling palette generation from rendering logic. This separation ensures a consistent visual language across regenerations and UI components, and makes it straightforward to tweak gradient stops or interpolation without touching rendering code. The PickByName path allows explicit palette overrides (e.g., per Project or Conversation overrides) without altering the main seed-driven flow. + +## Example +```csharp +// Common usage: pick a palette by seed and expand it to 16 colors +var template = PaletteTemplates.Pick(12345L); +var palette = PaletteTemplates.ExpandTo(template); +var first = palette[0]; // quiescent shadow color +var last = palette[palette.Count - 1]; // brightest accent color + +// Explicit override by name (if available) +var named = PaletteTemplates.PickByName("heat"); +if (named != null) +{ + var p = PaletteTemplates.ExpandTo(named); +} +``` + +## Notes +- ExpandTo supports 2–4 stop templates and expands them to size 16 by sampling the gradient evenly. If a template has a single stop, expansion returns that color for all entries. +- Pick mixes the seed with a deterministic scramble (folded seed) to avoid shadowing other seed-derived state, ensuring palette selection remains stable per-seed while not interfering with frame-pattern seeds. +- PickByName returns null when no matching template exists (caller should fall back to seed-based Pick). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PatternKind.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PatternKind.cs.md new file mode 100644 index 0000000..16600ff --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/PatternKind.cs.md @@ -0,0 +1,30 @@ +# PatternKind + +> **File:** `src/api/Gabriel.Engine/Sequence/PatternKind.cs` +> **Kind:** enum + +```csharp +public enum PatternKind +{ + Plasma, + Waves, + Spiral, + Pulse, + Shimmer, +} +``` + + +PatternKind enumerates the five pattern primitives the generator can render. Each member corresponds to a distinct animation grammar defined in Patterns.cs: Plasma, Waves, Spiral, Pulse, and Shimmer. By default, the seed selects one when no explicit override is provided on the owning Project or Conversation; explicit overrides let you pin a specific look without rerolling for it. + +## Remarks +PatternKind acts as a stable, high-level selector that decouples the rendering pipeline from the concrete pattern implementations. It defines the allowed set of looks and provides a single knob for consumers to request a style. The actual rendering logic and per-pattern parameters live in the Patterns.* implementations; PatternKind simply selects which primitive to use, allowing the system to evolve patterns independently and new patterns to be added with minimal impact to callers. + +## Example +```csharp +PatternKind kind = PatternKind.Plasma; +``` + +## Notes +- Do not rename or reorder the enum values, as existing overrides and serialized data rely on these exact identifiers. +- When introducing a new pattern, extend both PatternKind and the corresponding PatternInit/Renderer in Patterns.cs to maintain parity and preserve backward compatibility. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Patterns.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Patterns.cs.md new file mode 100644 index 0000000..460e73f --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/Patterns.cs.md @@ -0,0 +1,33 @@ +# Patterns + +> **File:** `src/api/Gabriel.Engine/Sequence/Patterns.cs` +> **Kind:** class + +```csharp +internal static class Patterns +``` + + +A collection of small, deterministic spatial+temporal pattern primitives used by the generator to produce looping animated values. Each pattern is a pure function that maps a coordinate (x, y) and a normalized time t (expected in [0, 1)) plus a pattern-specific parameter record to a scalar in [0, 1]. Use these primitives when you want reusable, compact shape/texture sources that the renderer will convert to palette indices; prefer them over embedding raw math in rendering code because the class centralizes loop-continuous, parameterized patterns and provides Init helpers to produce varied but well-formed parameter sets. + +## Remarks +The class separates pattern generation (spatial + temporal math) from palette mapping and frame construction. Each pattern offers an Init method (taking a Random and the canvas size) that produces a small readonly record of parameters; the corresponding pattern function is a stateless evaluator that accepts those parameters by reference (`in`). This design keeps the evaluators cheap and thread-friendly (pure math) while allowing randomized, size-aware parameter creation. Patterns are intentionally continuous between t=0 and t=1 so multiple primitives can be cycled or cross-faded without visible seams. + +## Example +```csharp +// create parameters for a 16x16 pattern and sample one pixel over a 64-frame cycle +var rng = new Random(12345); +int size = 16; +var plasmaParams = Patterns.PlasmaInit(rng, size); +for (int frame = 0; frame < 64; frame++) +{ + double t = frame / 64.0; // normalized time in [0,1) + double value = Patterns.Plasma(x: 7.5, y: 7.5, t: t, p: plasmaParams); + // 'value' is in [0,1]; map to palette index outside this helper +} +``` + +## Notes +- t is normalized: treat t==1 as equivalent to t==0 for seamless looping; callers should provide t in [0,1). +- The Init helpers use the supplied Random and the canvas size to set sensible defaults (for example Cx/Cy = size/2 - 0.5). If you need a different coordinate origin, construct the params manually. +- Many patterns expose a Sharpness/exponent parameter; values >1 concentrate outputs toward 0 or 1 and may reduce visible mid-range palette variation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/RgbColor.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/RgbColor.cs.md new file mode 100644 index 0000000..6334c59 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/RgbColor.cs.md @@ -0,0 +1,44 @@ +# RgbColor + +> **File:** `src/api/Gabriel.Engine/Sequence/RgbColor.cs` +> **Kind:** record + +```csharp +public readonly record struct RgbColor(byte R, byte G, byte B) +{ + public static RgbColor FromHsv(double hue, double saturation, double value) + { + var h = (hue % 1.0 + 1.0) % 1.0 * 6.0; + var c = value * saturation; + var x = c * (1 - Math.Abs(h % 2 - 1)); + var m = value - c; + var (r, g, b) = (int)Math.Floor(h) switch + { + 0 => (c, x, 0.0), + 1 => (x, c, 0.0), + 2 => (0.0, c, x), + 3 => (0.0, x, c), + 4 => (x, 0.0, c), + _ => (c, 0.0, x), + }; + return new RgbColor( + (byte)Math.Clamp((r + m) * 255, 0, 255), + (byte)Math.Clamp((g + m) * 255, 0, 255), + (byte)Math.Clamp((b + m) * 255, 0, 255)); + } +} +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `R` | `byte` | — | +| `G` | `byte` | — | +| `B` | `byte` | — | + + +RgbColor is a compact RGB color value using 8-bit channels, implemented as a readonly record struct for use as a value type. It is designed to be inlined in arrays of palette entries to avoid heap allocations. It provides FromHsv(double hue, double saturation, double value) which performs the standard HSV→RGB conversion (hue in [0,1), saturation and value in [0,1]), then scales and clamps the result to 0–255 per channel to produce a new RgbColor. + +## Remarks +Its readonly record struct nature makes it immutable and cheap to copy, ideal for embedding directly in color palettes. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md new file mode 100644 index 0000000..8549cd7 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md @@ -0,0 +1,33 @@ +# SequenceCatalog + +> **File:** `src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs` +> **Kind:** class + +```csharp +public static class SequenceCatalog +``` + + +SequenceCatalog is a static utility that enumerates valid pattern and palette identifiers used by a Project or standalone Conversation. It provides canonical, case-insensitive lists (Patterns and Palettes) and helpers to validate, normalize, and parse names into stable forms for persistence and interpretation. Unknown identifiers are silently ignored at generation time—the generator falls back to the seed-derived pick. + +## Remarks +SequenceCatalog centralizes the vocabulary and decouples wire-identifiers from internal representations, enabling the catalog to grow without database migrations. NormalizePattern/NormalizePalette ensure inputs are persisted in canonical form, while TryParsePattern bridges string names to the PatternKind enum. Together, these members reduce duplication and error-prone casing or whitespace handling across the codebase. + +## Example +```csharp +string input = " Plasma "; +var canonicalPattern = SequenceCatalog.NormalizePattern(input); +var kind = SequenceCatalog.TryParsePattern(input); + +if (canonicalPattern != null && kind != null) +{ + // Persist canonicalPattern as the authoritative identifier or apply as an override +} +else +{ + // Fall back to seed-derived selection +} +``` + +## Notes +- NormalizePattern/NormalizePalette return null when the input is unknown; use TryParsePattern to map to PatternKind when appropriate. Unknown inputs are effectively ignored and trigger fallback behavior. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md new file mode 100644 index 0000000..87ef501 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md @@ -0,0 +1,106 @@ +# AgentContext.cs + +> **Source:** `src/api/Gabriel.Engine/Services/AgentContext.cs` + +## Contents + +- [AgentContext](#agentcontext) +- [AgentContextBreakdown](#agentcontextbreakdown) + +--- + +## AgentContext +> **File:** `src/api/Gabriel.Engine/Services/AgentContext.cs` +> **Kind:** record + +```csharp +public record AgentContext( + string PersonaPrompt, + string? ProjectPrompt, + string? MemoryBlock, + string? Summary, + IReadOnlyList Messages, + IReadOnlyList Tools) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `PersonaPrompt` | `string` | — | +| `ProjectPrompt` | `string?` | — | +| `MemoryBlock` | `string?` | — | +| `Summary` | `string?` | — | +| `Messages` | `IReadOnlyList` | — | +| `Tools` | `IReadOnlyList` | — | + + +AgentContext is a record that encapsulates the complete context sent to the chat provider for a single turn. It centralizes persona context, optional project context, memory block, a summary, the active messages, and tool descriptors into a single, immutable value so both the live provider call and any telemetry built for the UI share the exact same data. + +## Remarks + +AgentContext serves as the canonical snapshot of a turn’s context used by both runtime interactions with the provider and by the UI/metrics layer. By performing variant filtering and orphaned-tool-message cleanup in one place, it prevents divergence between what the model actually sees and what is exposed in telemetry, and it avoids reassembly inconsistencies between the live call path and the metrics path. + +## Example + +```csharp +// Example usage +Conversation conversation = /* existing conversation */; +string personaPrompt = /* persona */; +string? projectPrompt = /* project context */; +string? memoryBlock = /* memory block */; +IReadOnlyList tools = /* tool descriptors */; + +var context = AgentContext.Build(conversation, personaPrompt, projectPrompt, memoryBlock, tools); +var providerHistory = context.ToProviderHistory(); +``` + +## Notes + +- Build starts from the conversation’s messages, optionally skipping messages before a summarized point. If SummarizedThroughMessageId is unset, it includes all messages from the start. +- It collects the set of active tool_call.ids from active assistant messages and filters tool messages to only those with an active tool call; non-tool messages are kept only if they are part of an active variant. Malformed or missing tool call data can influence what gets retained. + + +--- + +## AgentContextBreakdown +> **File:** `src/api/Gabriel.Engine/Services/AgentContext.cs` +> **Kind:** record + +```csharp +public record AgentContextBreakdown( + int SystemPromptTokens, + int ProjectPromptTokens, + int MemoryTokens, + int SummaryTokens, + int ToolsTokens, + int ConversationTokens) +{ + public int Total => + SystemPromptTokens + ProjectPromptTokens + MemoryTokens + + SummaryTokens + ToolsTokens + ConversationTokens; +} +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `SystemPromptTokens` | `int` | — | +| `ProjectPromptTokens` | `int` | — | +| `MemoryTokens` | `int` | — | +| `SummaryTokens` | `int` | — | +| `ToolsTokens` | `int` | — | +| `ConversationTokens` | `int` | — | + + +AgentContextBreakdown is an immutable data carrier that captures per-category token counts for a single AgentContext snapshot. It corresponds to the token breakdown used by the AgentService when deciding on compact behavior and provides a Total that sums all categories, matching the value represented by ContextMetrics.CurrentTokens. + +## Remarks +AgentContextBreakdown serves as a focused, transport-friendly representation of token usage. It aggregates the six categories into one object, making it easier to log, compare, or feed into decision logic without pulling the full AgentContext. The Total property offers a single, convenient aggregate that aligns with the token tally used by AgentService and with ContextMetrics. + +## Notes +- Total is computed from six fields; since the record is immutable, the value is fixed once constructed. The sum uses unchecked integer arithmetic; values approaching int.MaxValue could overflow; if there is a risk, consider using a larger type or introducing validation. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md new file mode 100644 index 0000000..d824a55 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md @@ -0,0 +1,366 @@ +# AgentEvent.cs + +> **Source:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` + +## Contents + +- [AgentAssistantMessage](#agentassistantmessage) +- [AgentCompactDone](#agentcompactdone) +- [AgentCompactStart](#agentcompactstart) +- [AgentDone](#agentdone) +- [AgentError](#agenterror) +- [AgentEvent](#agentevent) +- [AgentReasoningDelta](#agentreasoningdelta) +- [AgentTextDelta](#agenttextdelta) +- [AgentToolCall](#agenttoolcall) +- [AgentToolResult](#agenttoolresult) +- [AgentUserMessagePersisted](#agentusermessagepersisted) + +--- + +## AgentAssistantMessage +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentAssistantMessage(Guid MessageId, string Content, string? ReasoningContent = null) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `MessageId` | `Guid` | — | +| `Content` | `string` | — | +| `ReasoningContent` | `string?` | `null` | + + +Represents a single final assistant message produced by the Agent and published as an event in the Gabriel engine. It persists the assistant's textual content and, when available, the accompanying reasoning transcript for that turn. The MessageId provides a stable identity for the message; Content stores the visible reply, while ReasoningContent (when present) contains the accumulated chain-of-thought; null indicates that the provider did not emit a reasoning channel. Derived from AgentEvent, this record participates in the engine's event stream and serves as the canonical payload for persisting and streaming assistant messages, enabling clients to reconcile a delta-updated view against the persisted canonical content. + +## Remarks +AgentAssistantMessage is an immutable domain event that ties a concrete assistant reply to a unique identifier and an optional reasoning trace. By deriving from AgentEvent, it participates in the engine's event-based workflow and can be routed, persisted, or audited as part of the agent's conversation history. The separation between Content and ReasoningContent clarifies when full chain-of-thought data is available and ensures consumers can handle cases where only the final text is emitted. + +## Example +```csharp +var message = new AgentAssistantMessage( + MessageId: Guid.NewGuid(), + Content: "The answer is 42.", + ReasoningContent: "Step 1: Do X; Step 2: Do Y; Conclusion: 42." +); +``` + +## Notes +- ReasoningContent may be null if the provider did not emit a reasoning channel. +- MessageId must be unique per message; reusing IDs can disrupt client reconciliation of the persisted content. + +--- + +## AgentCompactDone +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentCompactDone(int MessageCount, int SummaryTokens) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `MessageCount` | `int` | — | +| `SummaryTokens` | `int` | — | + + +AgentCompactDone is a sealed record that signals the completion of a compaction pass in the agent’s workflow. It carries the results: MessageCount, the number of messages that were condensed into the new rolling summary, and SummaryTokens, the token-length of that summary. It derives from AgentEvent and is emitted only after a successful AgentCompactStart, enabling the UI to render a line such as 'summarized N messages into M tokens.' If the summary step fails, this event is skipped, and the UI may show a longer thinking phase without a compact pair, which is expected. + +## Remarks +>This event exists to transport the outcome of the summarization step through the engine’s event stream. By recording both the input size and the resulting token count, it enables downstream components (UI, telemetry, and dashboards) to present a concise view of the cost and impact of the summarization. Being a sealed record also enforces immutability and value-based equality, which makes it reliable for event-driven workflows and comparisons. + +## Notes +- This event assumes a preceding AgentCompactStart; if observed out of sequence, consumer code should handle the anomaly gracefully to avoid inconsistent UI states. + +--- + +## AgentCompactStart +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentCompactStart(int MessageCount, int CurrentTokens, int ThresholdTokens) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `MessageCount` | `int` | — | +| `CurrentTokens` | `int` | — | +| `ThresholdTokens` | `int` | — | + + +AgentCompactStart is an immutable event (sealed record) that marks the beginning of the compaction step within the agent's processing pipeline. It is emitted immediately before the rolling-summary LLM call that folds the first MessageCount messages into a single summary. The event exposes three pieces of context: MessageCount—the number of messages to be compacted; CurrentTokens—the token count before compaction; and ThresholdTokens—the token threshold that was crossed to trigger compaction. Consumers can rely on this event to drive user-facing UX (e.g., displaying a 'compacting…' overlay) and to coordinate with the summary provider invocation. + +## Remarks +This abstraction isolates the initiation of the rolling summary compaction from the rest of the processing pipeline, enabling UI and orchestration code to react in a decoupled way. Deriving from AgentEvent places it within the agent's event stream, standardizing how lifecycle moments like compaction are observed and handled. + +## Example +```csharp +// Common usage: pattern-match the event from an AgentEvent stream +AgentEvent e = new AgentCompactStart(5, 10240, 12000); +if (e is AgentCompactStart s) +{ + Console.WriteLine($"Compacting first {s.MessageCount} messages; current tokens: {s.CurrentTokens}, threshold: {s.ThresholdTokens}"); +} +``` + +## Notes +- Immutable: the sealed record makes instances value-based and thread-safe to reason about. +- The fields represent pre-compact state: CurrentTokens is the total token count before compaction; ThresholdTokens is the crossing point that triggered the event. +- This event is emitted BEFORE the summary provider call; do not rely on it for post-compact state. + +--- + +## AgentDone +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentDone() : AgentEvent +``` + + +AgentDone is a terminal AgentEvent that signals the end of the agent's event loop and the impending closure of the SSE stream. Implemented as a parameterless, sealed record deriving from AgentEvent, it serves as a lightweight sentinel that downstream code can pattern-match against to stop processing and perform necessary cleanup when the stream ends. + +## Remarks +AgentDone sits at the end of the AgentEvent hierarchy, providing a clear, type-based signal for completion rather than using exceptions or error states. By being a distinct, payload-free record, it keeps the end-of-stream semantics lightweight and easy to reason about; downstream components can pattern-match on AgentDone to terminate processing and flush resources deterministically. + +## Example +```csharp +// Example: terminate processing when the terminal event is observed +void HandleEvent(AgentEvent e) +{ + if (e is AgentDone) + { + // End of stream reached; stop consuming further events + return; + } + + // Handle other events... +} +``` + +## Notes +- Payloadless sentinel: AgentDone has no data, so if you need context about why the stream ended, use a separate event or attach data to another event type. + +--- + +## AgentError +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentError(string Message) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| [`Message`](../../Gabriel.Core/Entities/Message.cs.md) | `string` | — | + + +AgentError is a sealed record that represents an in-stream error signaling a lookup failure that occurs before streaming begins; such failures surface to the client as HTTP 4xx/5xx. It carries a single Message describing the failure and derives from AgentEvent to participate in the same event stream as other agent events. + +## Remarks +AgentError isolates initialization-time failures from the normal event flow, allowing the transport layer to translate its presence into a precise HTTP status and error payload. Being a sealed record, it guarantees immutability and a stable identity for logging, correlation, and telemetry within the agent's event stream. + +## Notes +- It is immutable: the Message value is assigned at construction and never changes. +- This type signals pre-stream lookup failures; if a failure occurs after streaming begins, a different AgentEvent-derived type should be used. + +--- + +## AgentEvent +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(AgentUserMessagePersisted), "userMessagePersisted")] +[JsonDerivedType(typeof(AgentTextDelta), "textDelta")] +[JsonDerivedType(typeof(AgentReasoningDelta), "reasoningDelta")] +[JsonDerivedType(typeof(AgentToolCall), "toolCall")] +[JsonDerivedType(typeof(AgentToolResult), "toolResult")] +[JsonDerivedType(typeof(AgentAssistantMessage), "assistantMessage")] +[JsonDerivedType(typeof(AgentCompactStart), "compactStart")] +[JsonDerivedType(typeof(AgentCompactDone), "compactDone")] +[JsonDerivedType(typeof(AgentError), "error")] +[JsonDerivedType(typeof(AgentDone), "done")] +public abstract record AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `TypeDiscriminatorPropertyName` | — | `"type"` | + + +AgentEvent is the abstract base record for events yielded by AgentService.RunAsync and served over Server-Sent Events. It represents a polymorphic stream of runtime events serialized to JSON using a discriminator named type, enabling clients to switch on the concrete event kind at runtime. The supported derivatives include AgentUserMessagePersisted, AgentTextDelta, AgentReasoningDelta, AgentToolCall, AgentToolResult, AgentAssistantMessage, AgentCompactStart, AgentCompactDone, AgentError, and AgentDone. When consuming the event stream, clients deserialize each payload as AgentEvent and pattern-match on the concrete type to drive UI updates, logging, or orchestration logic. + +## Remarks +This discriminated union serves as the single public contract for all streaming events emitted by AgentService. It decouples the event producers from consumers: new event shapes can be added by extending the derived types without changing existing deserialization logic, provided the discriminator string is wired. It also enables straightforward client rendering: a single stream of AgentEvent values can be pattern-matched against known derived types to render or react to the agent's turns, tool interactions, and completion states. + +## Notes +- The JSON payload must include the discriminator type for proper deserialization; omitting it can cause the deserializer to fail to resolve the concrete type. +- If a new derived event is added in the future, register it with a JsonDerivedType attribute on AgentEvent so clients can deserialize successfully. +- When consuming long-running streams, consider backpressure and cancellation to avoid unbounded buffering. + +--- + +## AgentReasoningDelta +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentReasoningDelta(string Delta) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Delta` | `string` | — | + + +AgentReasoningDelta is a sealed record that carries an incremental token of the model's reasoning stream as a Delta string, enabling it to flow as an AgentEvent in the engine. Use it when you need to surface or persist the evolving reasoning steps produced by reasoning-capable providers; the UI may render the Delta incrementally, while the final assistant message carries the accumulated reasoning for persistence. + +## Remarks +AgentReasoningDelta exists to separate reasoning data from other events, enabling targeted handling, auditing, or redaction. By modeling it as a dedicated AgentEvent subtype, components can subscribe to or filter reasoning tokens without parsing unrelated payloads, and streaming reasoning can be composed from discrete Delta fragments. This aligns with the event-driven architecture, allowing reasoning data to travel through the same pipelines as other agent events. + +## Notes +- Be mindful of privacy: exposing reasoning segments can reveal sensitive model internals; redact or avoid persisting in production. +- Delta may be large and emitted frequently; ensure consumers can handle backpressure and avoid overwhelming logs or transports. + +--- + +## AgentTextDelta +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentTextDelta(string Delta) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Delta` | `string` | — | + + +AgentTextDelta is a streaming event that carries a fragment of the assistant's text. Each instance exposes the Delta string, which consumers should append to the accumulating message to reconstruct the full reply as tokens arrive. + +## Remarks +AgentTextDelta is a concrete, immutable record derived from AgentEvent used in the engine's event stream to convey small text fragments. Each instance carries a Delta string that should be appended to the current output by the consumer. The pattern enables clients to render streaming responses as they are produced, without waiting for a complete message; it decouples token production from rendering and allows incremental updates to the UI. + +## Notes +- AgentTextDelta is immutable; the Delta value is fixed at construction. +- In streaming scenarios, several AgentTextDelta events may arrive sequentially; consumers should concatenate their Delta values in arrival order to form the full message. + +--- + +## AgentToolCall +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentToolCall(Guid MessageId, string ToolCallId, string Name, string ArgumentsJson) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `MessageId` | `Guid` | — | +| `ToolCallId` | `string` | — | +| [`Name`](../Providers/ToolBridge/GabrielToolBridge.cs.md) | `string` | — | +| `ArgumentsJson` | `string` | — | + + +Represents a single tool invocation event in the agent’s processing pipeline. This immutable record carries the context required to trigger a tool—linking the originating MessageId, a unique ToolCallId, the tool name, and a JSON-encoded Arguments payload—so that tool interactions are modeled as first-class events rather than ad-hoc calls. + +## Remarks +As a concrete member of the AgentEvent family, AgentToolCall enables type-based routing and stable correlation between requests and responses. The MessageId provides traceability to the originating turn, while ToolCallId offers a durable handle for matching responses. The record's immutability and value-based equality simplify caching, deduplication, and event-sourcing scenarios. + +## Example +```csharp +var toolCall = new AgentToolCall( + MessageId: Guid.NewGuid(), + ToolCallId: "tc-001", + Name: "SummarizeText", + ArgumentsJson: "{ \"text\": \"Sample input to summarize\" }" +); +``` + +## Notes +- ArgumentsJson must contain valid JSON; downstream consumers parse this payload to construct tool invocation requests. +- ToolCallId should uniquely identify the invocation within the message context to enable reliable correlation with tool responses. + +--- + +## AgentToolResult +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentToolResult(Guid MessageId, string ToolCallId, string Content) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `MessageId` | `Guid` | — | +| `ToolCallId` | `string` | — | +| `Content` | `string` | — | + + +AgentToolResult represents the completion of an Agent Tool run. It carries the GUID of the persisted observation message (MessageId), the identifier for the specific tool invocation (ToolCallId), and the textual Content produced by the tool. As a sealed record deriving from AgentEvent, it participates in the engine's event stream and is consumed by downstream handlers to persist results or trigger follow-up actions. + +## Remarks +AgentToolResult exists to decouple tool execution from result storage and routing. The MessageId references the persisted observation containing Content, enabling auditing and replay without duplicating payloads. The ToolCallId ties the result to the exact tool invocation, and Content holds the tool's output for display or storage. Being a sealed record provides immutability and value-based equality, aiding correctness and testing. + +--- + +## AgentUserMessagePersisted +> **File:** `src/api/Gabriel.Engine/Services/AgentEvent.cs` +> **Kind:** record + +```csharp +public sealed record AgentUserMessagePersisted(Guid MessageId) : AgentEvent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `MessageId` | `Guid` | — | + + +AgentUserMessagePersisted is a small, value-like event that signals the database has assigned the real MessageId to a user-originated message after persistence. It is emitted as the first event in every user turn that originates from RunAsync, and it carries the authoritative Id of the persisted message so the client can replace its temporary tmp-xxxxx identifier in the user entry with the actual database ID without causing an extra GET-conversation round trip. + +## Remarks +This event establishes an authoritative reference for the message at the very start of a user turn, enabling consistent correlation of subsequent events in the same turn. By providing the persisted MessageId up front, it reduces latency in the client experience and avoids a follow-up fetch. It also cleanly separates concerns: persistence sequencing remains server-side while the client updates its state using the real ID from this single source of truth. + +## Example +```csharp +// After persisting the user's message to the database, emit this event so the client can swap IDs +var persistedEvent = new AgentUserMessagePersisted(actualDbMessageId); +``` + +## Notes +- This event must be the first in the user-turn stream; emitting other events first can leave the client using a temporary ID. +- It is applicable to RunAsync turns; it does not apply to RegenerateAsync flows. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentService.cs.md new file mode 100644 index 0000000..f6c466d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/AgentService.cs.md @@ -0,0 +1,767 @@ +# AgentService.cs + +> **Source:** `src/api/Gabriel.Engine/Services/AgentService.cs` + +## Contents + +- [AgentService](#agentservice) +- [TurnPrompts](#turnprompts) +- [AgentService (constructor)](#agentservice-constructor) +- [AppendMemory](#appendmemory) +- [ExecuteToolSafelyAsync](#executetoolsafelyasync) +- [GenerateSummaryAsync](#generatesummaryasync) +- [GetContextMetricsAsync](#getcontextmetricsasync) +- [LoadMemoryBlockAsync](#loadmemoryblockasync) +- [LoadProjectSystemPromptAsync](#loadprojectsystempromptasync) +- [LoadTurnPromptsAsync](#loadturnpromptsasync) +- [MaybeCompactAsync](#maybecompactasync) +- [Preview](#preview) +- [RegenerateAsync](#regenerateasync) +- [ResolveModelSelectionAsync](#resolvemodelselectionasync) +- [RunAsync](#runasync) +- [RunStreamAsync](#runstreamasync) +- [RunStreamWithUserPreambleAsync](#runstreamwithuserpreambleasync) +- [SelectCompactCutIndex](#selectcompactcutindex) +- [SerializeToolCalls](#serializetoolcalls) +- [EmptyStopMaxRetries](#emptystopmaxretries) +- [EmptyStopRetryDelayMs](#emptystopretrydelayms) +- [LogPreviewLimit](#logpreviewlimit) + +--- + +## AgentService +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** class + +```csharp +public class AgentService : IAgentService +``` + + +Coordinates the end-to-end agent turn: it validates and persists the incoming user message, resolves the model selection and stable prompt pieces (persona, project prompt, memory, tool descriptors), populates the scoped tool-execution context, and then drives the provider loop that emits a stream of AgentEvent values. Use this service when you need an orchestrated, repository-backed conversational turn that includes prompt building, tool integration, memory loading, compacting long context, and post-processing of provider output rather than calling a chat provider directly. + +## Remarks +AgentService is the high-level coordinator that glues together conversation/project repositories, the provider registry, model selection, memory and tool subsystems, and response post-processing. It performs upfront validation and persists the user message and conversation state before starting the server-sent-event stream so errors surface as regular HTTP ProblemDetails (4xx) and the conversation timeline remains consistent if the client disconnects. The class also contains a small, bounded retry policy specifically for the provider "empty Stop" hiccup (a successful stream response with an empty final text) and constructs a GabrielToolBridge per-call for tool-related features; the bridge is stateless across calls and AgentService owns its logger. + +## Example +```csharp +// Consume the streaming events produced by the agent for a single turn. +IAgentService agent = /* resolve from DI */; +Guid conversationId = /* existing conversation id */; +string userInput = "Summarize the project status."; + +await foreach (var ev in await agent.RunAsync(conversationId, userInput)) +{ + // AgentEvent is a discriminated record containing progress, partial text, + // final assistant messages, tool call requests/results, etc. + Console.WriteLine(ev); +} +``` + +## Notes +- Empty-stop retry: the service retries the agent loop a small number of times (EmptyStopMaxRetries = 2; linear backoff of N * 500ms) to recover when the provider finishes with an empty Stop token — this is intentionally implemented inside the agent loop because the HTTP resilience pipeline cannot detect an empty final text on a successful 200 stream. +- Validation and persistence happen before the SSE headers are sent; this lets the caller receive clean 4xx responses (ProblemDetails) from global exception handling instead of a broken stream. +- ToolMode and tool descriptors are resolved early so features like ToolMode.None can drop tool descriptors at the source; project-aware tools get a populated IToolExecutionContext so they do not need to infer project scope from model input. + +--- + +## TurnPrompts +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** record + +```csharp +private sealed record TurnPrompts( + string PersonaPrompt, + string? ProjectPrompt, + string? MemoryBlock, + IReadOnlyList Tools) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `PersonaPrompt` | `string` | — | +| `ProjectPrompt` | `string?` | — | +| `MemoryBlock` | `string?` | — | +| `Tools` | `IReadOnlyList` | — | + + +TurnPrompts is an internal, immutable bundle of inputs that guides per-turn prompt construction for the agent. It collects the mandatory PersonaPrompt, the optional ProjectPrompt, the optional MemoryBlock, and the list of Tools into a single snapshot that AgentContext.Build uses to assemble the actual prompt and history exposed to the providers. + +Because it is private and sealed, callers cannot rearrange or replace its contents; the assembly happens within AgentContext.Build, ensuring a consistent context for token estimation, history provisioning, and tool discovery. + +## Remarks +TurnPrompts serves as a boundary that keeps prompt composition concerns isolated from public APIs. By bundling persona, project, memory, and tools in one place, it simplifies maintaining a coherent per-turn context across the agent lifecycle and enables consistent token budgeting and provider history generation. + +## Notes +- Private visibility means changes to this record shape are internal; external code should not rely on its structure. +- MemoryBlock and ProjectPrompt are nullable; null indicates absence. + + +--- + +## AgentService (constructor) +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** constructor + +```csharp +public AgentService( + IConversationRepository conversations, + IProjectRepository projects, + IChatProviderRegistry providerRegistry, + IModelCatalog modelCatalog, + IUserPreferences userPrefs, + IMemoryService memories, + IToolRegistry tools, + IToolExecutionContext toolContext, + IUnitOfWork uow, + ITokenEstimator tokens, + ICurrentUser currentUser, + IConversationStateUpdater stateUpdater, + ISystemPromptBuilder promptBuilder, + IResponsePostProcessor postProcessor, + IOptions options, + ILogger logger, + ILogger toolBridgeLogger) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversations` | [`IConversationRepository`](../../Gabriel.Core/Repositories/IConversationRepository.cs.md) | — | +| `projects` | [`IProjectRepository`](../../Gabriel.Core/Repositories/IProjectRepository.cs.md) | — | +| `providerRegistry` | [`IChatProviderRegistry`](../Providers/IChatProviderRegistry.cs.md) | — | +| `modelCatalog` | [`IModelCatalog`](../Providers/IModelCatalog.cs.md) | — | +| `userPrefs` | [`IUserPreferences`](../../Gabriel.Core/Identity/IUserPreferences.cs.md) | — | +| `memories` | [`IMemoryService`](../../Gabriel.Core/Services/IMemoryService.cs.md) | — | +| `tools` | [`IToolRegistry`](../Tools/IToolRegistry.cs.md) | — | +| `toolContext` | [`IToolExecutionContext`](../Tools/IToolExecutionContext.cs.md) | — | +| `uow` | [`IUnitOfWork`](../../Gabriel.Core/Repositories/IUnitOfWork.cs.md) | — | +| `tokens` | [`ITokenEstimator`](ITokenEstimator.cs.md) | — | +| `currentUser` | [`ICurrentUser`](../../Gabriel.Core/Identity/ICurrentUser.cs.md) | — | +| `stateUpdater` | [`IConversationStateUpdater`](../Personality/IConversationStateUpdater.cs.md) | — | +| `promptBuilder` | [`ISystemPromptBuilder`](../Personality/ISystemPromptBuilder.cs.md) | — | +| `postProcessor` | [`IResponsePostProcessor`](../Personality/IResponsePostProcessor.cs.md) | — | +| `options` | `IOptions` | — | +| `logger` | `ILogger` | — | +| `toolBridgeLogger` | `ILogger` | — | + + +The AgentService constructor acts as the wiring point for the engine's agent component. It gathers core collaborators—repositories for conversations and projects, a registry of chat providers, a model catalog, user preferences, memory and tool infrastructure, a tool execution context, unit of work, token estimation, current user context, state updater, system prompt builder, and a post-processor—and assigns them to internal fields for runtime use. It also materializes the AgentOptions from the provided `IOptions` so configuration knobs (e.g., MaxIterations, CompactThreshold) are available to the service. This constructor is intended to be resolved by the dependency-injection container, not called directly, and then used to orchestrate agent interactions within the Gabriel Engine. + +## Remarks +Architecturally, this constructor models AgentService as an orchestrator that composes multiple cross-cutting concerns: persistence, tooling, prompting, and model interaction. By taking all dependencies up front, it enables easier testing via mocks and allows behavior to be tuned via AgentOptions without changing code. The explicit dependency surface also clarifies the integration points with Gabriel Engine's tooling and provider ecosystem. + +## Notes +- No null checks are performed in the constructor; rely on the DI container to supply non-null instances. +- Accessing options.Value assumes a configured AgentOptions; ensure AgentOptions is configured in the host to provide required values (e.g., MaxIterations, CompactThreshold). + +--- + +## AppendMemory +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private static void AppendMemory(StringBuilder sb, Core.Entities.MemoryEntry m) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `sb` | `StringBuilder` | — | +| `m` | `Core.Entities.MemoryEntry` | — | + +**Returns:** `void` + + +AppendMemory is a private helper that renders a MemoryEntry as a Markdown fragment into the supplied StringBuilder. It formats a header line from the entry’s type (converted to lowercase in a culture-invariant manner) and the entry’s name, followed by the entry’s description and body, with blank lines separating these sections. This centralizes the formatting of memory entries when assembling documentation for the agent, guaranteeing a consistent presentation across all entries. + +## Remarks +This abstraction exists to keep memory-entry rendering consistent and isolated from higher-level document assembly. By using ToString().ToLowerInvariant() on the entry type, headings remain stable across locales. The method relies on the MemoryEntry’s Type, Name, Description, and Body to produce a self-contained Markdown snippet that can be appended to a larger document, keeping the concerns of formatting separate from data retrieval. + +## Notes +- No escaping is performed for m.Name, m.Description, or m.Body; any embedded Markdown or special characters will influence the final rendering. +- The method is private and static; it assumes the provided StringBuilder is properly scoped by the caller. It does not perform synchronization, so concurrent calls sharing the same StringBuilder require external synchronization. + +--- + +## ExecuteToolSafelyAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async Task ExecuteToolSafelyAsync(ChatProviderToolCall call, Guid conversationId, CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `call` | [`ChatProviderToolCall`](../Providers/ChatProviderToolCall.cs.md) | — | +| `conversationId` | `Guid` | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +Executes a named tool in a safe, asynchronous wrapper. It looks up the tool by name from the internal registry; if the tool is not registered it returns an error string and logs a rejection. If found, it logs the start of the tool call, starts a stopwatch, and awaits tool.ExecuteAsync(call.ArgumentsJson, ct). After completion, it stops the timer, inspects the result: if the observation begins with \"Error\" (case-insensitive), it is treated as a soft failure and logged as a warning; otherwise it is logged as a successful invocation with timing and length metrics. The method returns the tool's observation (or an empty string if null). If the tool throws, the exception is logged as an error and a user-facing error message is returned containing the tool name and exception message. + +## Remarks +Centralizes tool invocation inside the engine, providing uniform logging, timing, and error handling for all tool calls. It uses a short Preview of arguments and results to avoid flooding logs with potentially large payloads. It also distinguishes soft errors (strings starting with 'Error') from successful results, surfacing problems to operators without crashing the conversation flow. + +## Notes +- Soft-error detection relies on the observation string starting with 'Error'; legitimate tool outputs that begin with 'Error' could be misclassified. +- Exceptions from tool execution are surfaced as 'Error executing {call.Name}: {ex.Message}', which may reveal internal details. + +--- + +## GenerateSummaryAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async Task GenerateSummaryAsync( + string? previousSummary, + IReadOnlyList toSummarize, + ModelSelection selection, + CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `previousSummary` | `string?` | — | +| `toSummarize` | `IReadOnlyList` | — | +| `selection` | [`ModelSelection`](../../Gabriel.Core/Configuration/ModelSelection.cs.md) | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +Generates a concise, factual recap of a conversation by streaming a model provider with a system prompt and the turns to summarize, returning the accumulated text as a trimmed string. Use it internally when the agent needs to produce an up-to-date summary of a chat, optionally folding in a previous summary. + +## Remarks +This method centralizes the conversation-summarization workflow behind a provider-agnostic streaming interface. It constructs a history consisting of a system prompt and the user-assembled content, then consumes the provider's delta stream until a FinishEvent to produce a coherent summary. It preserves existing context when a previous summary is supplied and uses a placeholder for messages that contain tool calls, deferring tool-output handling to the summarization layer. By delegating the actual summarization to the configured provider, this function remains agnostic to the underlying model and formats results consistently for downstream consumers. + +## Notes +- Tool content placeholders: If a message includes ToolCallsJson, this method substitutes '(requested tools)' in the input to the provider since it passes an empty ToolDescriptor list; actual tool outputs are not fetched in this path. +- Streaming dependency: The final text is composed from delta events produced by the provider and stops when a FinishEvent is received; if no deltas are produced, the returned string may be empty after trim. +- Cancellation aware: A CancellationToken is passed through to the provider, so callers can cancel long-running summarization. + +--- + +## GetContextMetricsAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +public async Task GetContextMetricsAsync( + Guid conversationId, + CancellationToken ct = default) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversationId` | `Guid` | — | +| `ct` | `CancellationToken` | `default` | + +**Returns:** `Task` + + +GetContextMetricsAsync computes a snapshot of the contextual metrics used for a given conversation. It validates the current user, loads the conversation with its messages scoped to that user, resolves the active model selection, and loads the relevant prompts (persona, project, memory, tools). It then builds an AgentContext from these pieces, derives a token breakdown, and returns a ContextMetrics object containing total tokens, the contextual window size, the compact threshold in tokens and ratio, the number of messages after the cut, and whether the conversation was summarized, along with per-block token tallies (system, project, memory, tools, and the conversation). This method should be used whenever you need backend-determined context metrics for display, logging, or analytics instead of re-deriving them client-side. + +## Remarks +This abstraction centralizes the end-to-end preparation of metrics around a conversation's context. It coordinates authentication, data retrieval, model selection, prompt loading, and token estimation into a single, testable surface, ensuring consistent metrics across UI and analytics layers. By design, it fails fast for unauthenticated users and missing conversations, guarding sensitive data and making the contract explicit. + +## Notes +- Requires an authenticated user; otherwise UnauthorizedAccessException is thrown immediately. +- If the requested conversation cannot be found or accessed for the user, NotFoundException is thrown (resource: Conversation, key: conversationId). +- The computed CompactThresholdTokens are derived from the selected window and ratio; if the window is not positive, the threshold is 0. + + +--- + +## LoadMemoryBlockAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async Task LoadMemoryBlockAsync(Guid? projectId, CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `projectId` | `Guid?` | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +Formats saved memories into a single system message by collecting memories for a given conversation and organizing them into two sections: user-scope (global) memories and project-scope memories. If there are no memories, it returns null; otherwise it builds a block starting with [Saved memories] and delegates the formatting of each entry to AppendMemory, finally trimming trailing whitespace. + +## Remarks +This abstraction centralizes how persistent user and project metadata is delivered to the model. By separating user-scope and project-scope memories, it gives the caller precise control over what facts are available globally versus within a project, reducing cross-project leakage. It also normalizes the memory format with a header and clear sections so downstream prompt machinery can rely on a consistent structure. + +## Notes +- If no memories exist for the given projectId, the method returns null, so callers should guard against injecting an empty block. +- The final string is produced by appending each memory via AppendMemory and then trimming trailing whitespace; changes to AppendMemory can affect the layout or content of the block. + +--- + +## LoadProjectSystemPromptAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async Task LoadProjectSystemPromptAsync(Conversation conversation, Guid userId, CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversation` | [`Conversation`](../../Gabriel.Core/Entities/Conversation.cs.md) | — | +| `userId` | `Guid` | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +Constructs a project-scoped system prompt for a conversation and returns null when no non-default project is attached. When a non-default project is present, it declares the project name, advises memory: save project-scoped facts with scope='project' and user-scoped facts otherwise, and appends the project's SystemPrompt if provided. + +## Remarks +Centralizes per-project context into a single, testable unit. It hides the details of how a project is looked up and how its metadata and optional SystemPrompt shape the memory-scope guidance, so callers simply receive a ready-to-use prompt or null when no project context applies. + +## Notes +- If the conversation lacks a ProjectId or the project is default or missing, the method returns null; callers must handle absence gracefully. +- The method delegates to _projects.GetByIdAsync and does not catch exceptions; callers should propagate or handle cancellation. + +--- + +## LoadTurnPromptsAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async Task LoadTurnPromptsAsync( + Conversation conversation, + Guid userId, + ModelSelection selection, + CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversation` | [`Conversation`](../../Gabriel.Core/Entities/Conversation.cs.md) | — | +| `userId` | `Guid` | — | +| `selection` | [`ModelSelection`](../../Gabriel.Core/Configuration/ModelSelection.cs.md) | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +Loads and composes the per-turn prompts needed to drive the model for the current conversation. It gathers the project/system prompt, the memory block tied to the project, and a persona prompt built from the conversation state and mode, then selects the available tool descriptors based on the requested ToolMode. The resulting TurnPrompts instance is consumed downstream by the streaming pipeline (e.g., RunStreamAsync) to ensure consistent context, metrics, and tool availability across the turn. + +## Remarks + +By centralizing turn-context assembly in AgentService, the system guarantees that all code paths share the same turn context regardless of whether the underlying transport uses Native or Emulated tool descriptors. The cancellation token is threaded through all load operations, ensuring cooperative cancellation. The persona, project, and memory elements are assembled in a single TurnPrompts, so downstream components observe a consistent snapshot of the turn's context. + +## Notes + +- Tool availability depends on ToolMode; ToolMode.None yields an empty ToolDescriptor list. +- All four components (personaPrompt, projectPrompt, memoryBlock, and tools) are assembled in a single TurnPrompts instance to guarantee consistency across streaming and metrics. +- Array.`Empty`() is used when there are no tools to advertise, avoiding allocations. + +--- + +## MaybeCompactAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async IAsyncEnumerable MaybeCompactAsync( + Conversation conv, + TurnPrompts prompts, + ModelSelection selection, + [EnumeratorCancellation] CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conv` | [`Conversation`](../../Gabriel.Core/Entities/Conversation.cs.md) | — | +| `prompts` | `TurnPrompts` | — | +| `selection` | [`ModelSelection`](../../Gabriel.Core/Configuration/ModelSelection.cs.md) | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `IAsyncEnumerable` + + +Decides whether to trigger a rolling-summary (compact) of the current turn by comparing the full AgentContext breakdown against the configured threshold, and if so, generates and applies a new summary for the most relevant recent messages. It yields events to coordinate UI and persistence (AgentCompactStart and AgentCompactDone) and updates the stored conversation when compaction succeeds. If the context already fits within the threshold or no meaningful cut can be made, it yields nothing. + +## Remarks +This method centralizes the logic for deciding when long conversations should be condensed to fit within model/window limits. It relies on a precise accounting of the full contextual breakdown (persona, project, memory, summary, and tools) to decide whether compaction changes the visible history. It also coordinates with the persistence layer and token estimation to ensure the UI reflects the ongoing operation and that the stored state is updated atomically after a successful summary. + +## Notes +- If the compact decision criteria are not met, or there is no valid segment to summarize, the method yields no events and exits gracefully. +- When a summary is attempted, a preliminary AgentCompactStart event is emitted so the UI can show a compacting overlay, followed by either AgentCompactDone with the resulting token count or AgentCompactDone with zero tokens if the operation fails or returns an empty summary. + + +--- + +## Preview +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private static string Preview(string? text) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `text` | `string?` | — | + +**Returns:** `string` + + +It returns a compact, single-line preview of the provided text for logging or quick UI presentation. If the input is null or empty, it returns "(empty)"; otherwise, it collapses newline characters to spaces and truncates long previews using a trailing ellipsis when the length exceeds LogPreviewLimit. + +## Remarks +This helper centralizes how previews are generated, ensuring consistent, readable log output across the code path that renders tool or user-provided content. It relies on the LogPreviewLimit barrier to keep previews short, and indicates continuation with a Unicode ellipsis, which communicates truncation without introducing extra lines. + +## Notes +- Truncation appends a single-character ellipsis …, so the final string may be one character longer than LogPreviewLimit. +- It uses string.IsNullOrEmpty, so whitespace-only strings are treated as non-empty and will be processed rather than returning "(empty)". + +--- + +## RegenerateAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +public async Task> RegenerateAsync( + Guid conversationId, + Guid assistantMessageId, + CancellationToken ct = default) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversationId` | `Guid` | — | +| `assistantMessageId` | `Guid` | — | +| `ct` | `CancellationToken` | `default` | + +**Returns:** `Task>` + + +RegenerateAsync replays the assistant's previous reply for a given conversation by regenerating the message using the same user state, and it streams back AgentEvent items as the regeneration progresses. Use it when you want a fresh take on a past turn without resending the user's prompt. + +## Remarks +This method centralizes the regeneration workflow in the engineering surface: it coordinates authentication, domain validation, state mutation, and the streaming of events from the regeneration pipeline. It encapsulates the nuance of deactivating the prior variant group and reusing the same group id to preserve UI navigation paths between alternatives. By returning an `IAsyncEnumerable`, it allows callers to progressively react to the regeneration steps as they are produced rather than awaiting a single result. It relies on the surrounding infrastructure (conversations repository, unit of work, and the streaming runner) to ensure the regeneration respects permissions and history integrity. + +## Example +```csharp +// Example usage: stream regeneration events for a specific conversation message +await foreach (var evt in agentService.RegenerateAsync(conversationId, assistantMessageId, ct)) +{ + // handle each AgentEvent as it is produced +} +``` + +## Notes +- Requires an authenticated user; otherwise an UnauthorizedAccessException is thrown. +- If the conversation or target message cannot be found, a NotFoundException is thrown; if the target is not an active assistant variant, a DomainException is thrown. +- The selected variant group is deactivated and the conversation state is updated and persisted; the regenerated turn uses the same variant group ID to preserve UI navigation across alternatives. + + +--- + +## ResolveModelSelectionAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async Task ResolveModelSelectionAsync(CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +Centralizes the resolution of the active model by first loading the user's preferences and then delegating to the model catalog to produce a ModelSelection for the chosen provider and model. This ensures that a change to the user's preferred provider or model is reflected on the very next turn, without duplicating the resolution logic at every entry point. + +## Remarks +By funneling all callers through ResolveModelSelectionAsync, the system gains a single source of truth for mapping user preferences to a concrete ModelSelection. It cleanly separates concerns: preference storage and the catalog-based resolution are decoupled from the call sites that need the active model. This setup also simplifies testing, as you can mock the prefs store and the catalog to verify that changes in preferences propagate to the resulting ModelSelection. + +--- + +## RunAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +public async Task> RunAsync( + Guid conversationId, + string userInput, + CancellationToken ct = default) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversationId` | `Guid` | — | +| `userInput` | `string` | — | +| `ct` | `CancellationToken` | `default` | + +**Returns:** `Task>` + + +Runs a single user turn in the chat agent workflow. It validates input, ensures an authenticated user, loads the target conversation with its messages, persists the user message and updates the conversation state, resolves the model to use, loads the per-turn prompts, configures the tool-execution context, and finally streams the per-turn events via RunStreamWithUserPreambleAsync. + +## Remarks +This method centralizes all per-turn preparation and streaming so every turn shares a single, consistent surface: the chosen model, the loaded prompts, and the scoped tool context. Persisting the user message before streaming guarantees the conversation timeline remains coherent even if the client disconnects mid-turn; the subsequent stream reflects a single, authoritative turn that downstream components can rely on for rendering and metrics. + +## Example +```csharp +// Example: consume the streaming events for a user turn +await foreach (var evt in agentService.RunAsync(conversationId, userInput, cancellationToken)) +{ + // forward or render the streaming event to the client + Console.WriteLine($"Event: {evt?.GetType().Name}"); +} +``` + +## Notes +- The method will throw DomainException when the input message is empty, and UnauthorizedAccessException if there is no authenticated user. If the conversation cannot be found, NotFoundException is raised with details about the resource. +- The returned value is an `IAsyncEnumerable`, representing a streaming sequence of events (including preambles and post-processing events) that the client can consume as the model generates its reply. + +--- + +## RunStreamAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async IAsyncEnumerable RunStreamAsync( + Conversation conversation, + Guid? variantGroupIdOverride, + TurnPrompts prompts, + ModelSelection selection, + [EnumeratorCancellation] CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversation` | [`Conversation`](../../Gabriel.Core/Entities/Conversation.cs.md) | — | +| `variantGroupIdOverride` | `Guid?` | — | +| `prompts` | `TurnPrompts` | — | +| `selection` | [`ModelSelection`](../../Gabriel.Core/Configuration/ModelSelection.cs.md) | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `IAsyncEnumerable` + + +RunStreamAsync streams the assistant’s output by repeatedly rebuilding the conversation history and querying the active chat provider, yielding AgentEvent-derived deltas (text and reasoning) as they arrive and handling tool calls when the provider emits ToolCallReadyEvent. When tools are emulated, the provider is wrapped with GabrielToolBridge to inject tool docs; a finite retry loop protects against intermittent empty finishes, preserving history across iterations. + +## Remarks +This symbol acts as the streaming funnel between the agent's decision loop and the underlying chat provider, encapsulating the nuances of tool integration (via GabrielToolBridge) and history management. It centralizes iteration-level context recomposition so that tool calls and tool results are preserved across turns, while isolating resilience logic (empty-stop retries) from higher-level orchestration. + +## Notes +- It relies on an empty-stop retry strategy; if the provider consistently returns FinishEvent with no data, the method will retry up to EmptyStopMaxRetries, potentially delaying error reporting. +- In Emulated tool mode, it expects compatible tool descriptors to ensure tool docs are injected correctly; misconfiguration can cause tool docs to be omitted. + +--- + +## RunStreamWithUserPreambleAsync +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private async IAsyncEnumerable RunStreamWithUserPreambleAsync( + Guid userMessageId, + Conversation conversation, + Guid? variantGroupIdOverride, + TurnPrompts prompts, + ModelSelection selection, + [EnumeratorCancellation] CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `userMessageId` | `Guid` | — | +| `conversation` | [`Conversation`](../../Gabriel.Core/Entities/Conversation.cs.md) | — | +| `variantGroupIdOverride` | `Guid?` | — | +| `prompts` | `TurnPrompts` | — | +| `selection` | [`ModelSelection`](../../Gabriel.Core/Configuration/ModelSelection.cs.md) | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `IAsyncEnumerable` + + +Produces an asynchronous stream of AgentEvent that starts by emitting an AgentUserMessagePersisted event for the provided userMessageId and then yields the events produced by RunStreamAsync with the given parameters (conversation, variantGroupIdOverride, prompts, selection, ct). The cancellation token is forwarded to RunStreamAsync, so cancellation requests terminate the underlying streaming. + +## Remarks + +This helper composes persistence signaling with streaming into a single sequence, guaranteeing that the user message is recorded before any agent-generated events are observed. This ordering prevents a scenario where streaming begins without a corresponding persisted user message, and it centralizes the preamble behavior so call sites can opt into the pre-persist signal without duplicating boilerplate. + +## Notes + +- The first yielded item is AgentUserMessagePersisted; if RunStreamAsync later fails, the preamble has already been produced, which can influence downstream error handling. +- The method is private; external callers should use RunStreamAsync directly when the preamble is not required. +- Enumeration is lazy and cancellation is propagated through ct to the underlying stream. + +--- + +## SelectCompactCutIndex +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private static int SelectCompactCutIndex(IReadOnlyList messages, int keepLast) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `messages` | `IReadOnlyList` | — | +| `keepLast` | `int` | — | + +**Returns:** `int` + + +SelectCompactCutIndex determines where to cut a message history when creating a compacted conversation for model input. It preserves at least the last keepLast messages from the end, then walks backward to find the most recent User message and uses that position as the cut boundary. This anchoring ensures the model sees an assistant's tool invocation and the corresponding tool results together, avoiding splits that would separate related tool calls and results. + +## Remarks +This small helper centralizes the invariant that tool invocations and their results must not be separated by history trimming. It relies on the Message and MessageRole abstractions to identify the boundary at the most recent User message, making the boundary calculation deterministic and consistent across calls. By isolating this logic, callers can reliably trim history without duplicating boundary logic or risking misalignment between tool usage and outcomes. + +## Notes +- If keepLast is greater than or equal to messages.Count, the function returns 0. +- If there is no User message encountered before reaching the start of the list, the function returns 0. + +--- + +## SerializeToolCalls +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** method + +```csharp +private static string SerializeToolCalls(IReadOnlyList calls) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `calls` | `IReadOnlyList` | — | + +**Returns:** `string` + + +SerializeToolCalls takes a read-only list of ChatProviderToolCall and produces a JSON string that represents the calls. It projects each item to a compact representation containing id, type, and a nested function with the Name and ArgumentsJson of the call, then serializes the array using JsonSerializer. The resulting string is a stable, transportable representation of tool-call activity that can be consumed by clients or logged for auditing. + +## Remarks +- It centralizes the JSON shape of tool-call events to ensure consistency across consumers. It defines a stable, forward-compat JSON schema for tool calls, decoupling the in-memory model from the serialized form. +- It relies on the assumption that ArgumentsJson already contains a JSON payload; it does not attempt to parse or reformat that content. + +## Notes +- This method does not guard against null input; callers must pass a non-null `IReadOnlyList`. +- The serialized output uses the literal property names id, type, function, name, and arguments as defined by the code; any changes to the anonymous type shape will alter the produced JSON. + +--- + +## EmptyStopMaxRetries +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** field + +```csharp +private const int EmptyStopMaxRetries = 2 +``` + + +Provides a bounded retry budget for the “provider finished Stop with empty text” hiccup inside the agent loop. When the response arrives as a successful HTTP 200 stream with empty content, the retry must reside in the agent rather than the HTTP resilience pipeline. This constant allows up to two additional attempts (three total including the initial try) to ride through transient blanks without noticeably delaying a real failure path. The backoff is linear: the delay before the Nth retry is N × EmptyStopRetryDelayMs, where EmptyStopRetryDelayMs is defined in the dependency surface. + +## Remarks +By isolating the retry cap in a single constant, the agent loop stays readable and testable, and the policy can be adjusted without reworking loop logic. It also documents the intent behind the three-attempt boundary, grounding behavior in a concrete, maintainable policy. This abstraction clarifies that the three-attempt cap is a policy decision for transient empty-text events the HTTP resilience path won't address. + +## Notes +- If EmptyStopRetryDelayMs is non-positive, the backoff becomes zero or negative; ensure it's a positive duration. + +--- + +## EmptyStopRetryDelayMs +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** field + +```csharp +private const int EmptyStopRetryDelayMs = 500 +``` + + +Represents the fixed delay (in milliseconds) used between retry attempts for the empty-stop scenario in AgentService. With a value of 500, it prevents busy-waiting by introducing a short pause between retries, contributing to more predictable timing during retry loops. + +## Remarks +Centralizing this timing value in a private constant makes the retry behavior consistent and avoids scattering numeric literals across the class. The private scope signals that this is an internal tuning knob used only by AgentService; if configurability is needed, consider exposing it or moving it to settings. The 500 ms value balances responsiveness with CPU efficiency for typical retry storms. + +## Notes +- This constant is baked into the compiled code; changing it requires recompilation and retesting. +- Because it's private, other components cannot reuse this delay; if reuse is needed, refactor to expose via a configurable option or internal API. + +--- + +## LogPreviewLimit +> **File:** `src/api/Gabriel.Engine/Services/AgentService.cs` +> **Kind:** field + +```csharp +private const int LogPreviewLimit = 240 +``` + + +LogPreviewLimit defines the maximum number of characters from tool arguments or results that may be included in a log message. By capping log length, it prevents large payloads from bloating log files while preserving enough information for debugging. It is used internally by the AgentService when composing logs to keep messages concise and avoid leaking excessive data. + +## Remarks + +Having this as a private, compile-time constant ensures a single, predictable logging boundary across the codebase. The value is fixed at compile time, so changing it requires recompilation of dependents, making the boundary intentional and explicit. This helps reduce the risk of logging sensitive or oversized data inadvertently. + +## Example + +```csharp +string payload = new string('x', 1000); +string truncated = payload.Length > LogPreviewLimit ? payload.Substring(0, LogPreviewLimit) : payload; +Console.WriteLine(truncated); +``` + +## Notes + +- Substring with a fixed length can throw if the source is shorter than the requested length; guard with a length check or use a safe truncation pattern (as shown in the example). +- Changing the value requires rebuilding all consuming code since it is a const. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ContextMetrics.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ContextMetrics.cs.md new file mode 100644 index 0000000..4e688b3 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ContextMetrics.cs.md @@ -0,0 +1,66 @@ +# ContextMetrics + +> **File:** `src/api/Gabriel.Engine/Services/ContextMetrics.cs` +> **Kind:** record + +```csharp +public record ContextMetrics( + int CurrentTokens, + int ContextWindowTokens, + int CompactThresholdTokens, + double CompactThresholdRatio, + int MessagesAfterCut, + bool IsSummarized, + int SummaryTokens, + int SystemPromptTokens, + int ProjectPromptTokens, + int MemoryTokens, + int ToolsTokens, + int ConversationTokens) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `CurrentTokens` | `int` | — | +| `ContextWindowTokens` | `int` | — | +| `CompactThresholdTokens` | `int` | — | +| `CompactThresholdRatio` | `double` | — | +| `MessagesAfterCut` | `int` | — | +| `IsSummarized` | `bool` | — | +| `SummaryTokens` | `int` | — | +| `SystemPromptTokens` | `int` | — | +| `ProjectPromptTokens` | `int` | — | +| `MemoryTokens` | `int` | — | +| `ToolsTokens` | `int` | — | +| `ConversationTokens` | `int` | — | + + +ContextMetrics is a C# record that captures a snapshot of the token budget and contextual framing for the next agent turn. It is surfaced by the backend via IAgentService.GetContextMetricsAsync so the UI can render indicators like a context usage gauge and the point at which the context would be compacted, ensuring the frontend view matches the backend's decision to trim, summarize, or adjust the context. + +The fields provide a categorized breakdown of token usage (system prompts, memory, tools, and the per-turn conversation content) that, together with the total CurrentTokens, reflect how the upcoming message set will fit within the context window. The per-category breakdown mirrors what ToProviderHistory assembles, enabling the UI grid to sum to CurrentTokens and stay aligned with the backend's accounting. + +## Remarks +ContextMetrics acts as a bridge between the agent's internal token-budget model and the user-facing indicators. It decouples the cost calculus from rendering, so UI components can react to changes in summarization, memory usage, or prompt overrides without re-deriving the entire history. The breakdown helps diagnose which parts of the prompt and conversation contribute most to the current token footprint, facilitating informed tuning by developers and operators. + +## Example +```csharp +var metrics = new ContextMetrics( + CurrentTokens: 1200, + ContextWindowTokens: 1500, + CompactThresholdTokens: 1000, + CompactThresholdRatio: 0.75, + MessagesAfterCut: 2, + IsSummarized: false, + SummaryTokens: 200, + SystemPromptTokens: 320, + ProjectPromptTokens: 0, + MemoryTokens: 150, + ToolsTokens: 60, + ConversationTokens: 980); +``` + +## Notes +- ContextMetrics represents a point-in-time snapshot for the upcoming turn; changes to user input or prompts will produce new metrics. +- The per-category fields should align with the total CurrentTokens and reflect the same conceptual partitions used by the provider history and UI visualizations. When a project-level prompt override or memories are in effect, the corresponding tokens will reflect those contributions. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IAgentService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IAgentService.cs.md new file mode 100644 index 0000000..feacd2a --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IAgentService.cs.md @@ -0,0 +1,32 @@ +# IAgentService + +> **File:** `src/api/Gabriel.Engine/Services/IAgentService.cs` +> **Kind:** interface + +```csharp +public interface IAgentService +``` + + +IAgentService is an abstraction over the per-turn agent workflow for a chat conversation. It exposes three asynchronous operations: RunAsync drives the ReAct loop for a single user turn and yields a stream of AgentEvent events, RegenerateAsync creates an alternate assistant reply for an existing message, and GetContextMetricsAsync returns a snapshot of the current context-window state for progress awareness. Callers receive a streaming `IAsyncEnumerable` from RunAsync, with synchronous pre-flight validation that may throw to allow the API layer to respond appropriately before streaming begins; any in-stream failures are surfaced as AgentEvent items. + +## Remarks +IAgentService centralizes the orchestration of a single-turn interaction, decoupling the API surface from concrete implementations and enabling testability via the interface. It coordinates a conversation via conversationId, processes the user's input, and supports cancellation through CancellationToken. RegenerateAsync ties into the same variant-tracking flow used by the UI to present alternative assistant replies, while GetContextMetricsAsync feeds UI components with context-usage awareness so users can gauge how close they are to configured thresholds. The combination of real-time AgentEvent streaming and contextual metrics enables responsive applications that reflect both progress and results as they evolve. + +## Example +```csharp +// Example: driving a single-turn interaction and inspecting metrics +async Task DemoAsync(IAgentService service, Guid conversationId, string userInput, CancellationToken ct) +{ + await foreach (var evt in service.RunAsync(conversationId, userInput, ct)) + { + // handle AgentEvent (real-time updates, streaming results, or errors) + } + + ContextMetrics metrics = await service.GetContextMetricsAsync(conversationId, ct); + // use metrics to render a progress indicator +} +``` + +## Notes +- RunAsync and RegenerateAsync perform pre-flight validation and may throw synchronously for common bad inputs (e.g., empty input, missing conversation, not-found or inappropriate variant). Ensure callers map these exceptions to appropriate HTTP responses before streaming starts. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IMetricRecorder.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IMetricRecorder.cs.md new file mode 100644 index 0000000..3c23ef2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/IMetricRecorder.cs.md @@ -0,0 +1,32 @@ +# IMetricRecorder + +> **File:** `src/api/Gabriel.Engine/Services/IMetricRecorder.cs` +> **Kind:** interface + +```csharp +public interface IMetricRecorder +``` + + +IMetricRecorder provides a convenient surface for writing metric events to the generic metric event log. Subsystems should use this interface rather than depending directly on the storage repository; it serializes the payload to JSON and absorbs storage errors so a metric-record failure never bubbles up into the caller's business path. + +## Remarks +This abstraction decouples metric reporting from the storage implementation and makes metric writes best-effort. The stable, dotted system name (e.g. web_search.tavily, agent_loop.iteration) lets read-side tooling group and analyze events consistently, while the flexible payload lets subsystems evolve what they measure without API changes. The inclusion of an outcome field (success | error | empty) is a common convention to support high-level aggregation without inspecting every payload attribute. + +## Example +```csharp +// Wire example +var payload = new Dictionary +{ + ["outcome"] = "success", + ["query"] = "...", + ["result_count"] = 5, + ["latency_ms"] = 287 +}; +await recorder.RecordAsync("web_search.tavily", payload, CancellationToken.None); +``` + +## Notes +- Writes are best-effort; metric storage failures are swallowed by design and should not affect business logic. +- The payload must be JSON-serializable; using a `Dictionary` ensures compatibility with common JSON serializers. +- The CancellationToken parameter is optional; pass a token if you need to cancel the write. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ITokenEstimator.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ITokenEstimator.cs.md new file mode 100644 index 0000000..cacc1e9 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/ITokenEstimator.cs.md @@ -0,0 +1,19 @@ +# ITokenEstimator + +> **File:** `src/api/Gabriel.Engine/Services/ITokenEstimator.cs` +> **Kind:** interface + +```csharp +public interface ITokenEstimator +``` + + +ITokenEstimator is a pluggable contract for estimating token usage, enabling callers to budget prompts without tying themselves to a specific tokenizer. It exposes EstimateText for plain text and EstimateMessages for a sequence of Message objects, allowing the underlying tokenizer to be swapped later (e.g., from a naive char-based estimator to a real BPE tokenizer) without touching callers. + +## Remarks +ITokenEstimator decouples token counting from higher-level logic, so orchestration code can reason about costs and token limits independently from how tokens are computed. By supporting both raw text and collections of Message objects, it accommodates estimations for standalone prompts as well as chat histories. The interface is designed to be easy to mock in tests and to enable swapping tokenizer implementations across the Gabriel engine without changing consumer code. + +## Notes +- Null handling: the text parameter is nullable; implementations should handle null gracefully (e.g., treat as zero tokens or skip). +- EstimateMessages semantics: ensure the enumeration is stable and side-effect free; avoid altering input sequences. +- Performance considerations: large collections of messages may be expensive to estimate; implementations should avoid multiple passes or offer streaming-friendly approaches where possible. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/MetricRecorder.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/MetricRecorder.cs.md new file mode 100644 index 0000000..08805d4 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/MetricRecorder.cs.md @@ -0,0 +1,19 @@ +# MetricRecorder + +> **File:** `src/api/Gabriel.Engine/Services/MetricRecorder.cs` +> **Kind:** class + +```csharp +public sealed class MetricRecorder : IMetricRecorder +``` + + +MetricRecorder is a singleton service that records metric payloads into a persistent store by serializing them to snake_case JSON and persisting them as MetricEntry records tagged with a system name. It serves as a safe, record-and-forget bridge between telemetry producers (such as decorators, tooling, and the agent loop) and the repository, ensuring scoped resources are resolved per call via a DI scope. If the system name is empty or whitespace, or if serialization fails, the payload is dropped with a warning; non-cancellation persistence failures are logged and swallowed to avoid destabilizing the host, while cancellation requests propagate to the caller. + +## Remarks +This abstraction decouples metric emission from persistence concerns and centralizes the failure policy for telemetry writes. A per-call DI scope (created from IServiceScopeFactory) ensures that scoped services (like repositories or DbContexts) do not leak through the singleton. Storing the metric payload as a string JSON blob (instead of returning strong-typed metrics from the recorder) provides a flexible, schema-evolution-friendly approach while allowing subsystems to override their DTOs with JsonPropertyName attributes without affecting the recorder itself. The snake_case payload aligns with downstream querying conventions and the raw payload can be inspected in the Metric column when needed. + +## Notes +- Empty or whitespace system names drop the payload with a warning. +- Serialization failures drop the payload with a warning. +- Non-cancellation persistence failures are logged and swallowed; OperationCanceledException is rethrown to propagate cancellation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs.md new file mode 100644 index 0000000..c9f8a7d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs.md @@ -0,0 +1,19 @@ +# NaiveTokenEstimator + +> **File:** `src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs` +> **Kind:** class + +```csharp +public class NaiveTokenEstimator : ITokenEstimator +``` + + +NaiveTokenEstimator is a lightweight, coarse-grained implementation of ITokenEstimator. It provides a fast heuristic to estimate token usage for a single text fragment and for a collection of messages by counting characters and adding a fixed per-message overhead, suitable for development and prototyping where shipping a real tokenizer is overkill. + +## Remarks +This abstraction isolates a pragmatic budgeting tool from the complexities of real tokenizers. It relies on the Message structure's Content, ToolCallsJson, and ToolCallId to approximate the portion of tokens consumed by each message's content and any tool-use metadata, enabling quick experiments with context window planning. If a real tokenizer is introduced later, this class can be swapped to implement the same interface without changing callers. + +## Notes +- This is a rough estimate, not a precise token count, and should not be used for billing or strict budgeting in production. +- The per-message overhead is a fixed 8 tokens, which may not reflect actual protocol/serialization costs in all scenarios. +- Null values for Content, ToolCallsJson, or ToolCallId contribute zero tokens due to the null-safe helper used by EstimateText. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md new file mode 100644 index 0000000..809dbf4 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md @@ -0,0 +1,29 @@ +# CalculateTool + +> **File:** `src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs` +> **Kind:** class + +```csharp +public sealed class CalculateTool : ITool +``` + + +Evaluate a mathematical expression and return a numeric result as a plain string. Reach for CalculateTool whenever an agent needs reliable numeric evaluation of an arithmetic expression (sums, products, powers, roots, trig, common functions) instead of trying to compute the value in-protocol; the tool parses and evaluates the expression and returns either " = " or an "Error: ..." observation so callers never receive an exception. + +## Remarks +CalculateTool is a thin, self-contained evaluator wrapper that delegates parsing and numeric computation to the internal Evaluator class and enforces a safe, stable contract for callers: no I/O, no thrown exceptions for bad input, and defensive limits to avoid pathological inputs. Errors such as malformed syntax, unknown names, division by zero, or numeric overflow are converted into human-readable "Error: ..." strings rather than propagated as exceptions, keeping calling agent loops stable. The tool also enforces expression limits (MaxExpressionLength = 1000 and MaxDepth = 64) to guard against deep recursion or extremely large inputs. + +## Example +```csharp +// Call the tool with a JSON argument containing the expression string. +var tool = new CalculateTool(); +var result = await tool.ExecuteAsync("{\"expression\": \"(1234 * 9) / 7\"}", CancellationToken.None); +// result => "(1234 * 9) / 7 = 1586" +``` + +## Notes +- The JSON argument must contain an "expression" property whose value is a string; otherwise the tool returns an error. +- Operators are required between terms (write `2*pi`, not `2pi`). +- Trigonometric functions use radians. +- Unary sign and precedence follow the documented rules (exponentiation is right-associative; unary minus binds as shown: `-3^2 = -9`). +- If evaluation produces NaN or Infinity the tool returns a corresponding "Error: ..." message rather than a numeric value. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md new file mode 100644 index 0000000..25deb78 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md @@ -0,0 +1,27 @@ +# Base64Tool + +> **File:** `src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs` +> **Kind:** class + +```csharp +public sealed class Base64Tool : ITool +``` + + +Base64Tool is a pure function that encodes text to Base64 or decodes a Base64 string back to text, operating strictly on its arguments with no I/O or external dependencies. It treats input as UTF-8, enforces a maximum length, and supports URL-safe encoding when requested while decoding accepts both standard and URL-safe inputs. + +## Remarks +Base64Tool serves as a focused, dependency-free utility that encapsulates the common Base64 encode/decode workflow used by higher-level tooling. It provides consistent validation and error reporting through Base64Exception and a uniform ExecuteAsync entry, enabling predictable composition with other tools. This isolation prevents ad-hoc encoding logic scattered across the codebase and makes behavior consistent for both UTF-8 text and URL-safe variants. + +## Example +```csharp +var tool = new Base64Tool(); +var json = "{\"text\":\"sample text\",\"op\":\"encode\"}"; +var result = await tool.ExecuteAsync(json, CancellationToken.None); +// result starts with "Encoded: " +``` + +## Notes +- 'text' cannot be empty and is limited to a maximum of 100000 characters; violations throw Base64Exception. +- Decoding accepts both standard and URL-safe input; encoding can emit the URL-safe alphabet when url_safe is true. +- Results are returned as a string prefixed with either "Encoded: " or "Decoded: " to indicate the operation performed. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md new file mode 100644 index 0000000..c4ccc81 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md @@ -0,0 +1,29 @@ +# HashTool + +> **File:** `src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs` +> **Kind:** class + +```csharp +public sealed class HashTool : ITool +``` + + +HashTool computes a cryptographic hash digest of the provided text and returns it as lowercase hexadecimal, prefixed with the algorithm name. It is a pure function with no I/O or external dependencies; you pass UTF-8 text and an optional algorithm to obtain a stable fingerprint of the input, useful for integrity checks or deduplication. The default algorithm is sha256, and you may choose md5, sha1, sha256, or sha512 (md5/sha1 are legacy checksums). The text length is limited to 1,000,000 characters, and the empty string is allowed. The return format is a single string like ": " (lowercase hex). If arguments are invalid, the method returns an error string rather than throwing an exception. + +## Remarks +HashTool serves as a focused, testable abstraction for producing deterministic digests within the agent workflow. It centralizes UTF-8 input handling and hex-encoded output formatting, enacting a hard input limit to guard against pathological inputs. While it supports md5 and sha1 for legacy interoperability, those algorithms are explicitly treated as non-security checksums. Validation errors surface as a plain error string rather than as exceptions, and a private HashException distinguishes argument validation from genuine runtime bugs. + +## Example +```csharp +using System.Threading; + +var tool = new HashTool(); +var json = "{\"text\":\"abc\"}"; +var result = await tool.ExecuteAsync(json, CancellationToken.None); +// result == "sha256: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" +``` + +## Notes +- The returned string always starts with the chosen algorithm followed by a colon and a space, e.g., "sha256: ...". To extract the digest, split on the first ": ". +- If the input JSON is malformed, or if the required "text" field is missing or not a string, the tool returns an error string (e.g., "Error: ..."). +- The private HashException class is used to distinguish validation errors from real runtime failures; callers should rely on the normal string return value for all outcomes. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md new file mode 100644 index 0000000..8a0a647 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md @@ -0,0 +1,37 @@ +# ColorConvertTool + +> **File:** `src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs` +> **Kind:** class + +```csharp +public sealed partial class ColorConvertTool : ITool +``` + + +Converts a single color value between hex, rgb()/rgba(), and hsl()/hsla() notations and (optionally) returns just one target format or all three. Use this tool whenever you need a reliable channel-aware conversion or to preserve an alpha channel instead of implementing RGB↔HSL math yourself. + +## Remarks +This class exists to centralize the fiddly, easy-to-get-wrong math involved in color-channel conversions and to provide consistent validation and error messages for input strings. It is a pure, stateless converter: no I/O, no external dependencies, and it preserves alpha when present. The tool accepts short/long hex forms, rgb()/rgba(), and hsl()/hsla(), and can either return a single requested notation or a block containing all three. + +## Example +```csharp +// Create the tool and call ExecuteAsync with a JSON argument string. +var tool = new ColorConvertTool(); + +// Ask for all three notations (omit "to"): result is a newline-separated block +// with lines that start with "hex:", "rgb:", and "hsl:". +var allFormats = await tool.ExecuteAsync("{\"value\": \"#03f\"}", CancellationToken.None); +Console.WriteLine(allFormats); + +// Request a single target notation. The returned string is of the form +// "" (alpha is preserved if present). +var single = await tool.ExecuteAsync("{\"value\": \"rgba(255, 0, 0, 0.5)\", \"to\": \"hex\"}", CancellationToken.None); +Console.WriteLine(single); +``` + +## Notes +- Named CSS color keywords (e.g. "red") are not supported; input must be hex, rgb()/rgba(), or hsl()/hsla(). +- Input validation errors are returned as a string prefixed with "Error: " rather than thrown to the caller (ExecuteAsync catches ColorException and returns the message). +- The 'value' property must be a non-empty string no longer than 200 characters; the optional 'to' must be one of "hex", "rgb", or "hsl". +- The converter preserves any alpha channel provided in the input. +- The tool is stateless and safe for concurrent use from multiple callers. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs.md new file mode 100644 index 0000000..3c7dbf0 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs.md @@ -0,0 +1,29 @@ +# JsonFormatTool + +> **File:** `src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs` +> **Kind:** class + +```csharp +public sealed class JsonFormatTool : ITool +``` + + +JsonFormatTool provides a pure function to validate, pretty-print, or minify a JSON string. It delegates parsing and serialization to System.Text.Json, wrapping common formatting modes behind a user-friendly API and returning either the formatted JSON, a single-line minified string, or a validation message. The mode can be pretty (default), minify, or validate; invalid JSON yields a precise error location, and argument parsing validates required fields and length constraints. This tool is designed for human-readable JSON in logs or UIs and quick correctness checks without performing I/O. + +## Remarks +JsonFormatTool encapsulates a small, focused concern: transforming JSON text according to a requested presentation while reporting errors consistently. By centralizing formatting behavior (indentation, escaping) and leveraging a standard library for parsing/serialization, it provides a predictable, testable surface that callers can reuse across tooling without duplicating logic. The use of relaxed JSON escaping keeps non-ASCII characters readable in output, which is helpful for human inspection and diagnostics. + +## Example +```csharp +// Example usage: pretty-print a JSON string +var tool = new JsonFormatTool(); +string args = "{\"json\": \"{\\\"name\\\":\\\"Alice\\\",\\\"age\\\":30}\", \"mode\": \"pretty\"}"; +string result = await tool.ExecuteAsync(args, CancellationToken.None); +``` + +## Notes +- If JSON is invalid, the result begins with "Error: invalid JSON at line X, position Y". +- The input must contain a non-empty string property named 'json'; an optional 'mode' may be provided and must be one of: pretty, minify, validate. +- The maximum allowed length for the JSON string is 500,000 characters. +- The default mode is pretty when mode is omitted. +- This tool performs formatting/validation without side effects or I/O. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md new file mode 100644 index 0000000..8df1c2f --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md @@ -0,0 +1,21 @@ +# DocsListTool + +> **File:** `src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs` +> **Kind:** class + +```csharp +public sealed class DocsListTool : ITool +``` + + +DocsListTool retrieves Gabriel's official docs and renders a two-source, path-sorted catalog, prioritizing the LLM-native pages for quick, canonical reference. Use it when you need a complete inventory of what Gabriel's documentation covers and a direct path you can pass to docs_read to view a page. + +## Remarks + +It provides a single, authoritative view by grouping entries by their source (local LLM-native vs GitHub human-prose) and presenting them in path order. This design helps you quickly assess coverage and navigate to a page without manually scanning multiple sources. The tool gracefully reports errors from the underlying docs lookup and returns a clear fallback message when docs are unavailable. + +## Notes + +- Deterministic ordering: LocalLlmNative group comes first according to source priority. +- Empty results trigger a user-facing message suggesting fallback discovery. +- Titles are shown alongside paths when available; entries are listed in path-sorted order. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md new file mode 100644 index 0000000..1a8065e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md @@ -0,0 +1,18 @@ +# DocsReadTool + +> **File:** `src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs` +> **Kind:** class + +```csharp +public sealed class DocsReadTool : ITool +``` + + +DocsReadTool retrieves an official Gabriel documentation page by a given path and returns the content wrapped in explicit authority markers so downstream consumers treat it as canonical Gabriel knowledge. It uses an injected IDocsLookup to read the page and provides clear, self-describing errors when the input is invalid or the page cannot be found, guiding developers to use docs_list to discover available paths. + +## Remarks +This symbol exists to centralize access to Gabriel's official documentation and to protect the returned content from being blended with auxiliary context. By wrapping the content with explicit "BEGIN/END" markers and including a source tag (e.g., local-llm-native or github), it creates a trustworthy, ground-truth snippet that downstream responses can rely on when explaining architecture, agent loop, or internal APIs. The tool deliberately defers path discovery to docs_list, keeping the retrieval path explicit and auditable, and it surfaces errors that encourage developers to verify available documentation before attempting reads. + +## Notes +- Ensure you call docs_list to discover valid document paths before attempting a read; this tool does not enumerate available docs itself. +- The returned payload is annotated with origin information and is intended to be treated as ground truth about Gabriel; avoid blending it with unrelated context. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md new file mode 100644 index 0000000..275cf52 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md @@ -0,0 +1,140 @@ +# IDocsLookup.cs + +> **Source:** `src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs` + +## Contents + +- [IDocsLookup](#idocslookup) +- [DocsSources](#docssources) +- [DocsContent](#docscontent) +- [DocsEntry](#docsentry) + +--- + +## IDocsLookup +> **File:** `src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs` +> **Kind:** interface + +```csharp +public interface IDocsLookup +``` + + +IDocsLookup defines a minimal, async contract for discovering and reading Gabriel's official internal documentation from pluggable backends. It is implemented by local and remote lookups (e.g., LocalDocsLookup and GitHubDocsLookup) and composed through a facade such as CompositeDocsLookup to present a single, unified API to tooling. + +IDocsLookup exposes two asynchronous operations: ListAsync to enumerate available documentation entries, and ReadAsync to retrieve the content of a doc by path. This interface is designed as a facade over multiple inner sources, allowing tooling to remain agnostic to where docs come from (local disk, remote repositories, or cached stores). + +## Remarks +IDocsLookup centralizes access to Gabriel's docs, enabling the tooling to swap sources or combine results without changing call sites. By decoupling consumers from concrete storage, it supports scenarios such as local development, caching layers, and remote provisioning while keeping the surface stable. + +## Notes +- Both ListAsync and ReadAsync are asynchronous and accept CancellationToken to support cooperative cancellation. +- ReadAsync may return null if the specified path cannot be resolved by any configured source. +- The exact interpretation of the path is implementation-specific; callers should rely on the underlying DocsEntry semantics when constructing paths. + +--- + +## DocsSources +> **File:** `src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs` +> **Kind:** class + +```csharp +public static class DocsSources +``` + + +DocsSources is a static utility class that defines canonical identifiers used by the documentation lookup system. It exposes two string constants named LocalLlmNative and GitHub, with values "local-llm-native" and "github", respectively. Use these constants instead of literal strings to avoid typos and to enable centralized updates. When building a documentation query or selecting a source, reference DocsSources.LocalLlmNative or DocsSources.GitHub. Since the class is static and contains only constants, it cannot be instantiated and does not maintain state—it's a typed bag of well-known keys. + +## Remarks +DocsSources centralizes the source keys to prevent scattering of magic strings and to provide a clear contract for what sources are supported by the docs subsystem. It helps decouple consumers from exact string literals and makes it easier to evolve the set of sources over time. The constants reflect the supported destinations and are intended to be consumed by the repository's tooling that resolves documentation sources. + +## Example +```csharp +string key = DocsSources.LocalLlmNative; +``` + +## Notes +- The constants are compile-time constants; they cannot be changed at runtime. +- Add new sources here and update all call sites accordingly to maintain consistency. + +--- + +## DocsContent +> **File:** `src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs` +> **Kind:** record + +```csharp +public sealed record DocsContent(string Path, string Content, string? CanonicalUrl, string Source) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Path` | `string` | — | +| `Content` | `string` | — | +| `CanonicalUrl` | `string?` | — | +| `Source` | `string` | — | + + +DocsContent is a lightweight, immutable data holder that encapsulates a single documentation page retrieved by the docs lookup workflow. It carries the page's path, the raw content, an optional canonical URL used for citations, and a source indicator that reveals where the content originated. This record is typically produced by ReadAsync and consumed by renderers or indexers that need both content and provenance in one object. + +## Remarks +DocsContent's role is to separate content from its provenance while offering a stable, serializable container that can be passed through the rendering pipeline or to citation/branding features. The CanonicalUrl property helps maintain a stable, linkable reference to the doc—even if its local path changes—while Source records the provenance, aiding diagnostics and caching. + +## Example +```csharp +// Example: construct a DocsContent instance representing a locally stored doc +var doc = new DocsContent( + "docs/intro.md", + "# Introduction\nThis is an introduction.", + "file:///C:/Projects/Gabriel.Engine/docs/intro.md", + "local" +); +``` + +## Notes +- CanonicalUrl may be null; callers should gracefully handle the absence of a canonical link when rendering citations. +- DocsContent being a record means it is immutable; use the with-expression to derive a modified copy rather than mutating. + +--- + +## DocsEntry +> **File:** `src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs` +> **Kind:** record + +```csharp +public sealed record DocsEntry(string Path, string? Title, string Source) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Path` | `string` | — | +| `Title` | `string?` | — | +| `Source` | `string` | — | + + +DocsEntry is an immutable, value-based record that holds the metadata for a single documentation entry: Path, an optional Title, and Source. It serves as a lightweight data carrier within the documentation lookup/catalog workflow, enabling components to transport, compare, and render references to documentation without embedding behavior. + +## Remarks + +Because DocsEntry is a record, it benefits from value equality and deconstruction, making it easy to use as a dictionary key, cache entry, or pass-through object in pipelines that assemble documentation catalogs. The Source field encodes provenance (e.g., local-llm-native or github), allowing consumers to group or display entries by origin without inspecting the doc content. + +## Example + +```csharp +// Example: representing a locally sourced docs entry +var entry = new DocsEntry("docs/api.md", "Docs API", "local-llm-native"); + +// Also supports missing titles +var entryNoTitle = new DocsEntry("docs/intro.md", null, "github"); +``` + +## Notes + +- Title is nullable; downstream rendering should gracefully handle a missing Title. +- Path should uniquely identify the document within its Source; consider normalization if comparing across different sources. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md new file mode 100644 index 0000000..053d5cd --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md @@ -0,0 +1,26 @@ +# AgentPathResolver + +> **File:** `src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs` +> **Kind:** class + +```csharp +public sealed class AgentPathResolver : IAgentPathResolver +``` + + +AgentPathResolver is a sealed implementation of IAgentPathResolver that computes an absolute, sandboxed path from a user-provided relative or absolute input. It derives the root from the requested mode (Host or Project) using injected services and then validates and normalizes the resulting path to ensure it cannot escape the allowed root. This makes it safe to translate user or tool inputs into concrete file-system locations without leaking or traversing outside the designated sandbox. + +## Remarks +AgentPathResolver centralizes root-bound path resolution and guards against directory traversal by verifying the computed path against the configured root. It collaborates with IToolExecutionContext to determine the current project context, IProjectFileService to locate the project directory, and AgentToolsOptions to read host-root configuration. By encapsulating this logic, it keeps path-safety concerns out of caller code and provides a single point of validation for tooling components. + +## Example +```csharp +// Example usage +var resolver = new AgentPathResolver(context, projectFiles, options); +CancellationToken ct = CancellationToken.None; +var resolved = await resolver.ResolveAsync("docs/readme.md", PathRootMode.Project, ct); +``` + +## Notes +- If PathRootMode.Host is requested but HostRoot is not configured, a DomainException is thrown to indicate the host sandbox is disabled. +- If the computed absolute path escapes the allowed root, a DomainException is thrown to prevent access outside the sandbox. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md new file mode 100644 index 0000000..5fa3de9 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md @@ -0,0 +1,29 @@ +# FileInfoTool + +> **File:** `src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs` +> **Kind:** class + +```csharp +public sealed class FileInfoTool : ITool +``` + + +Inspect a single file or directory and produce a compact, read-only report useful for deciding whether to open or edit the target. Use this tool when you want a quick summary (type, size, mtime, MIME/encoding guess, line count) and a small head/tail preview of text files or an entry summary for directories; it resolves paths under the configured root (AgentTools:HostRoot by default) or the conversation's project sandbox when mode="project". + +## Remarks +FileInfoTool is a lightweight, read-only inspector intended as a safe preview step before performing heavier operations (read, edit, replace). It centralizes path resolution and validation (via the injected IAgentPathResolver) and enforces size/preview limits so callers can request brief previews without reading entire files. Errors from argument parsing or path resolution are returned as a simple "Error: ..." string rather than propagated exceptions, so callers should treat non-prefixed results as the successful report body. + +## Example +```csharp +// Given an existing FileInfoTool instance (fileInfoTool): +var argsJson = "{\"path\":\"README.md\",\"mode\":\"project\",\"preview_lines\":6}"; +string report = await fileInfoTool.ExecuteAsync(argsJson, CancellationToken.None); +Console.WriteLine(report); +``` + +## Notes +- The JSON arguments must include a non-empty "path" string; invalid or missing values cause a DomainException that is returned as an "Error: ..." message. +- The "mode" property accepts only "host" or "project"; other values result in an argument error. +- Preview line count is bounded (MaxPreviewLines = 50) and the tool respects a configured default preview lines value when none is provided. Passing 0 for preview_lines disables the preview. +- The tool performs binary sniffing of the first 4096 bytes to decide whether a file is text or binary; expect no text preview for files detected as binary. +- Path resolution is performed against the active root; absolute paths must canonicalize under that root or resolution will fail with an error message. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md new file mode 100644 index 0000000..b8f4db4 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md @@ -0,0 +1,19 @@ +# FindTool + +> **File:** `src/api/Gabriel.Engine/Tools/Files/FindTool.cs` +> **Kind:** class + +```csharp +public sealed class FindTool : ITool +``` + + +FindTool is a glob-based filename search utility that maps glob patterns to Microsoft.Extensions.FileSystemGlobbing.Matcher, enabling idiomatic patterns like "**/*.cs" or "src/**/test_*.py". It skips noisy directories by default (node_modules, bin, obj, .git, dist) to keep results focused and supports limiting results with a hard cap (500) and a default max of 100 results. The tool resolves a configured search root via IAgentPathResolver, validates that root, and returns matching paths relative to that root, up to the specified max_results. + +## Remarks +FindTool encapsulates globbing behind a small tool API and decouples path resolution from pattern matching. It relies on IAgentPathResolver to determine the search root and uses a centralized set of default excludes to keep results fast and relevant. The max-results cap provides predictable response sizes and protects against enumerating huge sets of files. + +## Notes +- Hard cap: the implementation enforces a ceiling of 500 results; although the schema allows up to 500, actual results are bounded to 500. +- Input validation: the JSON argument must include a non-empty string 'pattern'; invalid or missing inputs produce an error message (e.g. "Error: ..."). +- Path validation: if the resolved root is a file or does not exist, the tool returns a clear error indicating the root location. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md new file mode 100644 index 0000000..20aa0da --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md @@ -0,0 +1,32 @@ +# GrepTool + +> **File:** `src/api/Gabriel.Engine/Tools/Files/GrepTool.cs` +> **Kind:** class + +```csharp +public sealed class GrepTool : ITool +``` + + +Search file contents under a resolved root for a regular expression or a literal string and emit ripgrep-style hits (path:line:content). Use this tool when you need a fast, file-walking content search that applies noisy-directory excludes, optional context lines, and either regex or literal matching instead of manually reading and filtering files. + +## Remarks +GrepTool implements the ITool contract to expose a file-content search capability to the tool runner. It resolves the search root via IAgentPathResolver, walks files constrained by a glob, and applies a .NET regex (or an escaped literal) to each scanned line. The implementation intentionally avoids scanning large or binary blobs (PerFileByteCap and GlobalByteCap) and skips common noisy directories by default to keep searches fast and to avoid feeding irrelevant or huge files into the regex engine. + +## Example +```csharp +// `paths` is an IAgentPathResolver provided by the caller environment. +var grep = new GrepTool(paths); +string argsJson = "{" + + "\"pattern\": \"TODO\", " + + "\"path_glob\": \"**/*.cs\", " + + "\"context_lines\": 1" + + "}"; +string result = await grep.ExecuteAsync(argsJson, CancellationToken.None); +// `result` contains ripgrep-style lines like: "src/Foo.cs:42: // TODO: fix this" +``` + +## Notes +- Default noisy directories (node_modules, bin, obj, .git, dist, .vs, .idea, .vscode) are excluded; pass an empty array for exclude_dirs to disable that behavior. +- The tool enforces safety caps: individual files above PerFileByteCap are not scanned and a GlobalByteCap limits total bytes scanned; there is also a hard cap on max_matches (HardCapMatches) even if max_matches is set higher. +- context_lines is limited (0–5) and long matched lines may be truncated for display (LineDisplayCap), so very long single-line matches may be shortened in the output. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md new file mode 100644 index 0000000..0638fbb --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md @@ -0,0 +1,90 @@ +# IAgentPathResolver.cs + +> **Source:** `src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs` + +## Contents + +- [IAgentPathResolver](#iagentpathresolver) +- [ResolvedPath](#resolvedpath) +- [PathRootMode](#pathrootmode) + +--- + +## IAgentPathResolver +> **File:** `src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs` +> **Kind:** interface + +```csharp +public interface IAgentPathResolver +``` + + +IAgentPathResolver abstracts the policy and mechanics for turning a path argument into an absolute file-system path that a tool is allowed to access. It scopes resolution to either the configured host root or the active conversation's project sandbox, and ensures the same hardening — prefix checks, mode gating, and project authorization — is enforced exactly once during resolution. Call ResolveAsync with the path (relative or absolute), the desired root mode, and a CancellationToken; it returns a ResolvedPath wrapped in a Task. + +## Remarks +Centralizes security-sensitive path handling to prevent directory traversal and accidental access to forbidden areas. By providing a single place where host-root versus project-sandbox boundaries are evaluated, this abstraction reduces duplication and improves testability across filesystem-related tools. It also makes future changes to path policy easier to propagate. + +## Notes +- Always propagate the CancellationToken; do not block on the returned Task to avoid deadlocks. +- Do not bypass the resolver by manually composing paths in consuming tools; rely on ResolveAsync so the host/root sandbox boundaries and authorization checks are consistently applied. + +--- + +## ResolvedPath +> **File:** `src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs` +> **Kind:** record + +```csharp +public sealed record ResolvedPath( + string Absolute, + string Display, + string RootAbsolute, + PathRootMode Mode) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Absolute` | `string` | — | +| `Display` | `string` | — | +| `RootAbsolute` | `string` | — | +| `Mode` | `PathRootMode` | — | + + +ResolvedPath represents the result of resolving a path within Gabriel.Engine tooling. It carries both the actual absolute path to operate on (Absolute) and a user-facing display path (Display), together with the root directory anchoring the path (RootAbsolute) and the resolution mode that describes how the root was determined (Mode). + +## Remarks +This abstraction cleanly separates IO concerns from presentation: callers can use Absolute for file operations while presenting Display to users or logs. RootAbsolute provides a stable anchor for re-rooting or walking the path, and Mode communicates the rooting semantics so callers can adapt if paths are rooted under different roots. Together, it coordinates path information across resolvers and consumers without leaking path-walking logic into business code. + +## Notes +- Absolute and Display may differ (e.g., in cases where a user-facing display path is shortened or reformatted); prefer Absolute for any actual filesystem IO. +- RootAbsolute must be consistent with Absolute to avoid confusing representations; use Display to show a more friendly version when appropriate. +- As a record, ResolvedPath is immutable; construct a new instance to reflect any change in path resolution rather than mutating an existing one. + +--- + +## PathRootMode +> **File:** `src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs` +> **Kind:** enum + +```csharp +public enum PathRootMode +{ + Host, + Project, +} +``` + + +PathRootMode is an enumeration used to indicate the root context for path resolution. It allows callers to choose whether relative paths are anchored to the hosting environment root (Host) or to the project root (Project). This separation lets the path resolver adapt to different scenarios (tooling vs. project assets) without changing its internal logic. + +## Remarks +PathRootMode provides a clean abstraction boundary between host-level resources and project-scoped assets. It enables consumers and tests to express intent about where a path should be resolved, reducing coupling to a particular directory structure. By centralizing this choice, the resolver can support multiple environments while keeping the calling code simple. + +## Notes +- Be mindful that Host and Project roots may differ across environments (e.g., IDE-driven sessions vs. CI vs. deployed runners); ensure you select the mode that matches the actual resolution context. +- If the project root is unavailable or not configured, using Project may cause resolution to fail; consider fallbacks or configuration checks. +- Do not mix resolution modes within a single operation; pick one mode per path operation to avoid inconsistent results. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md new file mode 100644 index 0000000..1f22458 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md @@ -0,0 +1,27 @@ +# ListDirTool + +> **File:** `src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs` +> **Kind:** class + +```csharp +public sealed class ListDirTool : ITool +``` + + +List the contents of a directory (a small "ls -la" equivalent) rooted under the configured host root or the active project sandbox. Use this tool when you need a human-friendly, table-style listing of files and directories (columns: type, size, modified, name), optionally walking subdirectories. Use mode="host" (the default) to operate under AgentTools:HostRoot or mode="project" to operate inside the conversation's project sandbox. + +## Remarks +This tool is a safe, bounded directory lister intended for interactive use by agents. It delegates path canonicalization and root scoping to the environment's path resolver so callers cannot escape the configured root; it also enforces an entry cap to avoid extremely large outputs (the hard cap comes from AgentToolsOptions.MaxListEntries and the default count from AgentToolsOptions.DefaultListEntries). Errors are returned as text values prefixed with "Error:" rather than being thrown, making the tool easier to call from automation that expects string results. + +## Example +```csharp +// Request a recursive, project-scoped listing including hidden entries, limited to 100 entries. +string argsJson = "{ \"path\": \"./\", \"mode\": \"project\", \"recursive\": true, \"max_entries\": 100, \"include_hidden\": true }"; +string result = await tool.ExecuteAsync(argsJson, CancellationToken.None); +// 'result' will contain a plain-text table (dirs first, alphabetical) or an "Error: ..." message. +``` + +## Notes +- The output is a human-readable plain-text table (not structured JSON); recursive entries are indented by depth and the listing is truncated when max_entries is reached. +- If the resolved path is a file, the tool returns: "Error: '' is a file, not a directory. Use file_info to inspect files." — it does not attempt to show file contents. +- Supplying a large max_entries is limited by the configured hard cap (AgentToolsOptions.MaxListEntries); invalid or malformed argument JSON results in an "Error: ..." return value rather than an exception. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md new file mode 100644 index 0000000..44f1ecf --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md @@ -0,0 +1,26 @@ +# GetCurrentTimeTool + +> **File:** `src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs` +> **Kind:** class + +```csharp +public class GetCurrentTimeTool : ITool +``` + + +GetCurrentTimeTool is a minimal ITool implementation that returns the current UTC time as an ISO 8601 string. It has no input parameters and returns DateTimeOffset.UtcNow.ToString("o") via ExecuteAsync, making it a convenient, pluggable time source for tool workflows where you want to avoid direct DateTime calls in business logic. + +## Remarks +GetCurrentTimeTool is stateless and safe to reuse across concurrent executions. It supports testability and swap-ability in pipelines: you can replace it with a mock that returns a fixed timestamp during tests, keeping time concerns isolated from core logic. This abstraction helps separate concerns between time reporting and domain behavior within a tool-driven workflow. + +## Example +```csharp +using System.Threading; + +var tool = new GetCurrentTimeTool(); +string timestamp = await tool.ExecuteAsync("{}", CancellationToken.None); +``` + +## Notes +- The returned value represents the moment of invocation and will differ between calls. +- ExecuteAsync completes synchronously (no I/O) by returning a completed Task. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ITool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ITool.cs.md new file mode 100644 index 0000000..0b431a4 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ITool.cs.md @@ -0,0 +1,38 @@ +# ITool + +> **File:** `src/api/Gabriel.Engine/Tools/ITool.cs` +> **Kind:** interface + +```csharp +public interface ITool +``` + + +An ITool is a pluggable unit of functionality that can be discovered and invoked by name with a JSON-encoded argument payload, returning a string result. Implementations expose a Name, a human-friendly Description, a JSON Schema via ParametersJsonSchema to validate inputs, and an asynchronous ExecuteAsync to perform the work. + +## Remarks +ITool provides an abstraction that decouples tool definitions from their host, enabling runtime extension via DI and a registry. It enables the host to enumerate available tools and invoke them uniformly by name, using a JSON-encoded payload defined by ParametersJsonSchema. This uniform contract makes it easy to add or swap capabilities without recompiling the host engine. + +## Notes +- Ensure unique Name values across all tools; Find(string) behavior may become ambiguous if duplicates exist. +- Validate inputs against ParametersJsonSchema and respect the CancellationToken in ExecuteAsync to avoid blocking or long-running operations. + +## Dependencies +- IToolRegistry + +## Dependency APIs +```csharp +public interface IToolRegistry +{ + IReadOnlyList All { get; } + ITool? Find(string name); + IReadOnlyList AsDescriptors(); +} +``` + +## Symbol To Document +- Name: ITool +- Kind: interface +- File: src/api/Gabriel.Engine/Tools/ITool.cs +- Language: csharp +- ID: dbbd4a88-4c71-47b1-918c-1c4a7a5cf83b \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md new file mode 100644 index 0000000..21c2ba2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md @@ -0,0 +1,55 @@ +# IToolExecutionContext.cs + +> **Source:** `src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs` + +## Contents + +- [IToolExecutionContext](#itoolexecutioncontext) +- [ToolExecutionContext](#toolexecutioncontext) + +--- + +## IToolExecutionContext +> **File:** `src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs` +> **Kind:** interface + +```csharp +public interface IToolExecutionContext +``` + + +Represents a per-turn execution context used by tools to discover the current conversation, user, and optional project identifiers. The AgentService populates this scope at the start of each turn and registers it as Scoped so every tool invoked during that turn shares the same values, avoiding the need to thread IDs through every method call. Call Set to initialize ConversationId, UserId, and ProjectId; the properties themselves are nullable, reflecting that a turn may omit some identifiers. + +## Remarks +This abstraction centralizes identity propagation for multi-step tool interactions. It decouples tool implementations from authentication details by providing a single, testable contract for the current context, which improves consistency and auditability across the tool suite. The contract ensures that all collaborators read the same IDs during a turn. + +## Notes +- Nullable properties mean callers must handle nulls gracefully. +- Set should be invoked once per turn before any tool usage to ensure consistent context. +- Avoid caching the interface beyond its scoped lifetime; a new turn creates a fresh context with potentially different IDs. + +--- + +## ToolExecutionContext +> **File:** `src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs` +> **Kind:** class + +```csharp +public sealed class ToolExecutionContext : IToolExecutionContext +``` + + +ToolExecutionContext is a simple, concrete implementation of IToolExecutionContext that stores the identifiers describing the current tool execution: ConversationId, UserId, and an optional ProjectId. It exposes read-only access to these values and provides a single Set method to initialize them in a controlled fashion. Use it when you need a mutable, but well-scoped, container for execution context data that can be populated at the start of a tool run and consumed by downstream components. + +## Remarks +This class acts as a lightweight context carrier that encapsulates cross-cutting identifiers for a tool's lifecycle. By making the properties read-only from outside and funneling updates through Set, it promotes clearer ownership and easier testing. It fits between the orchestration layer and tooling components, ensuring consistent access to ConversationId, UserId, and ProjectId wherever the tool execution context is required. + +## Example +```csharp +// Initialize a new execution context and populate its IDs +var context = new ToolExecutionContext(); +context.Set(Guid.NewGuid(), Guid.NewGuid(), null); +``` + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolRegistry.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolRegistry.cs.md new file mode 100644 index 0000000..45e9aa6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/IToolRegistry.cs.md @@ -0,0 +1,18 @@ +# IToolRegistry + +> **File:** `src/api/Gabriel.Engine/Tools/IToolRegistry.cs` +> **Kind:** interface + +```csharp +public interface IToolRegistry +``` + + +IToolRegistry is a lightweight registry abstraction that enumerates and exposes available tools. Consumers can enumerate All to see every ITool, use Find to retrieve a tool by its name, or call AsDescriptors to translate the registry into ToolDescriptor objects that are sent to the LLM as the tools parameter. This abstraction decouples tool registration from tool consumption, enabling dynamic tool sets and a stable descriptor surface for provider integrations. + +## Remarks +IToolRegistry serves as the discovery boundary between concrete tool implementations and the provider/LLM pipeline. It aggregates ITool instances and exposes a stable, serializable surface (ToolDescriptor) via AsDescriptors, which the provider uses to populate the LLM's tools payload. By offering both a full enumeration (All) and a name-based lookup (Find), it supports dynamic tool composition while keeping downstream consumers insulated from the concrete tool types. + +## Notes +- Find(string) may return null if no tool with the given name exists; callers must handle potential absence. +- AsDescriptors reflects the registry's current state; call it when you need up-to-date tool metadata for the LLM. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md new file mode 100644 index 0000000..b694b6a --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md @@ -0,0 +1,26 @@ +# MemoryListTool + +> **File:** `src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs` +> **Kind:** class + +```csharp +public sealed class MemoryListTool : ITool +``` + + +MemoryListTool is a read-only utility that reveals the memories visible to the current conversation. It queries the memory store for memories associated with the active project (via the execution context) and returns a concise, human-friendly list that includes each memory's type, scope (user or project), name, and a one-line description. Use it to quickly determine whether prior memories are relevant before composing a response, without loading full memory payloads or bloating prompts. + +## Remarks +By separating memory discovery from response generation, MemoryListTool provides a single, consistent surface for understanding what the agent can see. It encapsulates how scope is determined (user vs project) and how entries are formatted, reducing coupling between callers and the memory storage implementation. This helps keep prompts lean while still giving enough context to decide relevance. + +## Example +```csharp +// Most common usage: discover what memories are visible to the current conversation +string result = await memoryListTool.ExecuteAsync("{}", CancellationToken.None); +Console.WriteLine(result); +``` + +## Notes +- The output is a human-readable summary, not a structured payload. +- If the memory store cannot be queried, the method returns an error string (e.g., "Error: could not list memories — ...") instead of throwing. +- Each entry shows memory type, scope (user vs project), name, and description; the header includes the total count of visible memories. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md new file mode 100644 index 0000000..39bf6e8 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md @@ -0,0 +1,19 @@ +# MemoryRemoveTool + +> **File:** `src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs` +> **Kind:** class + +```csharp +public sealed class MemoryRemoveTool : ITool +``` + + +MemoryRemoveTool deletes a saved memory entry by its kebab-case name, scoped to either user-wide memories or the current project. Lookups are performed by a (scope, name) pair, where the kebab-case slug is what the model echoes from memory_list results, and this tool is invoked when a user asks to forget or when a saved memory is stale or incorrect. + +## Remarks +MemoryRemoveTool encapsulates the removal as a dedicated action behind the ITool contract, keeping memory management separate from higher-level dialogue logic. It enforces scope rules by consulting the execution context for a project when scope is 'project', returning a precise error if no project is attached. The returned message clearly indicates whether a memory was removed or none was found, informing subsequent user interaction. + +## Notes +- Project scope requires a project context; without one, the command returns an error. +- The 'name' argument must be non-empty. +- Scope matching is case-insensitive (e.g., 'project' and 'PROJECT' are treated the same). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md new file mode 100644 index 0000000..8922f3b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md @@ -0,0 +1,20 @@ +# MemorySaveTool + +> **File:** `src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs` +> **Kind:** class + +```csharp +public sealed class MemorySaveTool : ITool +``` + + +MemorySaveTool is an agent-callable tool that persists a memory entry for future conversations. It is designed to capture durable information about a user, feedback to guide future behavior, project-specific context, or external references, and to reuse that memory across interactions. Invoke it with a scope of either user or project, a type drawn from {user, feedback, project, reference}, a short kebab-case name, a one-line description, and the full body content. If an entry with the same name already exists in the chosen scope, the tool updates it in place rather than creating a duplicate. The tool delegates persistence to IMemoryService via SaveAsync using a MemoryEntrySpec constructed from the provided arguments, and it validates the scope against the current project context and the allowed set of types. On success, it returns a confirmation message indicating the saved scope, memory type, and name. This abstraction enables durable, retrievable memories that improve consistency and personalization across conversations. + +## Remarks +MemorySaveTool encapsulates the persistence concern for conversational memories. By separating storage from the agent logic, it provides a uniform mechanism to remember user preferences, feedback, project context, or external references, while enforcing scope and type constraints. It fits alongside memory retrieval capabilities and context management, serving as the controlled entry point for adding durable knowledge that should outlive a single turn. The idempotent design (updating an existing entry with the same name) helps prevent accidental duplication when the same memory is saved multiple times. + +## Notes +- scope='project' requires the current conversation to be attached to a project; otherwise an error is returned. +- name, description, and body are required and must be non-empty. +- Saving with the same name updates the existing memory rather than creating a duplicate. +- The resulting confirmation message communicates the scope (user or project), the memory type, and the memory name. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md new file mode 100644 index 0000000..8cb3a81 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md @@ -0,0 +1,27 @@ +# BaseConvertTool + +> **File:** `src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs` +> **Kind:** class + +```csharp +public sealed class BaseConvertTool : ITool +``` + + +Converts a whole (integer) number from one radix to another (bases 2–36). Reach for this tool whenever you need an exact, unbounded base conversion (binary, octal, decimal, hexadecimal or any base 2..36) instead of attempting mental conversion or ad-hoc parsing; it accepts a string value (optionally negative, with '_' separators) and returns a human-readable result string. + +## Remarks +This class implements a pure, deterministic conversion utility (no I/O or external dependencies) intended to avoid the common off-by-one and overflow mistakes that arise when converting manually or with limited-width numeric types. The tool validates input (including a maximum input length), treats digit characters case-insensitively, and formats a concise textual result; errors are surfaced as user-facing messages rather than exceptions escaping the ExecuteAsync call. + +## Example +```csharp +var tool = new BaseConvertTool(); +string args = "{ \"value\": \"FF\", \"from_base\": 16, \"to_base\": 10 }"; +string result = await tool.ExecuteAsync(args, CancellationToken.None); +// result => "FF (base 16) = 255 (base 10)" +``` + +## Notes +- '_' characters in the input are treated as digit separators and ignored; input letters are case-insensitive and a leading '-' denotes a negative number. +- The tool accepts only whole (integer) numbers — it does not handle fractional parts or perform general arithmetic. +- If the input is invalid the tool returns a string beginning with "Error: " containing the validation message (the method catches BaseConvertException and converts it into this user-visible string). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md new file mode 100644 index 0000000..55570ac --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md @@ -0,0 +1,19 @@ +# ListProjectFilesTool + +> **File:** `src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs` +> **Kind:** class + +```csharp +public sealed class ListProjectFilesTool : ITool +``` + + +ListProjectFilesTool lists all files uploaded to the current conversation's project and formats them into a human-friendly manifest. It resolves the active project from the execution context, fetches the files via the project file service, and renders a header with the total count followed by one line per file in the form "- {Name} [id: {Id}, {Size}, {ContentType}, uploaded {UploadedAt:u}]". Sizes are shown using B, KiB, or MiB units, and the upload timestamp uses the universal sortable format. The manifest always includes the file's GUID id so downstream commands (such as read_project_file) can reliably locate a file by its ID. If the conversation isn't attached to a project, it returns the error "Error: this conversation isn't attached to a project yet." If listing fails, it returns a descriptive error like "Error: could not list project files - {ex.Message}". If there are no files, it returns "No files uploaded to this project yet." This tool is intended for discovery: use it to see what's available before selecting a file to read by its id. + +## Remarks +ListProjectFilesTool serves as a stable discovery surface for a project's assets. By delegating to the project-file service and consistently formatting every entry with its GUID, it decouples presentation from storage and ensures downstream operations can locate a file deterministically. The design prioritizes human-readable output while guaranteeing the presence of the essential identifier needed for subsequent read operations. + +## Notes +- The output is a plain string designed for readability; if you need structured data, consider querying the underlying services directly or parsing the string with the guaranteed id field present. +- File names containing unusual characters or newlines may affect the formatting of a single line; consumers should be prepared for potential edge cases in parsing. +- The operation requires an attached project context; ensure the conversation is connected to a project before invoking. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md new file mode 100644 index 0000000..1b9ff7f --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md @@ -0,0 +1,19 @@ +# ReadProjectFileTool + +> **File:** `src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs` +> **Kind:** class + +```csharp +public sealed class ReadProjectFileTool : ITool +``` + + +Read the contents of a file in the current project's workspace. It accepts either the file's GUID (from list_project_files) or its filename (case-insensitive); only text-like files are readable and binary content is refused, with output limited to 20,000 characters by default and up to 80,000 via max_bytes. + +## Remarks +Its role is to fetch a textual representation of a project file for display or analysis within the chat context. By accepting either a GUID or a filename, it hides the details of the underlying file identity and resolves filenames via a project-wide listing when needed. It centralizes error handling for missing files, non-text content, and read failures, ensuring consistent UX across the tool suite. + +## Notes +- Requires the conversation to be attached to a project; otherwise it returns an error. +- If a filename is provided, the tool performs a follow-up lookup against list_project_files to resolve to the corresponding GUID (case-insensitive match). +- The tool refuses to dump binary content; if a file is not text-like, it returns an error instead of raw bytes. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md new file mode 100644 index 0000000..dd59843 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md @@ -0,0 +1,30 @@ +# TextStatsTool + +> **File:** `src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs` +> **Kind:** class + +```csharp +public sealed partial class TextStatsTool : ITool +``` + + +TextStatsTool is a deterministic utility that measures a block of text and reports basic statistics: characters, words, lines, sentences, and paragraphs, plus an estimated reading time and a rough token count. Use it when you need an exact, reproducible sense of how long a piece of text is, rather than relying on eyeballing or ad-hoc estimates. The tool treats Unicode code points as characters, so emoji and CJK characters are counted correctly. It is not designed for hashing or arithmetic operations. + +## Remarks +TextStatsTool encapsulates text-metric logic behind an ITool interface, making it easy to reuse in UI diagnostics, content editors, or tooling that needs quick, offline text measurements. It operates purely on the input text and simple heuristics, with no external I/O, which helps ensure consistent results across call sites. The presence of the JSON-based input schema and the internal validation means callers can rely on predictable error messages when input is invalid or exceeds the allowed size. + +## Example +```csharp +using System.Threading; + +// Example usage +var tool = new TextStatsTool(); +var json = "{\"text\":\"Hello world! This is a sample text to illustrate counting. It has multiple sentences.\"}"; +var result = await tool.ExecuteAsync(json, CancellationToken.None); +Console.WriteLine(result); +``` + +## Notes +- The tool expects a JSON object with a single string property named "text". Missing, non-string, empty, or oversized input yields an error string (e.g., "arguments were not valid JSON." or "'text' cannot be empty."). +- Sentence and paragraph counts are heuristic; if no sentences are detected but words exist, it reports one sentence. If no paragraphs are detected but words exist, it reports one paragraph. Reading time uses a fixed WordsPerMinute baseline (200 WPM) to produce a rough estimate. +- The estimated token count is a naive ceil(chars / 4) approximation and is not intended for cryptographic or exact-token purposes. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md new file mode 100644 index 0000000..1a4d399 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md @@ -0,0 +1,30 @@ +# TextTransformTool + +> **File:** `src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs` +> **Kind:** class + +```csharp +public sealed partial class TextTransformTool : ITool +``` + + +Transform the case or general shape of a string value according to a small set of well-known operations (upper, lower, title, sentence, snake, camel, pascal, kebab, slug, trim). Use this tool when you need to convert an identifier or label between conventions (for example, snake_case -> camelCase, or free-form text -> a URL-friendly slug) rather than performing character-level edits manually. + +## Remarks +This tool is a pure, self-contained transformer: it validates its JSON arguments, enforces a maximum input length (100,000 characters), and performs no I/O. Word boundary detection is aware of spaces, underscores, hyphens and camelCase humps so mixed-format inputs convert cleanly between programmer-case forms and natural-language forms. Most operations preserve Unicode letters; the "slug" operation produces an ASCII-only form. When argument validation fails the implementation throws TextTransformException (ExecuteAsync catches that and returns an error string). + +## Example +```csharp +var tool = new TextTransformTool(); +// async/await style +var resultTask = tool.ExecuteAsync("{\"text\": \"example_text_here\", \"op\": \"camel\"}", CancellationToken.None); +string transformed = await resultTask; // "exampleTextHere" + +// or synchronous blocking (not recommended in UI code): +string transformedSync = tool.ExecuteAsync("{\"text\": \"Hello World!\", \"op\": \"kebab\"}", CancellationToken.None).Result; // "hello-world" +``` + +## Notes +- The tool requires a JSON object with string properties "text" and "op"; missing/invalid JSON or a missing/empty "text" will cause a TextTransformException which ExecuteAsync converts into a returned string prefixed with "Error:". +- Input length is capped at 100,000 characters; longer inputs are rejected with an error. +- "title" uses CultureInfo.InvariantCulture.TextInfo.ToTitleCase (after lowercasing), so casing is invariant-culture based rather than locale-specific. "slug" intentionally drops non-ASCII characters to produce a URL-friendly token. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ToolRegistry.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ToolRegistry.cs.md new file mode 100644 index 0000000..dd77edf --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/ToolRegistry.cs.md @@ -0,0 +1,19 @@ +# ToolRegistry + +> **File:** `src/api/Gabriel.Engine/Tools/ToolRegistry.cs` +> **Kind:** class + +```csharp +public class ToolRegistry : IToolRegistry +``` + + +ToolRegistry centralizes access to all ITool implementations and provides fast, case-insensitive lookup by tool name. Use it when you need to discover available tools or resolve a tool by name without depending on concrete collections. It also exposes a read-only snapshot of tools (All) and a metadata surface (ToolDescriptor) via AsDescriptors for tooling and UIs. + +## Remarks +By encapsulating the lookup and descriptor logic, ToolRegistry decouples consumers from the details of how tools are stored or described. It provides a single, testable surface for discovery that can be swapped or extended without affecting call sites. It assumes unique, case-insensitive tool names; duplicates will fail at construction. Additionally, AsDescriptors iterates over All to produce ToolDescriptor records, ensuring a stable, serializable description surface for downstream consumers. + +## Notes +- Duplicate tool names (case-insensitive) cause construction to fail. Ensure tool names are unique regardless of case. +- Find returns null when no tool matches the provided name. +- All is captured at construction time and exposed as a read-only list; it is not intended for runtime mutation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md new file mode 100644 index 0000000..ba5500c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md @@ -0,0 +1,82 @@ +# IUrlFetcher.cs + +> **Source:** `src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs` + +## Contents + +- [IUrlFetcher](#iurlfetcher) +- [UrlFetchResult](#urlfetchresult) + +--- + +## IUrlFetcher +> **File:** `src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs` +> **Kind:** interface + +```csharp +public interface IUrlFetcher +``` + + +IUrlFetcher defines an asynchronous contract for fetching public HTTP(S) URLs and returning cleaned, plain-text content suitable for agent consumption. Implementations must guard against SSRF by rejecting non-HTTP(S) schemes and internal/private/loopback addresses, cap response size to protect the model's context, and convert HTML into readable plain text by stripping scripts, styles, navigation elements, and all tags. + +## Remarks +By isolating the fetch operation behind IUrlFetcher, the agent layer remains decoupled from transport and content-sanitization concerns. Different implementations (for example, in-process mocks for testing or production-grade fetchers using an HTTP client) can be swapped without altering callers, enabling centralized enforcement of URL validation and content normalization across the codebase. + +## Notes +- Always propagate cancellation and respect timeouts; callers may cancel via the provided CancellationToken. +- Enforce strict content-size caps to prevent huge responses from inflating the model's context, and ensure HTML-to-text conversion strips scripts/styles and tag noise before returning UrlFetchResult. + +--- + +## UrlFetchResult +> **File:** `src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs` +> **Kind:** record + +```csharp +public sealed record UrlFetchResult( + string FinalUrl, + string ContentType, + string Content, + bool Truncated, + int ContentLength) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `FinalUrl` | `string` | — | +| `ContentType` | `string` | — | +| `Content` | `string` | — | +| `Truncated` | `bool` | — | +| `ContentLength` | `int` | — | + + +UrlFetchResult is an immutable data carrier that encapsulates the outcome of fetching a URL. It records the final URL after redirects, the Content-Type reported by the server, and the cleaned textual Content prepared for downstream processing. It also indicates whether the retrieved content was truncated due to a configured cap and exposes ContentLength for the length of the cleaned Content. + +## Remarks + +UrlFetchResult is designed as a simple, transport-friendly representation of a fetch operation. FinalUrl reflects the actual destination after redirects, which is useful for logging, analytics, or follow-up requests. ContentType hints at how to interpret Content (for example, HTML vs. JSON), and Truncated communicates that the original payload exceeded the configured cap, so Content may be incomplete. ContentLength provides the size of Content after cleaning, which aids progress reporting and prevents unnecessary re-processing. Being a record, it supports deconstruction and pattern matching without mutating state. + +## Example + +```csharp +var result = new UrlFetchResult( + FinalUrl: "https://example.org/", + ContentType: "text/html", + Content: "Cleaned text of the page", + Truncated: false, + ContentLength: 128 +); + +var (finalUrl, contentType, content, truncated, length) = result; +``` + +## Notes + +- ContentLength is the length of Content after cleaning, not the original page size. +- Truncated being true means the original page exceeded the retrieval cap, so Content may be incomplete. +- FinalUrl may differ from the initially requested URL due to server redirects; use it for downstream requests and logging. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md new file mode 100644 index 0000000..4b70523 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md @@ -0,0 +1,56 @@ +# IWebSearch.cs + +> **Source:** `src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs` + +## Contents + +- [IWebSearch](#iwebsearch) +- [WebSearchResult](#websearchresult) + +--- + +## IWebSearch +> **File:** `src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs` +> **Kind:** interface + +```csharp +public interface IWebSearch +``` + + +IWebSearch is an abstraction over a web search provider used by the WebSearchTool. Implementations supply SearchAsync to query a backend (Brave, Tavily, SerpAPI, etc.) and return up to a specified number of results as WebSearchResult; the interface enables swapping backends without modifying consumer code, while callers await the async results and may cancel via the CancellationToken. + +## Remarks +WebSearch acts as the boundary between the application logic and concrete search backends. It decouples the consumer from a specific provider, enabling easy swapping (Brave, Tavily, SerpAPI) without touching higher-level code, and centralizes asynchronous search semantics via SearchAsync. By returning a Task<`IReadOnlyList`> and accepting a CancellationToken, the contract favors asynchronous, cancelable queries and safe, read-only consumption of results. Implementations should honor the limit parameter and return at most that many items; consumers may assume the collection is immutable. + +## Notes +- Implementations must observe and respond to the provided CancellationToken, canceling in a timely fashion when requested. +- The returned collection is `IReadOnlyList`; callers should not attempt to mutate it, and may rely on its order and bounded length as an upper bound by the limit parameter. + + +--- + +## WebSearchResult +> **File:** `src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs` +> **Kind:** record + +```csharp +public sealed record WebSearchResult(string Title, string Url, string Snippet) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `Title` | `string` | — | +| `Url` | `string` | — | +| `Snippet` | `string` | — | + + +WebSearchResult is an immutable value object that represents a single web search result, bundling the Title, Url, and Snippet together. Developers reach for this type when a web search API returns results that must be passed around, compared, or serialized as a single unit rather than handling separate strings or ad-hoc collections. + +## Remarks + +WebSearchResult is declared as a sealed record, which provides value-based equality and immutability out of the box. This makes it ideal as a boundary object between the web search service and its consumers, ensuring that two results with the same Title, Url, and Snippet compare as equal and that instances are inherently thread-safe due to immutability. By encapsulating the three related pieces of data in one type, the API gains a clear, discoverable shape for search results and easier serialization to JSON or other formats. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md new file mode 100644 index 0000000..e5a1640 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md @@ -0,0 +1,19 @@ +# WebFetchTool + +> **File:** `src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs` +> **Kind:** class + +```csharp +public sealed class WebFetchTool : ITool +``` + + +Fetch and read the actual content of a public web page by URL, returning cleaned plain text (HTML tags stripped, script/style/nav removed, whitespace normalized) capped at roughly 12,000 characters. Use this after web_search when a result snippet looks relevant and you need the full page text to answer the user; do not use this for Gabriel-specific questions—use docs_read for those. + +## Remarks +WebFetchTool acts as a thin, pluggable adapter in the tool ecosystem, delegating the network fetch to an IUrlFetcher and presenting a consistent plain-text payload. By emitting a small header with the FinalUrl, Content-Type, and Length before the content, it makes it easier to diagnose fetch results and content characteristics without parsing the body. Keeping the fetch logic isolated from downstream reasoning helps ensure predictable behavior and easier testing. + +## Notes +- Error handling is conservative: invalid or missing URL returns a string starting with 'Error:' rather than throwing. +- Output is plain text; HTML and site navigation are removed, but some semantic cues may be lost depending on parsing. +- If the underlying URL fetcher is not network-accessible or blocks requests, the tool will return an error string instead of raising. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md new file mode 100644 index 0000000..1d3fc01 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md @@ -0,0 +1,19 @@ +# WebSearchTool + +> **File:** `src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs` +> **Kind:** class + +```csharp +public sealed class WebSearchTool : ITool +``` + + +WebSearchTool is a sealed class that implements ITool to perform web searches through an injected IWebSearch backend. It exposes a tool named ``web_search`` that accepts a JSON payload with a required ``query`` and an optional ``limit`` (1–10), and returns a human-friendly list of the top results including title, URL, and an optional snippet. Use this tool when you need current, external information or third-party documentation that the model cannot reliably recall, such as recent events or public docs of external tools; do not rely on it for Gabriel-specific architecture or internal APIs. + +## Remarks +WebSearchTool decouples the search provider from the tool orchestration by depending on an IWebSearch backend, enabling swapping implementations or inserting mocks for tests without changing the consumer logic. It centralizes input validation and output formatting: the input is validated and the limit is clamped to the range 1–10 (defaulting to 5), and results are presented in a stable, readable text format. Exceptions from the underlying search are captured and surfaced as descriptive error messages rather than thrown, which helps callers maintain a consistent interaction model. The class being sealed reinforces its role as a concrete, final tool in the system, with a clear responsibility to perform web lookups and present them in a predictable way. + +## Notes +- If the query is missing or not a string, ExecuteAsync returns a clear error message instead of throwing. +- The limit defaults to 5 and is clamped to the inclusive range [1, 10]. +- If the search yields no results, the method returns "No results for: {query}"; if an exception occurs, it returns an error string like "Error: web search failed - {ex.Message}". diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md new file mode 100644 index 0000000..1916b95 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md @@ -0,0 +1,240 @@ +# DependencyInjection.cs + +> **Source:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` + +## Contents + +- [DependencyInjection](#dependencyinjection) +- [AddChatProvider](#addchatprovider) +- [AddDocsLookup](#adddocslookup) +- [AddInfrastructure](#addinfrastructure) +- [AddWebFetch](#addwebfetch) +- [AddWebSearch](#addwebsearch) +- [ConfigureDdgHttpClient](#configureddghttpclient) +- [ConfigureGrokResilience](#configuregrokresilience) + +--- + +## DependencyInjection +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** class + +```csharp +public static class DependencyInjection +``` + + +The DependencyInjection class provides an AddInfrastructure extension on IServiceCollection to register infrastructure services, including disk-backed project file storage configured from Projects:Files (persisting under a {Root}/{ProjectId:N} path). It also outlines internal helpers for wiring a web page fetcher (a single HttpClient with sensible defaults and UA handling) and a web search pipeline that can combine multiple providers (with instrumentation and fallback behavior) as described in the comments. You’d reach for this during application startup when configuring the DI container to enable infrastructure features. + +## Remarks +Architecturally, it serves as a centralized wiring hub for infrastructure concerns, outlining how web-search providers are composed and instrumented so telemetry and per-provider behavior remain consistent regardless of the provider mix. + +--- + +## AddChatProvider +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** method + +```csharp +private static void AddChatProvider(IServiceCollection services, IConfiguration config) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `services` | `IServiceCollection` | — | +| `config` | `IConfiguration` | — | + +**Returns:** `void` + + +Bootstraps chat providers by always registering MockChatProvider as the safe default and conditionally wiring Grok support when a Providers:Grok section with at least one active model exists. It uses the standard Options pipeline to bind and validate Grok configuration, reads secrets via the PROVIDERS__GROK__APIKEY environment key, and only wires Grok when configured, keeping MockChatProvider as the fallback to prevent crashes and to support UI fallback in development. + +## Remarks +This method encapsulates provider wiring and startup validation, separating the concerns of a guaranteed fallback provider from the optional, config-driven Grok integration. It enables clean bootstrapping of real providers without impacting consumers, and it wires authentication and HTTP clients in a provider-safe, testable manner for Grok while preserving a stable default path. + +## Notes +- Grok is registered only if a Providers:Grok section exists and contains at least one active model; otherwise only the MockChatProvider is registered. +- Startup validation runs for Grok options (ApiKey, BaseUrl, TimeoutSeconds, Models) unless the SKIP_DB_INIT environment flag is set to true; a misconfiguration will fail startup. +- Bearer authentication for Grok is applied through a DelegatingHandler (GrokAuthHandler) rather than DefaultRequestHeaders, enabling seamless key rotation without recreating HttpClient instances. +- HttpClient.Timeout is left effectively infinite to let the resilience pipeline govern request timeouts. + +--- + +## AddDocsLookup +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** method + +```csharp +private static void AddDocsLookup(IServiceCollection services, IConfiguration config) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `services` | `IServiceCollection` | — | +| `config` | `IConfiguration` | — | + +**Returns:** `void` + + +Configures the documentation lookup pipeline at startup by wiring together two sources into a single, prioritized provider. LocalDocsLookup serves as the primary on-disk, model-facing documentation source, while GitHubDocsLookup acts as a fallback that retrieves docs from GitHub. The method constructs a CompositeDocsLookup that queries LocalDocsLookup first and GitHubDocsLookup second, ensuring a resilient and deterministic lookup flow. It also wires two named HttpClient instances (one for the GitHub API and one for raw content), applies GitHub-related options from configuration, and registers the composed IDocsLookup as the application's implementation. Invoke this during service configuration to enable the repository of documentation entries the model and tooling rely on. + +## Remarks + +This method centralizes the DI wiring for the documentation subsystem, insulating callers from the details of which sources exist or how they are composed. By using a CompositeDocsLookup with a defined priority (local first, then GitHub), failures in one source do not poison the others, and lookups transparently fall back to alternatives. The HTTP clients and options binding are encapsulated here, providing a single, configurable integration point for sourcing docs from either local assets or GitHub. + +## Notes + +- LocalDocsLookup is registered as a singleton and serves as the primary source; GitHubDocsLookup is registered as a singleton and serves as the fallback. The CompositeDocsLookup enforces the priority order when listing or reading docs. +- The GitHub HTTP client path is configured with a dedicated API client (for ListAsync) and a Raw client (for ReadAsync); the Authorization header is added only if a Token is supplied in GitHubDocsOptions. +- Configuration sections LocalDocsOptions.SectionName and GitHubDocsOptions.SectionName drive the wired options; ensure these sections exist and provide valid values for Owner, Repo, Branch, DocsPath, and related fields. +- The method is private, intended to be invoked as part of the application's startup bootstrap within its defining class; external callers cannot call it directly. + +--- + +## AddInfrastructure +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** method + +```csharp +public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `services` | `IServiceCollection` | — | +| `config` | `IConfiguration` | — | + +**Returns:** `IServiceCollection` + + +Bootstraps the application's infrastructure by wiring the data access layer, repositories, and ancillary providers in a single, cohesive startup path. AddInfrastructure configures SQLite-backed persistence (using the Default connection string if present, or a local gabriel.db file as a fallback), registers AppDbContext and a set of repositories, binds ProjectFilesOptions from configuration, and exposes a disk-backed implementation for project file storage. It also orchestrates the registration of chat, web search, web fetch, and docs lookup providers to keep startup concerns centralized and easily extensible. + +## Remarks +This method centralizes infrastructure bootstrap, ensuring consistent lifetimes and configuration for the DbContext and repositories while isolating persistence concerns from the rest of the app. Binding ProjectFilesOptions and registering DiskProjectFileService make file storage configurable and swap-friendly, promoting testability and easier maintenance. By coordinating provider registrations (Chat, WebSearch, WebFetch, DocsLookup) here, startup remains cohesive and extensible. + +## Notes +- If the configuration does not provide a Default connection string, a local SQLite database named gabriel.db is used as a fallback. +- Disk-backed project file storage relies on ProjectFilesOptions.Root being a valid, writable path; ensure the directory exists and the application has write permissions. +- AddInfrastructure registers DiskProjectFileService as IProjectFileService; replacing this implementation requires adjusting the DI registrations or supplying an alternative implementation. + +--- + +## AddWebFetch +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** method + +```csharp +private static void AddWebFetch(IServiceCollection services) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `services` | `IServiceCollection` | — | + +**Returns:** `void` + + +Configures the dependency injection container to provide a web URL fetcher by registering a named HttpClient with sensible defaults and wiring HttpUrlFetcher as the IUrlFetcher implementation. This centralizes HTTP fetch configuration so external resources can be retrieved through a single, testable service. + +## Remarks +Centralizes HTTP fetch concerns behind IUrlFetcher to decouple callers from HttpClient details. The named HttpClient pattern keeps its configuration scoped to this fetch path, enabling different outbound HTTP strategies elsewhere. Registering HttpUrlFetcher as a singleton means the fetcher should be thread-safe and stateless across calls. + +## Notes +- The User-Agent and Accept-Language headers are hard-coded to improve compatibility with major sites; changing them can affect responses. +- HttpClientFactory manages lifetimes; avoid disposing the named client directly. +- If you need different settings for other external endpoints, define a separate named client rather than reusing HttpUrlFetcher.HttpClientName. + +--- + +## AddWebSearch +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** method + +```csharp +private static void AddWebSearch(IServiceCollection services, IConfiguration config) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `services` | `IServiceCollection` | — | +| `config` | `IConfiguration` | — | + +**Returns:** `void` + + +Configures and wires the web-search providers into the DI container based on Tools:Web:Active, registering each provider's HttpClient and concrete implementation, and wrapping the final set in InstrumentedWebSearch for metrics reporting. If Tools:Web:Active is not provided, it defaults to DuckDuckGo; unknown provider keys are ignored with a warning. + +## Remarks +AddWebSearch centralizes provider wiring and enables runtime extensibility by selecting providers from configuration rather than hard-coding them. The InstrumentedWebSearch decorator applies cross-cutting metrics to all provider calls without requiring each provider to implement its own instrumentation. The approach simplifies testing and extension: to add a new provider, extend the switch with its own registration path and options class, leaving the rest of the wiring intact. + +## Notes +- If a provider key appears that is not recognized, it is dropped with a warning rather than causing a failure. + +--- + +## ConfigureDdgHttpClient +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** method + +```csharp +private static void ConfigureDdgHttpClient(IServiceCollection services) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `services` | `IServiceCollection` | — | + +**Returns:** `void` + + +Configures and registers the named HttpClient for DuckDuckGoWebSearch and centralizes its shared HTTP behavior so the active-providers path and the empty-config fallback share one source of truth. It wires a long-lived HttpClientHandler with a CookieContainer and automatic decompression to preserve session state across requests, while per-request headers such as User-Agent rotation are applied at request time by DuckDuckGoWebSearch rather than being pinned to the client lifetime. + +## Remarks +By centralizing this configuration, any adjustments to timeout, decompression, cookies, or handler lifetime propagate to all users of the named client, keeping behavior consistent across code paths. The CookieContainer ensures session continuity across homepage and subsequent /html/ or /lite/ requests, aligning with how real browsers maintain a session. + +## Notes +- Do not use DefaultRequestHeaders here; per-request headers are set on each HttpRequestMessage to rotate User-Agent and related context. +- SetHandlerLifetime(TimeSpan.FromHours(1)) means the HttpMessageHandler is reused for up to an hour; consider alignment with DNS/Cookie TTL if your deployment changes endpoints frequently. + +--- + +## ConfigureGrokResilience +> **File:** `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +> **Kind:** method + +```csharp +private static void ConfigureGrokResilience(HttpStandardResilienceOptions opts, TimeSpan totalTimeout) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `opts` | `HttpStandardResilienceOptions` | — | +| `totalTimeout` | `TimeSpan` | — | + +**Returns:** `void` + + +Configures the resilience policy for the Grok streaming path used by the SSE chat pipeline. It applies the provided totalTimeout to both TotalRequestTimeout and AttemptTimeout, ensuring the overall request and each individual attempt share a single timeout window and preventing long-running streaming generations from being terminated mid-stream by a too-aggressive default. It also widens CircuitBreaker.SamplingDuration to twice the total timeout to satisfy the framework's requirement that sampling spans at least two attempts. This total timeout is typically sourced from Providers:Grok:TimeoutSeconds and applied consistently across the pipeline. + +## Remarks +Centralizes resilience tuning for streaming workloads; callers adjust a single total timeout and have it reflected across the per-attempt and circuit-breaker settings. The approach ensures retries occur before the response stream starts, protecting against transient DNS/network issues, while the widened circuit-breaker window provides guards during longer streaming segments. + +## Notes +- Mutates the options instance in place; call this during startup before handling requests. +- Keep totalTimeout in sync with Providers:Grok:TimeoutSeconds to avoid inconsistent behavior. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddChatProvider.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddChatProvider.cs.md new file mode 100644 index 0000000..b0a3699 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddChatProvider.cs.md @@ -0,0 +1 @@ +Configures the dependency injection container with chat providers. It always registers a MockChatProvider as a safe, zero-dependency fallback so the application never runs out of a provider, and to expose a UI fallback for development. If the configuration includes Grok options with at least one active model, it wires up the Grok-based provider by binding GrokOptions from configuration, applying a set of validation rules (API key presence, a valid base URL, positive timeout, a single default model, and non-empty model names with positive context window tokens), and then enabling startup-time validation contingent on the SKIP_DB_INIT environment flag. It also registers a per-call HttpClient and an auth delegating handler for Grok, leaving HttpClient.Timeout to the resilience pipeline for centralized timeout control. This method is intended to run during application startup so that chat provider wiring is centralized and easily testable. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddDocsLookup.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddDocsLookup.cs.md new file mode 100644 index 0000000..37f301f --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddDocsLookup.cs.md @@ -0,0 +1,8 @@ +Sets up the docs lookup pipeline by registering a composite that prioritizes a local, LLM-native docs source and falls back to GitHub-backed docs. Use this during application startup to enable runtime doc lookups via IDocsLookup with local-first behavior and safe fallback. + +## Remarks +Encapsulates the wiring in one DI-scoped place so the rest of the app can depend on IDocsLookup without caring about sources. The CompositeDocsLookup enforces priority (local first, then GitHub) and remains resilient if a source is unavailable; a failure in one source does not poison the others. LocalDocsLookup handles disk-stored, developer-authored docs while GitHubDocsLookup provides external, human-prose references, and the two are composed with an ILogger to aid diagnostics. + +## Notes +- Local-first primacy; if you need to bypass, adjust the DI wiring to reorder sources. +- GitHub API usage relies on two named HttpClients (List API and raw content); the token is optional and only sent when provided, enabling access to both public and private docs depending on configuration. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddInfrastructure.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddInfrastructure.cs.md new file mode 100644 index 0000000..3b2fa7a --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddInfrastructure.cs.md @@ -0,0 +1,7 @@ +AddInfrastructure is an extension method for IServiceCollection that wires the infrastructure layer into the application's DI container. It configures Entity Framework Core to use SQLite with a connection string sourced from configuration (falling back to a local Data Source=gabriel.db), registers UoW and repository services with scoped lifetimes, binds and applies ProjectFilesOptions from configuration, and registers a file storage implementation DiskProjectFileService. It then delegates to helper registrations for chat providers, web search, web fetch, and docs lookup, before returning the service collection for fluent configuration. + +## Remarks +By centralizing infrastructure composition, callers gain a single, discoverable entry point for wiring up persistence, repositories, and ancillary services. The extension hides concrete implementations behind interfaces (IUnitOfWork, IConversationRepository, etc.), enabling swap-out for testing or alternate environments without touching business logic. It also ensures configuration-driven concerns—like database provider and file storage options—are consistently applied across the application. + +## Notes +- Be aware that the default connection string uses a local SQLite file (gabriel.db) in the application's working directory; ensure the hosting environment grants write access to that location. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebFetch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebFetch.cs.md new file mode 100644 index 0000000..e796614 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebFetch.cs.md @@ -0,0 +1,8 @@ +Configures the dependency injection container to provide a browser-like HTTP client for the web_fetch tool. It wires a named HttpClient with a 15-second timeout and standard browser headers, and registers HttpUrlFetcher as the IUrlFetcher implementation. Redirects are allowed; the SSRF guard evaluates the final destination through request hooks. Use this during startup whenever you need production-like web-page retrieval via the web_fetch mechanism with consistent HTTP behavior. + +## Remarks +This abstraction localizes all HTTP client configuration for web-page fetching. By naming the client and binding it to IUrlFetcher, the rest of the system can perform URL fetches without duplicating header or timeout logic, ensuring policy (like realistic User-Agent and SSRF safeguards) is applied uniformly. + +## Notes +- Hard-coded headers (User-Agent and Accept-Language) may not suit every target; consider parameterizing or exposing a configuration switch if you encounter compatibility issues. +- If you need alternate fetch behavior (different timeout, headers, or redirects), prefer a separate named HttpClient or an override at call sites rather than modifying this client. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebSearch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebSearch.cs.md new file mode 100644 index 0000000..99f9374 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/AddWebSearch.cs.md @@ -0,0 +1,9 @@ +Adds web search providers to the dependency injection container based on runtime configuration. It reads Tools:Web:Active (defaulting to ddg if not provided) and registers the concrete providers (BraveWebSearch, TavilyWebSearch, DuckDuckGoWebSearch) along with their provider-specific HttpClient wiring, so they can be consumed as implementations of IWebSearch. The registrations are tagged for subsequent wrapping by the InstrumentedWebSearch decorator to enable metrics collection via IMetricRecorder and surface diagnostics through IMetricRepository. If an unknown provider key is encountered, it is ignored with a warning rather than failing startup. + +## Remarks +This method centralizes the wiring of multiple web search providers behind a single DI entry point, allowing the application to compose and switch among providers purely through configuration. It separates provider-specific HTTP configuration from consumer code and enables uniform instrumentation by delegating to the InstrumentedWebSearch decorator used elsewhere in the composition. The approach makes it straightforward to add new providers in the future by extending the switch-case without altering consumer code. + +## Notes +- Unknown provider keys are dropped with a warning instead of causing startup failure. +- Each provider registers its own HttpClient configuration (base address, timeout, and relevant headers) and a singleton implementation to be consumed by higher-level code. +- The actual composition into a single IWebSearch with instrumentation happens outside this method (via the InstrumentedWebSearch decorator and related wiring). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureDdgHttpClient.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureDdgHttpClient.cs.md new file mode 100644 index 0000000..70d8529 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureDdgHttpClient.cs.md @@ -0,0 +1,9 @@ +Registers the named HttpClient for DuckDuckGoWebSearch and centralizes transport configuration used by both the active-providers path and the empty-config fallback. This helper wires a named HttpClient with a long‑lived handler and a CookieContainer so the session established on DuckDuckGo's homepage persists into subsequent searches, reducing first‑request anomalies. It also avoids pinning a single User-Agent here, since per-request headers (including User-Agent rotation and Sec-Fetch-* context) are applied on each HttpRequestMessage by DuckDuckGoWebSearch. Endpoint URLs are absolute for this client, so a shared BaseAddress cannot be used. + +## Remarks +One source of truth for the DuckDuckGo transport configuration, ensuring cookies survive across requests and keeping request headers per-request rather than globally pinned. This keeps the transport behavior consistent across the active-providers path and the empty-config fallback, and it isolates HTTP concerns from higher‑level search logic. The 1‑hour handler lifetime balances session continuity with the need to reflect DNS changes and resource management. + +## Notes +- This HttpClient is tailored to DuckDuckGoWebSearch; reuse for other endpoints is discouraged since they may require different BaseAddress/timeout rules. +- Handler lifetime is one hour; if you adjust this, verify that cookie persistence and session behavior remain correct. +- Do not set DefaultRequestHeaders here; per-request headers are applied by DuckDuckGoWebSearch on each request. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureGrokResilience.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureGrokResilience.cs.md new file mode 100644 index 0000000..a83fdd8 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection/ConfigureGrokResilience.cs.md @@ -0,0 +1,7 @@ +Configures the HTTP resilience options for the Grok streaming path, aligning total and per-attempt timeouts to a single totalTimeout value. It sets TotalRequestTimeout.Timeout and AttemptTimeout.Timeout to totalTimeout, and expands CircuitBreaker.SamplingDuration to twice the total timeout. This prevents long-running SSE-like generation from being cut off mid-stream and ensures the circuit-breaker window covers the entire attempt. + +## Remarks +Acts as a domain-specific adapter that centralizes Grok's resilience policy. By encapsulating the timeout semantics in one place, it reduces duplication and ensures consistency with the Providers:Grok:TimeoutSeconds setting. It complements the streaming nature of the Grok path, where tokens flow after the initial request, making mid-stream timeouts particularly disruptive. + +## Notes +- This method mutates the provided HttpStandardResilienceOptions; call it during application startup to configure a shared policy, not per-request. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection_overview.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection_overview.cs.md new file mode 100644 index 0000000..2c9a84d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/DependencyInjection_overview.cs.md @@ -0,0 +1,11 @@ +Static DependencyInjection acts as the central wiring point for infrastructure services, exposing extension methods like AddInfrastructure, AddWebFetch, and AddWebSearch to configure storage, HTTP access, and search behavior via the DI container. AddInfrastructure wires disk-backed project-file storage with options bound from Projects:Files; AddWebFetch registers a browser-like HttpClient with a sensible timeout and redirects allowed to support the web-fetch tool, while SSRF protection runs against the final destination; AddWebSearch wires a configurable set of web-search providers (driven by Tools:Web:Active), wrapping each provider in instrumentation and composing them in a rank-aware composite, with DuckDuckGo as a safe fallback when configuration yields no known providers. + +## Remarks + +Consolidates infrastructure concerns behind a small, testable surface, enabling easy swapping of providers, adjustments to metrics, or feature toggling in tests or different environments. The web-search wiring is configuration-driven: a comma-separated list of provider keys selects providers, per-provider instrumentation is applied via InstrumentedWebSearch, and diagnostics are exposed through the /diagnostics/web-search endpoint. By centralizing this logic, consumer code remains agnostic to provider identities and focuses on higher-level workflows. + +## Notes + +- The web-search wiring falls back to DuckDuckGo when no known keys are found in configuration; verify Tools:Web:Active to avoid unexpected defaults. +- AddWebFetch is private; to alter fetch behavior, modify this wiring class rather than adding external HttpClient usage. +- AddInfrastructure relies on configuration bindings from Projects:Files to determine storage behavior; ensure the config section exists to avoid misconfigured storage paths. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md new file mode 100644 index 0000000..3bb125c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md @@ -0,0 +1,19 @@ +# ApplicationUser + +> **File:** `src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs` +> **Kind:** class + +```csharp +public class ApplicationUser : IdentityUser +``` + + +Represents an application user stored with a Guid key and wired into the infrastructure's Identity persistence. By deriving from `IdentityUser`, it keeps identity concerns in Infrastructure while the Core projects interact with users via their Guid. It adds per-user model preferences via PreferredProvider and PreferredModel to remember a user's chosen chat provider and model; both properties are nullable so the agent can fall back to configuration defaults when unset. When unset, the agent falls back to whichever Providers:*:Models entry has IsActive=true in config. + +## Remarks +This type exists in Infrastructure because Identity is a persistence concern; the Core only uses user identifiers (Guid). It centralizes per-user chat preferences: PreferredProvider maps to IChatProvider.Name and PreferredModel maps to the provider's wire-level model identifier, stored as plain strings to remain decoupled from concrete providers. The model catalog handles stale references gracefully by falling back to the default model when needed, guided by the configuration's active entries. + +## Notes +- Nullable properties mean unset values trigger the global defaults for provider and model. +- Stored as plain strings can require vigilance to keep them in sync with the model catalog and available providers; rely on the catalog to resolve or fall back as configured. +- This class encapsulates persistence-related state related to identity, separating concerns from the Core domain logic that operates on user identities. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md new file mode 100644 index 0000000..bb5b0ae --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md @@ -0,0 +1,19 @@ +# GabrielIdentityExtensions + +> **File:** `src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs` +> **Kind:** class + +```csharp +public static class GabrielIdentityExtensions +``` + + +GabrielIdentityExtensions serves as a centralized bootstrapper for Gabriel's authentication stack. Call AddIdentityAndAuth during startup to wire IdentityCore with the EF user store, register the JWT bearer authentication scheme, and expose token services, ensuring a single, consistent path for identity and token handling across the application. + +## Remarks +This extension encapsulates the entire auth bootstrap behind a single entry point, reducing drift between identity, token minting/validation, and token storage. It binds JwtOptions from configuration and conditionally validates them at startup (subject to the SKIP_DB_INIT flag), while wiring IJwtTokenService, IRefreshTokenStore, and per-user preferences support. The JWT validation is explicit about issuer, audience, signing key, and lifetime, and the system uses a single JwtBearer scheme with a header-based flow and a HttpOnly access-cookie fallback for login scenarios. + +## Notes +- If SKIP_DB_INIT is not set to "true", JwtOptions must be configured and validated at startup; specifically, SigningKey must be provided (and it must be at least 32 characters). This prevents runtime misconfiguration when the signing key is missing. +- The authentication pipeline is wired as a single JwtBearer scheme; the code path also notes a cookie-based writer on login, but the framework authentication remains JwtBearer, so mixing in other schemes requires careful coordination. +- The SigningKey, if provided, is converted to a SymmetricSecurityKey for token validation; an empty SigningKey results in a null IssuerSigningKey which will fail validation at runtime. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md new file mode 100644 index 0000000..be932cc --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md @@ -0,0 +1,61 @@ +# JwtTokenService + +> **File:** `src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs` +> **Kind:** class + +*Figure: How JwtTokenService works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB + +IJwtTokenService["IJwtTokenService: caller of token operations"] -->|"Call IssueAsync(userId,email) or RefreshAsync(refreshToken)"| JwtTokenService["JwtTokenService: IssueAsync / RefreshAsync"] + +%% IssueAsync path +JwtTokenService -->|"EnsureConfigured & MintAccessJwt(userId, email)"| JwtOptions["JwtOptions: configuration used to mint access JWT"] +JwtTokenService -->|"PersistNewRefreshTokenAsync(userId) -> create plaintext & hash"| IRefreshTokenStore["IRefreshTokenStore: store/find refresh token hashes"] +IRefreshTokenStore -->|"Returns RefreshToken entity (hash stored)"| RefreshToken["RefreshToken: persisted refresh token entity (ExpiresAt, hash)"] +JwtTokenService -->|"Commit refresh row"| IUnitOfWork["IUnitOfWork: SaveChangesAsync to persist refresh token"] +IUnitOfWork -->|"SaveChanges completed"| JwtTokenService +JwtTokenService -->|"Return TokenPair(accessJwt, expires, refreshPlaintext, refreshExpires)"| TokenPair["TokenPair: returned access + refresh info"] + +%% RefreshAsync path +JwtTokenService -->|"HashToken(refreshToken) & FindByHashAsync(hash)"| IRefreshTokenStore +IRefreshTokenStore -->|"Returns existing RefreshToken or null"| RefreshToken +RefreshToken -->|"If null -> throw UnauthorizedAccessException (invalid)"| JwtTokenService +RefreshToken -->|"If found -> detect reuse/rotate (respect RotationGracePeriod)"| JwtTokenService +JwtTokenService -->|"Persist rotation / revoke old row"| IRefreshTokenStore +JwtTokenService -->|"Commit rotation"| IUnitOfWork +IUnitOfWork -->|"Rotation saved"| JwtTokenService +JwtTokenService -->|"Mint new access JWT and return TokenPair"| TokenPair + +%% Reference to user identity involved in minting +JwtTokenService -->|"Uses user identity (userId, email) when minting"| ApplicationUser["ApplicationUser: user identity used to mint JWTs"] +``` + +```csharp +public class JwtTokenService : IJwtTokenService +``` + + +Issues and rotates JSON Web Tokens (access + refresh) for an authenticated user and enforces safe refresh-token lifecycle policies. Use this service from authentication endpoints to mint a fresh access token and a persistent refresh token (IssueAsync), to rotate and validate refresh tokens on client refresh (RefreshAsync), and to revoke refresh tokens when needed. It centralizes signing, hashing/persisting refresh tokens, and detection/handling of suspicious token reuse. + +## Remarks +Centralizes the JWT and refresh-token workflow so callers don't need to mix signing, storage, and rotation logic. The service delegates storage to IRefreshTokenStore, user lookups to `UserManager`, and transactional commits to IUnitOfWork; this keeps token lifecycle rules (hashing, rotation, grace window for reuse detection, and revocation) in one place while letting the backing store and identity implementations vary. + +## Example +```csharp +// Typical usage in an auth controller or service +// (dependencies are usually injected via DI) +TokenPair pair = await jwtService.IssueAsync(userId, email, cancellationToken); +// send pair.AccessToken to client and persist pair.RefreshToken in an HttpOnly cookie + +// Later, when the client sends the refresh token back: +TokenPair rotated = await jwtService.RefreshAsync(refreshTokenFromCookie, cancellationToken); +// return rotated.AccessToken and replace the refresh cookie with rotated.RefreshToken +``` + +## Notes +- IssueAsync saves a newly-created refresh-token row via the unit-of-work; without that SaveChanges call the refresh token can remain only in EF's change tracker and never persist (the source comments call this out as a real pitfall). +- A rotation-grace period is applied when detecting reuse of recently-rotated tokens to tolerate benign races (multi-tab, in-flight requests, long SSE streams). This reduces false theft detections at the cost of a short grace window (configured in the implementation as a few minutes). +- RefreshAsync throws UnauthorizedAccessException for missing/invalid/blank refresh tokens; callers should map that to an appropriate HTTP 401/403 response and avoid exposing sensitive internals. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs.md new file mode 100644 index 0000000..80d6194 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs.md @@ -0,0 +1,18 @@ +# UserPreferencesService + +> **File:** `src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs` +> **Kind:** class + +```csharp +internal sealed class UserPreferencesService : IUserPreferences +``` + + +UserPreferencesService is a lightweight IUserPreferences implementation that persists a small set of per-user preferences directly on ApplicationUser via UserManager, avoiding a separate table for cheap, in-place updates. GetAsync returns a UserPreferences with null values when there is no authenticated user, while SetPreferredModelAsync requires authentication and updates the user's PreferredProvider and PreferredModel (normalizing empty strings to null to represent 'unset'), throwing UnauthorizedAccessException if there is no user and InvalidOperationException for not-found users or update failures. + +## Remarks +It centers per-user preferences within the identity model, drawing on ICurrentUser for the active user and `UserManager` to read and persist fields. By routing through ApplicationUser, it relies on existing identity lifecycle, which ensures consistent validations and hooks. The normalization of empty inputs to null makes the catalog's 'unset' path behave identically regardless of client input. + +## Notes +- SetPreferredModelAsync requires an authenticated user; calling it without authentication will throw UnauthorizedAccessException. +- GetAsync safely returns default preferences when there is no authenticated user, avoiding exceptions in non-UI or background contexts. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md new file mode 100644 index 0000000..72f148a --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md @@ -0,0 +1,11 @@ +# AppDbContext + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs` +> **Kind:** class + +```csharp +public class AppDbContext : IdentityDbContext, Guid> +``` + + +AppDbContext is the Entity Framework Core DbContext that serves as the data access hub for Gabriel's persistence layer. It inherits from IdentityDbContext`, Guid>, thereby wiring in ASP.NET Identity tables alongside the application’s domain entities. It exposes DbSet properties for Conversations, Messages, RefreshTokens, Projects, ProjectFiles, MemoryEntries, and MetricEntries, which EF Core uses to map and query these aggregates. In OnModelCreating, it first delegates identity-related configuration to the base implementation, then applies all entity-type configurations discovered in this assembly via ApplyConfigurationsFromAssembly, ensuring consistent mappings across the model. It also overrides ConfigureConventions to persist DateTimeOffset properties using a DateTimeOffsetToBinaryConverter, addressing SQLite's lack of native DateTimeOffset support and ensuring correct ordering and comparisons in SQL. This context is intended to be registered in the DI container and used by repositories and services to read and write application data. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md new file mode 100644 index 0000000..5792642 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md @@ -0,0 +1,19 @@ +# ConversationConfiguration + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs` +> **Kind:** class + +```csharp +public class ConversationConfiguration : IEntityTypeConfiguration +``` + + +Configures how EF Core persists the Conversation aggregate. It maps to the Conversations table, defines the primary key and property constraints, sets up indices for the hot dashboard queries, and wires the one-to-many relationship to Message entities. It also maps the Conversations.Messages collection to a private backing field to ensure mutations flow through the aggregate and supports lazy or backfilled fields such as ProjectId and StateJson. + +## Remarks +This configuration centralizes persistence concerns in one place, keeping EF Core details out of the domain entity. It encodes practical decisions for schema shape, query performance, and aggregate boundaries—such as nullable ProjectId to survive migrations with lazy backfill, and a field-backed Messages collection to enforce mutation through the aggregate. By configuring the state (StateJson) and optional attributes (PatternOverride, PaletteOverride) thoughtfully, it supports forward-compatibility and lazy initialization patterns without disturbing the in-domain logic. + +## Notes +- Nullable ProjectId is intentional to preserve existing conversations during migrations; backfilled lazily when the user first creates or visits a project. +- The rolling-summary columns (Summary and SummarizedThroughMessageId) are nullable until the conversation crosses the compact threshold, so consumers must handle absence of these values. +- The Messages navigation uses a private backing field (_messages) with Field access mode to ensure all mutations go through the aggregate, preserving invariants and encapsulation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs.md new file mode 100644 index 0000000..06d2519 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs.md @@ -0,0 +1,18 @@ +# MemoryEntryConfiguration + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs` +> **Kind:** class + +```csharp +public class MemoryEntryConfiguration : IEntityTypeConfiguration +``` + + +Configures the MemoryEntry entity for EF Core. This fluent configuration maps MemoryEntry to the MemoryEntries table, declares Id as the primary key, and enforces required properties and max lengths, plus two indices for common queries: a hot lookup path index on (UserId, ProjectId, UpdatedAt) and a unique index on (UserId, ProjectId, Name) to enforce slug uniqueness per user/project. + +## Remarks +By isolating persistence mapping here, MemoryEntry remains a plain data model while the database schema is defined in this single place. The two indices reflect typical access patterns: fast listing of memories within a user scope by UpdatedAt, and strict uniqueness of slugs within (UserId, ProjectId). This separation also makes it easier to adapt the data layer without altering the entity. + +## Notes +- In SQLite, the unique index on (UserId, ProjectId, Name) treats NULL ProjectId values as distinct; this enables a slug to be reused across user-scope and project-scope memories but also means identical (UserId, Name) pairs with NULL ProjectId won't violate the constraint. +- Ensure this configuration is registered in the DbContext to enforce the mappings. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs.md new file mode 100644 index 0000000..97c7642 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs.md @@ -0,0 +1,41 @@ +# MessageConfiguration + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs` +> **Kind:** class + +```csharp +public class MessageConfiguration : IEntityTypeConfiguration +``` + + +MessageConfiguration configures how EF Core maps the Message entity to the database schema. It centralizes all Fluent API rules for Message, including the table name, primary key, required fields, a role-to-integer conversion, optional fields for different message roles, and the indexes that support efficient per-conversation retrieval and variant-history lookups. + +## Remarks +This abstraction encapsulates persistence concerns for Message, allowing the domain model to remain clean while all EF Core specifics live here and are applied during model creation. The defined indexes reflect common query patterns: (ConversationId, CreatedAt) enables fast retrieval of messages in a conversation by time, while (ConversationId, VariantGroupId) supports provider-history filtering across multiple variants within the same turn. The VariantGroupId and IsActiveVariant properties enable multi-variant turns, where the same conversational turn can exist in several variants; in singleton scenarios VariantGroupId equals the Id. + +## Dependencies +- IEntityTypeConfiguration +- Message + +## Dependency APIs +- class [`Message`](../../../Gabriel.Core/Entities/Message.cs.md) (`src/api/Gabriel.Core/Entities/Message.cs`) + - property `Guid Id` + - property `Guid ConversationId` + - property `MessageRole Role` + - property `string? Content` + - property `string? ToolCallId` + - property `string? ToolCallsJson` + - property `string? ReasoningContent` + - property `DateTimeOffset CreatedAt` + - property `Guid VariantGroupId` + - property `bool IsActiveVariant` + - `Message()` + - `Message Create(Guid conversationId, MessageRole role, string? content, string? toolCallId, string? toolCallsJson, Guid? variantGroupId)` + - …and 3 more member(s) not shown + +## Symbol To Document +- Name: `MessageConfiguration` +- Kind: class +- File: `src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs` +- Language: csharp +- ID: 74f4e0f8-4ecb-430b-b649-4de743cd3fb9 \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs.md new file mode 100644 index 0000000..d88fef6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs.md @@ -0,0 +1,19 @@ +# MetricEntryConfiguration + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs` +> **Kind:** class + +```csharp +public class MetricEntryConfiguration : IEntityTypeConfiguration +``` + + +MetricEntryConfiguration configures how MetricEntry persists to the database. It maps to the MetricEntries table, enforces property constraints (Id as key, System length cap, Metric as required JSON payload, and CreatedAt as required), and defines indexes to support the application's common read patterns. + +## Remarks +By centralizing the EF Core mapping here, persistence concerns are kept separate from the domain entity, making it straightforward to adjust storage details (such as length limits or indexing) without touching the domain model. The composite index on System and CreatedAt enables fast retrieval of recent entries for a specific system and for systems sharing a prefix, while a separate CreatedAt index supports cleanup queries that delete older data. + +## Notes +- The 128-character limit on System bounds index size and enforces a sane naming convention. +- Metric is stored as JSON text (TEXT in SQLite); while some databases offer a native JSON type, this configuration remains valid across stores, and JSON-related queries may be performed when supported. +- The index on (System, CreatedAt) is designed to support both per-system recent-entry queries and broader recent-entry scans; if you alter storage or query patterns, revisit the indexing strategy accordingly. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs.md new file mode 100644 index 0000000..7c94267 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs.md @@ -0,0 +1,18 @@ +# ProjectConfiguration + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs` +> **Kind:** class + +```csharp +public class ProjectConfiguration : IEntityTypeConfiguration +``` + + +Configures the EF Core mapping for the Project aggregate. It specifies the target database table, the primary key, required properties and maximum lengths, and the two indices used to support common queries. It also wires the one-to-many relationship to ProjectFile with cascade delete and configures the Files navigation to be field-backed for encapsulation. + +## Remarks +EF Core model configuration like this centralizes persistence concerns for the Project aggregate, keeping domain logic decoupled from database specifics. It defines a compact index strategy (a per-user-by-updated ordering index and a filtered index ensuring at most one Default per user) to support efficient queries while preserving invariants. The use of a private backing field (_files) for the Files collection demonstrates a deliberate boundary between the domain model and EF Core's change-tracking, enabling controlled mutation of the aggregate's children. + +## Notes +- Filtered index (HasFilter) relies on database provider support for partial indexes (SQLite supports this). Other providers may ignore the filter or require different syntax. +- Field-backed navigation means callers mutate the Files collection through the aggregate’s domain methods (or through the aggregate’s intended mutation pathways) to maintain invariants; direct field access is managed by EF Core. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs.md new file mode 100644 index 0000000..bb981df --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs.md @@ -0,0 +1,18 @@ +# ProjectFileConfiguration + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs` +> **Kind:** class + +```csharp +public class ProjectFileConfiguration : IEntityTypeConfiguration +``` + + +Configures how the ProjectFile entity is mapped to the database in EF Core: it specifies the table name, primary key, property constraints, and critical indexes. Use this configuration when setting up the persistence model to ensure the schema enforces invariants like per-project RelativePath uniqueness and efficient retrieval of a project's files by upload time. + +## Remarks +By centralizing the persistence mapping, this configuration keeps storage concerns decoupled from the domain model while enforcing schema-level invariants. The two indexes express intended query patterns: a composite index on (ProjectId, UploadedAt) for listing a project's files in upload order, and a uniqueness index on (ProjectId, RelativePath) to prevent duplicate paths within a single project (complementing service-layer validation). + +## Notes +- The configuration marks the following fields as required and constrains their lengths: Name (max 256), RelativePath (max 512), ContentType (max 128), SizeBytes, and UploadedAt. This aligns the database schema with the domain expectations for a ProjectFile. +- The per-project RelativePath uniqueness constraint ensures cross-renaming scenarios are handled deterministically by the database in tandem with the service-layer checks described in the code comments. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs.md new file mode 100644 index 0000000..8db80a2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs.md @@ -0,0 +1,18 @@ +# RefreshTokenConfiguration + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs` +> **Kind:** class + +```csharp +public class RefreshTokenConfiguration : IEntityTypeConfiguration +``` + + +Configures how the RefreshToken entity is persisted by EF Core: maps it to the RefreshTokens table, enforces a primary key and required fields, and applies indices for lookup and user-scoped queries. This configuration is consulted when the model is built, ensuring data integrity and efficient access for token validation, revocation, and replacement workflows. + +## Remarks +By centralizing schema details in one place, this configuration ensures the RefreshToken table enforces a stable contract: required fields, a fixed token hash length, and predictable keys. The unique index on TokenHash guards against duplicate tokens and makes token validation efficient, while the UserId index supports per-user lookups for revocation and hygiene tasks. + +## Notes +- The unique index on TokenHash means two tokens with the same hash cannot be stored; ensure token hashing and lifetime logic minimize collisions. +- CreatedAt and ExpiresAt are required; ensure tokens are assigned these values at creation to avoid constraint violations. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md new file mode 100644 index 0000000..7702b28 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md @@ -0,0 +1,19 @@ +# ConversationRepository + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs` +> **Kind:** class + +```csharp +public class ConversationRepository : IConversationRepository +``` + + +A thin EF Core repository that implements IConversationRepository and encapsulates common data access patterns for Conversation and Message entities tied to a specific user. Use this class when you need user-scoped queries (by userId), to include a conversation's messages, or to add/update/remove conversations and messages through the application's AppDbContext. + +## Remarks +This repository centralizes query logic and mutation helpers so callers don't duplicate filtering or include logic across the codebase. It intentionally delegates persistence (SaveChanges/SaveChangesAsync) to the caller: methods add/update/remove entities on the injected AppDbContext but do not commit changes. The class assumes a per-request or otherwise appropriately scoped DbContext (DbContext is not thread-safe). + +## Notes +- These methods modify the injected AppDbContext but do not call SaveChanges/SaveChangesAsync; callers must persist changes explicitly. +- AppDbContext (and therefore this repository) is not thread-safe — do not reuse the same instance concurrently on multiple threads. +- GetByIdWithMessagesAsync uses an Include with an OrderBy for messages; depending on EF Core version and materialization, consumers who rely on a specific in-memory ordering should apply an explicit OrderBy when enumerating the Messages collection. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md new file mode 100644 index 0000000..9e8d17d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md @@ -0,0 +1,19 @@ +# MemoryRepository + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs` +> **Kind:** class + +```csharp +public class MemoryRepository : IMemoryRepository +``` + + +A lightweight EF Core-backed repository that implements IMemoryRepository for querying and mutating MemoryEntry entities scoped to a user (and optionally to a project). Use this when you want database-backed access to memories with the repository handling common query shapes (list by project, agent view that mixes user/global and project-scoped entries) instead of writing LINQ against AppDbContext directly. + +## Remarks +MemoryRepository is a thin persistence adapter over AppDbContext.MemoryEntries. It intentionally performs queries as IQueryable -> ToListAsync so callers get materialized, ordered collections and the class does not call SaveChanges/SaveChangesAsync; callers are responsible for committing changes after AddAsync/Update/Remove. ListForAgentAsync encodes the agent-facing view: it returns the user's global (project-null) entries plus, when a project id is supplied, that project's entries in a single SQL query and orders results so user-scope (global) entries appear first, then project-scoped entries, with secondary ordering by Type then Name. + +## Notes +- AddAsync/Update/Remove only change the DbContext state; persistence requires an explicit save on the DbContext (e.g. SaveChangesAsync) outside this repository. +- ListAsync uses equality on ProjectId (m.ProjectId == projectId). To retrieve only global (user-scoped) entries call with projectId == null; to retrieve only one project's entries pass that project's id. +- FindByNameAsync matches Name and ProjectId exactly within the given UserId — it will not search across both global and project scopes. ListForAgentAsync is the method to use when you need the combined agent view (global + optional project) in one round-trip. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs.md new file mode 100644 index 0000000..f293ad0 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs.md @@ -0,0 +1,37 @@ +# MetricRepository + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs` +> **Kind:** class + +```csharp +public sealed class MetricRepository : IMetricRepository +``` + + +MetricRepository is the EF Core-based persistence implementation of IMetricRepository. It writes MetricEntry records directly via AddAsync followed by SaveChangesAsync, treating metrics as standalone events that should not participate in a surrounding unit of work. This keeps metric emission decoupled from business transactions and prevents metric failures from cascading to the main operation. + +## Remarks + +This class acts as the persistence adapter between the domain concept of a metric and its database representation. Reads are optimized for concurrency and performance by using AsNoTracking, and queries rely on standard EF Core operators (equality filters, ordering by CreatedAt, and limitations via Take). The prefix-based query uses StartsWith, which EF translates to SQL LIKE; on SQLite, a literal prefix enables index usage for improved performance. For cleanup, DeleteOlderThanAsync uses a bulk delete via ExecuteDeleteAsync, avoiding entity materialization and maximizing efficiency for maintenance tasks. + +## Example + +```csharp +// Example usage of MetricRepository +var repo = new MetricRepository(db); + +var entry = new MetricEntry().Create("PaymentsService", "{\"latencyMs\":123}"); +await repo.AddAsync(entry, ct); + +var recent = await repo.RecentAsync("PaymentsService", 10, ct); + +var prefixRecent = await repo.RecentByPrefixAsync("Payments", 5, ct); + +var deletedCount = await repo.DeleteOlderThanAsync(DateTimeOffset.UtcNow.AddDays(-7), ct); +``` + +## Notes + +- Metrics are emitted as standalone events; a failure to persist should not roll back the primary business operation. Handle exceptions accordingly if you rely on metrics in critical workflows. +- Be aware that RecentByPrefixAsync relies on EF Core's translation of StartsWith to SQL LIKE. On SQLite, using a literal prefix can leverage an index for better performance. +- DeleteOlderThanAsync performs a bulk deletion without loading entities into memory, which is efficient for log cleanup but should be used with care if you rely on cascading side effects. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs.md new file mode 100644 index 0000000..509ad57 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs.md @@ -0,0 +1,40 @@ +# ProjectRepository + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs` +> **Kind:** class + +```csharp +public class ProjectRepository : IProjectRepository +``` + + +Provides CRUD-style access to Project entities backed by an AppDbContext and implements IProjectRepository. Use this repository when you need to query, add, update or remove projects scoped to an owner user, or to bulk-assign orphaned conversations to a project without loading rows into the change tracker. + +## Remarks +This class is a thin EF Core-backed repository that centralizes common Project operations and enforces scoping by OwnerUserId on all queries. It intentionally does not call SaveChanges/SaveChangesAsync: persistence is expected to be handled by the surrounding unit-of-work or higher-level service. The AssignOrphanConversationsAsync method uses EF Core's ExecuteUpdateAsync to perform a server-side, non-tracked bulk update (returns number of rows affected), which avoids loading Conversation entities into the change tracker for performance. + +## Example +```csharp +// list projects for a user +IProjectRepository repo = new ProjectRepository(dbContext); +var projects = await repo.ListAsync(ownerUserId, cancellationToken); + +// add a new project (note: caller must persist changes via the DbContext / unit-of-work) +var newProject = new Project +{ + Id = Guid.NewGuid(), + OwnerUserId = ownerUserId, + Name = "New Project", + CreatedAt = DateTimeOffset.UtcNow, + UpdatedAt = DateTimeOffset.UtcNow +}; +await repo.AddAsync(newProject, cancellationToken); + +// bulk assign orphan conversations to a project (returns number of rows updated) +int updated = await repo.AssignOrphanConversationsAsync(ownerUserId, newProject.Id, cancellationToken); +``` + +## Notes +- None of the modifying methods (AddAsync, Update, Remove) call SaveChanges; callers must save changes on the AppDbContext or via a unit-of-work. +- GetByIdWithFiles includes the project's Files collection and requests them ordered descending by UploadedAt — the order is applied in the Include expression. +- AssignOrphanConversationsAsync performs a server-side bulk update and returns the number of affected rows; because it uses ExecuteUpdateAsync the updated Conversation entities are not tracked by the current DbContext change tracker. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs.md new file mode 100644 index 0000000..0ad933e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs.md @@ -0,0 +1,20 @@ +# RefreshTokenStore + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs` +> **Kind:** class + +```csharp +public class RefreshTokenStore : IRefreshTokenStore +``` + + +RefreshTokenStore is a concrete implementation of IRefreshTokenStore that persists RefreshToken entities via AppDbContext. It provides three asynchronous operations: FindByHashAsync to locate a token by its hash, AddAsync to persist a new token, and RevokeAllForUserAsync to revoke all active tokens for a particular user. The revoke operation performs a bulk update to mark RevokedAt for matching tokens in a single database call, avoiding loading rows into the change tracker. + +## Remarks +This class serves as a focused persistence adapter over EF Core, isolating the storage concerns of refresh tokens behind the IRefreshTokenStore contract. By using a bulk update via ExecuteUpdateAsync for revocation, it minimizes memory usage and round-trips while ensuring tokens are consistently marked as revoked. Callers can swap this store for another persistence strategy without changing domain code. + +## Notes +- AddAsync queues the new token for insertion; the actual database insert occurs when the context is saved (SaveChanges/SaveChangesAsync). +- RevokeAllForUserAsync uses a single UPDATE statement via ExecuteUpdateAsync, avoiding per-token reads. +- It revokes only tokens where RevokedAt is null, making the operation idempotent for already revoked tokens. +- Cancellation tokens are threaded through all EF Core calls to support cooperative cancellation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md new file mode 100644 index 0000000..73197ee --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md @@ -0,0 +1,18 @@ +# UnitOfWork + +> **File:** `src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs` +> **Kind:** class + +```csharp +public class UnitOfWork : IUnitOfWork +``` + + +UnitOfWork is a concrete implementation of IUnitOfWork that encapsulates an AppDbContext and exposes a single asynchronous operation to persist changes. It forwards the provided CancellationToken to the underlying context's SaveChangesAsync, returning the number of state entries written to the database. This class provides a persistence contract that higher-level services can depend on, without needing to reference EF Core details directly. + +## Remarks +This symbol acts as a minimal façade over the data layer, decoupling domain services from EF Core specifics and enabling easier testing and substitution of the persistence mechanism. By sharing a single context, multiple repositories can participate in a single SaveChangesAsync call, aligning with the Unit of Work pattern where a coordinated commit is desired. + +## Notes +- This implementation does not introduce its own transaction scope; SaveChangesAsync commits changes tracked by the AppDbContext in one batch. If cross-context or multi-transaction behavior is required, external transaction management is needed. +- Do not instantiate UnitOfWork per repository; inject and reuse the same instance (typically with a scoped lifetime) so changes across repositories can be saved together via a single SaveChangesAsync call. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs.md new file mode 100644 index 0000000..a42bd91 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs.md @@ -0,0 +1,51 @@ +# DiskProjectFileService + +> **File:** `src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs` +> **Kind:** class + +*Figure: How DiskProjectFileService works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB +step1["DiskProjectFileService: receive call; call AuthorizeAsync(projectId)"] +step2["Query AppDbContext.ProjectFiles for ProjectId & fileId"] +checkFound{"ProjectFile found?"} +notFound["Throw NotFoundException(ProjectFile, fileId)"] +resolvePath["ResolveFilePath(projectId, ProjectFile.RelativePath) using ProjectFilesOptions; verify inside project dir"] +checkDisk{"File.Exists(fullPath)?"} +missingOnDisk["Log warning and throw NotFoundException(ProjectFile, fileId)"] +openStream["Open FileStream(fullPath) and return (ProjectFile, Stream)"] +isTextLike{"ReadTextAsync: IsTextLike(ProjectFile.ContentType)?"} +returnNull["Return null (not text-like)"] +readLoop["Read up to cap bytes in loop until EOF or cap"] +returnString["Return UTF8 string of bytes read"] + +step1 --> step2 +step2 --> checkFound +checkFound -- yes --> resolvePath +checkFound -- no --> notFound +resolvePath --> checkDisk +checkDisk -- no --> missingOnDisk +checkDisk -- yes --> openStream +openStream --> isTextLike +isTextLike -- no --> returnNull +isTextLike -- yes --> readLoop +readLoop --> returnString +``` + +```csharp +public sealed class DiskProjectFileService : IProjectFileService +``` + + +Stores and retrieves project files by keeping file contents on local disk and file metadata in the database. Use this service when you want durable, file-system-backed storage for project attachments where files are placed under {Root}/{ProjectId:N}/{filename} and every operation enforces authorization and path-traversal protections before touching disk. + +## Remarks +This implementation ties project file metadata (ProjectFile) to physical files on disk and is intended for environments where a shared, mountable filesystem is acceptable. It centralizes concerns that callers would otherwise need to implement themselves: filename sanitization, extension validation, a collision-resilient naming strategy, strict path resolution to prevent traversal attacks, and consistent authorization checks before any read or write. The service logs mismatches between database metadata and on-disk state and surfaces NotFoundException when files are absent. + +## Notes +- OpenAsync returns a FileStream that the caller is responsible for disposing; the method comment explicitly delegates disposal to the caller. +- ReadTextAsync only returns text for content types recognized as "text-like"; for non-text content it returns null. It also caps the returned data to the smaller of the provided maxBytes and the file's recorded size and decodes bytes using UTF-8. +- Uploaded filenames are sanitized and validated against AllowedExtensions; when a sanitized name collides the service chooses a short, fresh suffix so concurrent uploads do not clobber each other. +- Before any disk access the service resolves the final path and verifies it is inside the project's directory to mitigate path-traversal attacks; if metadata exists but the on-disk file is missing a warning is logged and NotFoundException is thrown. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs.md new file mode 100644 index 0000000..4cb206a --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs.md @@ -0,0 +1,18 @@ +# GrokAuthHandler + +> **File:** `src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs` +> **Kind:** class + +```csharp +internal sealed class GrokAuthHandler : DelegatingHandler +``` + + +GrokAuthHandler is a DelegatingHandler that attaches the Grok API key as a Bearer token to every outbound request sent through the Grok-enabled HttpClient pipeline. It reads the key via `IOptionsMonitor`, so a rotated key can be picked up at runtime without recycling HttpClient or its handlers. This centralizes authentication for Grok API calls and avoids scattering token management across individual request sites. + +## Remarks +GrokAuthHandler isolates authentication concerns from business logic and centralizes how the Grok API key is applied. As part of the HttpClient pipeline, it guarantees that all requests from the configured client include the Authorization header when a key is configured, without requiring per-request boilerplate. The use of `IOptionsMonitor` enables dynamic key rotation at runtime, so updated credentials take effect without restarting services. + +## Notes +- Ensure GrokOptions is configured and that SectionName points to a live configuration source; otherwise, apiKey may remain empty and no Authorization header will be added. +- The handler only injects a Bearer header when a non-empty key is present; requests to endpoints that should be anonymous can proceed without authentication when the key is missing. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md new file mode 100644 index 0000000..8c8f60d --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md @@ -0,0 +1,21 @@ +# GrokChatProvider + +> **File:** `src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs` +> **Kind:** class + +```csharp +public class GrokChatProvider : IChatProvider +``` + + +Streams chat completions from xAI's OpenAI-compatible /v1/chat/completions endpoint and exposes them as an IAsyncEnumerable of ChatProviderEvent. Use this provider when you need progressive, token- or chunk-level updates (reasoning deltas, text deltas, and assembled tool calls) instead of waiting for a single completed response. The provider resolves a named HttpClient (constant HttpClientName = "Grok") from IHttpClientFactory for each StreamAsync call, reads the response as a streaming body, parses server-sent `data:` chunks into StreamChunk objects and yields the appropriate ChatProviderEvent instances. On non-success HTTP responses it logs the error and yields a FinishEvent with FinishReason.Error. + +## Remarks +GrokChatProvider encapsulates the streaming and HTTP plumbing required to speak to xAI while keeping DI configuration and connection lifetime concerns out of the caller. It intentionally resolves the named HttpClient per call (via IHttpClientFactory) so the underlying handlers remain pooled and refresh DNS/lifetimes correctly. The Models property returns a snapshot of the configured model catalog (a ToList copy taken from GrokOptions) so the UI can populate a model picker without depending on mutation of the options object. + +The implementation expects chunked SSE-style output: lines prefixed with "data:" containing JSON-serialized StreamChunk objects. It tolerates and logs malformed JSON chunks and skips them rather than failing the whole stream. Tool call fragments are accumulated by index until the stream emits the chunk that indicates tool calls are finished, at which point assembled tool-call events are emitted. + +## Notes +- Models returns a copy of the configured list (ToList()); updates to GrokOptions after construction are not reflected in this collection. +- The stream is driven by enumerating the IAsyncEnumerable; if the caller does not iterate StreamAsync, no request will be sent. +- Tool calls are emitted only after the provider receives the terminal chunk for that tool-call sequence; if the service never sends that terminal chunk, those tool calls will not be emitted. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md new file mode 100644 index 0000000..963778b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md @@ -0,0 +1,17 @@ +# MockChatProvider + +> **File:** `src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs` +> **Kind:** class + +```csharp +public class MockChatProvider : IChatProvider +``` + + +MockChatProvider is a development-time mock implementation of IChatProvider. It streams canned, word-delimited replies to simulate a real provider without credentials and, on the first user turn when tools are present, kicks off the ReAct loop by emitting a ToolCallReadyEvent for the first tool before continuing with text deltas. + +## Remarks +MockChatProvider serves as a lightweight stand-in to exercise the streaming interaction path and tool invocation in the UI and integration tests. It configures a single mock LLM model (mock-default) and uses simple templates to produce predictable, repeatable replies while keeping the real provider's interface intact, which makes it easy to swap in a real provider via DI without changing the consuming code. + +## Notes +- The Templates array initializer uses square brackets, which is invalid C# (should be an array initializer with curly braces). \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs.md new file mode 100644 index 0000000..5f5cd92 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs.md @@ -0,0 +1,21 @@ +# CompositeDocsLookup + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs` +> **Kind:** class + +```csharp +public sealed class CompositeDocsLookup : IDocsLookup +``` + + +CompositeDocsLookup aggregates multiple IDocsLookup sources into a single, ordered view, honoring a defined priority. It delegates ListAsync and ReadAsync calls to its inner sources, merges results by path with earlier sources winning duplicates, and returns primary entries before fallbacks. + +## Remarks + +By design this abstraction avoids a total failure when one source becomes unavailable, skipping failing sources and returning whatever other sources can answer. ReadAsync continues to search in order and returns the first non-null hit; input-validation errors bubble up immediately and cancellation propagates. + +## Notes + +- Path comparison is case-insensitive and duplicates across sources are deduplicated by path; the first entry for a given path wins. +- If all sources fail or return empty, but at least one source threw, the last transient exception is rethrown to reveal the root cause (e.g., rate limits, DNS issues). +- The constructor materializes the provided sources into a list to preserve registration order and avoid re-enumeration on every call. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs.md new file mode 100644 index 0000000..665e281 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs.md @@ -0,0 +1,21 @@ +# GitHubDocsLookup + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs` +> **Kind:** class + +```csharp +public sealed class GitHubDocsLookup : IDocsLookup +``` + + +GitHubDocsLookup is a concrete implementation of IDocsLookup that fetches docs from a GitHub repository. It uses two transport paths—listing the repository tree via the GitHub API and reading individual docs from raw.githubusercontent.com—and caches the list of entries for a configured interval; path validation and a semaphore ensure safe, efficient operation under concurrent access. + +## Remarks + +GitHubDocsLookup abstracts GitHub-specific access behind the IDocsLookup interface, letting the rest of the system discover and retrieve docs without caring about transport or cache details. When listing, it queries the recursive tree, filters for markdown blobs under the configured DocsPath, and returns DocsEntry items marked as GitHub sources. Reading a doc validates the path, builds an absolute URL to raw.githubusercontent.com, and fetches the document content via a dedicated HTTP client. The implementation relies on two named HttpClients (GitHubDocsApi and GitHubDocsRaw) and a configurable ListCacheMinutes to balance freshness with rate-limiting constraints. + +## Notes + +- List cache is controlled by GitHubDocsOptions.ListCacheMinutes and guarded by a semaphore to serialize refreshes. +- ValidatePath(path) protects against '..' segments and absolute paths before any request. +- On a non-successful API response during ListAsync, a warning is logged and an HttpRequestException is thrown to surface the failure to callers. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs.md new file mode 100644 index 0000000..e3b9346 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs.md @@ -0,0 +1,35 @@ +# LocalDocsLookup + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs` +> **Kind:** class + +```csharp +public sealed class LocalDocsLookup : IDocsLookup +``` + + +LocalDocsLookup is a local, on-disk implementation of IDocsLookup that serves Markdown documentation from a configured folder, taking precedence over the GitHub-backed fallback when enabled. It lazily resolves the root path and then enumerates or fetches docs relative to that root, normalizing paths with forward slashes for consistent cataloging. Titles are derived from the first H1 header in each file, and content reads return a DocsContent with a canonical URL and the LocalLlmNative source tag. + +## Remarks +LocalDocsLookup acts as the primary source of self-hosted documentation for the model when LocalDocsOptions.Enabled is true. It uses the same DocsEntry/DocsContent types as the rest of the docs system, so catalogs and readers stay consistent whether the source is local or GitHub. Root resolution is performed once (thread-safely) and cached, so the first ListAsync/ReadAsync call triggers discovery and subsequent calls reuse the computed root. If the root cannot be resolved or the feature is disabled, ListAsync yields an empty set and ReadAsync returns null, allowing a graceful fallback to the GitHub source. + +## Example +```csharp +// Example usage (assumes an initialized LocalDocsLookup named `lookup`) +var entries = await lookup.ListAsync(CancellationToken.None); +foreach (var e in entries) +{ + Console.WriteLine($"{e.Path} -> {e.Title}"); +} + +// Read a specific document +var content = await lookup.ReadAsync("intro/getting-started.md", CancellationToken.None); +if (content is not null) +{ + Console.WriteLine(content.Content.Substring(0, 120)); +} +``` + +## Notes +- Title may be null if the first H1 isn't found in a file; consumers should handle null Title gracefully. +- Root resolution is lazy and cached; changes to LocalDocsOptions.Path won't take effect until the instance is recreated (or the app restarts). diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md new file mode 100644 index 0000000..d152d71 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md @@ -0,0 +1,19 @@ +# BraveWebSearch + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs` +> **Kind:** class + +```csharp +public sealed class BraveWebSearch : IWebSearch +``` + + +BraveWebSearch is a concrete implementation of IWebSearch that queries the Brave Search API via a dedicated HttpClient. It validates configuration, constructs a search URL with a bounded count, executes the request, and maps Brave's response into a uniform list of WebSearchResult for consumers. + +## Remarks +BraveWebSearch encapsulates provider-specific HTTP calls and Brave’s response shape behind IWebSearch, allowing callers to remain agnostic about which search provider is used. It wires HttpClientFactory, BraveSearchOptions, and ILogger to centralize concerns like authentication, timeouts, and error reporting. Only the fields we care about (title, url, description) are projected into WebSearchResult, ensuring downstream code works with a stable, provider-agnostic model. + +## Notes +- The Brave API key must be configured; otherwise, BraveWebSearch throws InvalidOperationException with guidance to set Tools:Web:Brave:ApiKey. +- The requested result count is clamped to 1–10, ensuring a bounded response regardless of the caller's input. +- Non-success HTTP responses are logged with a warning and the method rethrows as HttpRequestException containing the status code; callers should handle this as a transient or propagating error as appropriate. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs.md new file mode 100644 index 0000000..facd71e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs.md @@ -0,0 +1,17 @@ +# CompositeWebSearch + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs` +> **Kind:** class + +```csharp +public sealed class CompositeWebSearch : IWebSearch +``` + + +CompositeWebSearch acts as an IWebSearch that fans out a query to multiple backing providers in parallel and merges their results into a single ranked list. This approach provides broader coverage and a cross-provider signal, improving the chances of surfacing relevant results compared to using any single provider alone. The merge groups results by a canonicalized URL and assigns a score of appearance_count * 1000 - min_rank_across_providers, so cross-provider hits outrank single-provider hits and, within the same appearance count, the highest-ranked hit wins. When duplicates are detected, the first (highest-ranked) raw URL seen is preserved for display, while the Title and Snippet fields are chosen via the described merging rules (best rank, longest non-empty snippet). Each provider is queried with the requested limit and results are merged before trimming back to that limit. Errors from individual providers are caught with a logged warning and do not poison the others; if all providers fail, a zero-result list is surfaced to match the contract of any single backend. The implementation executes per-provider calls in parallel and then aggregates results in memory to produce the final set. + +Key points captured by this symbol include: +- Parallel execution of providers and per-provider error isolation. +- Canonical URL grouping for deduplication without altering the display URL. +- A simple yet effective cross-provider ranking that favors multi-provider signals. +- Safe handling of partial failures with graceful degradation. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md new file mode 100644 index 0000000..ae5adcc --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md @@ -0,0 +1,679 @@ +# DuckDuckGoWebSearch.cs + +> **Source:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` + +## Contents + +- [DuckDuckGoWebSearch](#duckduckgowebsearch) +- [DuckDuckGoWebSearch (constructor)](#duckduckgowebsearch-constructor) +- [BuildRequest](#buildrequest) +- [CleanText](#cleantext) +- [DetectAnomalyPage](#detectanomalypage) +- [EnsureSessionAsync](#ensuresessionasync) +- [FetchAsync](#fetchasync) +- [FirstChars](#firstchars) +- [ParseHtmlEndpoint](#parsehtmlendpoint) +- [ParseLiteEndpoint](#parseliteendpoint) +- [ResetSession](#resetsession) +- [SearchAsync](#searchasync) +- [UnwrapRedirect](#unwrapredirect) +- [Homepage](#homepage) +- [HtmlEndpoint](#htmlendpoint) +- [HtmlResultBlockRegex](#htmlresultblockregex) +- [HtmlSnippetRegex](#htmlsnippetregex) +- [HtmlTitleLinkRegex](#htmltitlelinkregex) +- [HttpClientName](#httpclientname) +- [LiteEndpoint](#liteendpoint) +- [LiteResultLinkRegex](#literesultlinkregex) +- [LiteSnippetRegex](#litesnippetregex) +- [TagStripRegex](#tagstripregex) +- [UserAgents](#useragents) + +--- + +## DuckDuckGoWebSearch +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** class + +```csharp +public sealed class DuckDuckGoWebSearch : IWebSearch +``` + + +A lightweight, no-API-key IWebSearch implementation that queries DuckDuckGo by scraping its public HTML endpoints. It issues searches first against the richer html.duckduckgo.com/html/ endpoint and, if that returns no parseable results, falls back to lite.duckduckgo.com/lite/. Use this implementation when you need an out-of-the-box web search provider without an API key and can tolerate HTML-driven parsing and occasional rate-limiting behavior. + +## Remarks +DuckDuckGoWebSearch is built to be pragmatic rather than strictly robust: it prefers ease-of-use (no key, no quota) and diagnostic transparency over the guarantees of a paid API. To reduce bot detection it picks a single realistic User-Agent for the lifetime of a session, pre-warms the session by visiting DuckDuckGo’s homepage so the HttpClientHandler cookie container is populated, and deduplicates concurrent warmups with a semaphore. The class parses HTML with forgiving regexes (parse failures yield zero results instead of exceptions) and logs detailed diagnostics when DuckDuckGo returns an anomaly/anti-bot page so operators can understand and react to rate-limiting. + +## Example +```csharp +// Resolve an IWebSearch (for example via DI) and perform a search. +// IWebSearch.SearchAsync is the public entrypoint implemented by DuckDuckGoWebSearch. +CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); +IWebSearch webSearch = /* obtain from DI or construct with required services */ null!; +IReadOnlyList results = await webSearch.SearchAsync("how to parse html in c#", 5, cts.Token); +foreach (var r in results) +{ + Console.WriteLine($"{r.Title} - {r.Url}"); +} +``` + +## Notes +- Parsing is HTML/regex-driven; structural changes to DuckDuckGo’s pages can cause zero results rather than exceptions. +- DuckDuckGo may return an anomaly (bot-detection) page; the implementation detects and logs this and will ResetSession() to pick a fresh UA/cookie state when appropriate. +- Session warmup is protected by a SemaphoreSlim; first-call latency may be higher due to the initial homepage request, and concurrent callers are deduped rather than causing multiple warmups. + +--- + +## DuckDuckGoWebSearch (constructor) +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** constructor + +```csharp +public DuckDuckGoWebSearch(IHttpClientFactory httpFactory, ILogger logger) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `httpFactory` | `IHttpClientFactory` | — | +| `logger` | `ILogger` | — | + + +DuckDuckGoWebSearch's constructor wires the object with its essential dependencies: an IHttpClientFactory for creating HttpClient instances and an `ILogger` for logging. This constructor is designed for dependency-injected scenarios or for tests where you provide mocks or fakes for those services; once constructed, the instance stores these dependencies for its HTTP operations and log output. + +## Remarks +- The constructor does not perform null checks; ensure non-null dependencies are supplied (DI containers typically enforce this, but be aware of test or manual setups). +- Using `ILogger` ties log messages to this class, enabling focused filtering and consistent formatting. IHttpClientFactory promotes efficient HttpClient usage and centralized HTTP configuration, reducing socket exhaustion risks. + +## Example +```csharp +// Example: direct construction (useful in tests) +IHttpClientFactory httpFactory = /* obtain from DI or mocks */; +ILogger logger = /* obtain from DI or mocks */; +var search = new DuckDuckGoWebSearch(httpFactory, logger); +``` + +## Notes +- No parameter validation is performed here; rely on your DI setup to supply valid instances. +- Ensure DI configuration provides appropriate HttpClientFactory and logging capabilities for production scenarios. + +--- + +## BuildRequest +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private HttpRequestMessage BuildRequest(HttpMethod method, string url, bool isInitialNavigation) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `method` | `HttpMethod` | — | +| `url` | `string` | — | +| `isInitialNavigation` | `bool` | — | + +**Returns:** `HttpRequestMessage` + + +BuildRequest creates a new HttpRequestMessage and populates it with a browser-like header set for each request. It ensures the User-Agent can be rotated per session, and it fills Accept, Accept-Language, DNT, Upgrade-Insecure-Requests and Sec-Fetch-* headers; crucially, it differentiates initial navigation (no Referer, Sec-Fetch-Site: none) from subsequent in-site navigations (Sec-Fetch-Site: same-site and a Referer). + +## Remarks +BuildRequest centralizes per-request header construction to avoid leaking per-session state into HttpClient.DefaultRequestHeaders, and to preserve accurate navigation semantics for server-side behavior and analytics. It also enables session pinning of the User-Agent and correct variation of Sec-Fetch-Site between navigation phases, which helps mimic real browser traffic more closely and reduces accidental fingerprinting. + +## Notes +- Per-request header construction is deliberate to allow session-specific UA rotation and correct Sec-Fetch-* values for initial vs subsequent navigations; do not rely on HttpClient.DefaultRequestHeaders for this scenario. +- The UA fallback uses UserAgents[0] when the session UA is not yet populated; ensure the session initialization populates _sessionUserAgent before the first BuildRequest call to avoid fingerprint drift. + +--- + +## CleanText +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private static string CleanText(string raw) + => TagStripRegex.Replace(WebUtility.HtmlDecode(raw), "").Trim() +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `raw` | `string` | — | + +**Returns:** `string` + + +Converts a raw HTML string into plain text by decoding HTML entities, stripping HTML tags, and trimming whitespace. It is intended for scenarios where you need readable text extracted from HTML sources rather than markup, such as displaying summaries or processing web content where only the text matters. + +## Remarks +By centralizing the HTML-to-text normalization, CleanText ensures consistent behavior across all call sites and reduces duplication of the decode-strip-trim sequence. It relies on two collaborators: TagStripRegex (for removing tags) and WebUtility (for decoding entities). Changes to those components will propagate to every consumer of CleanText, making this small helper a stable, single point of truth for this transformation. + +## Notes +- The tag-stripping is regex-based and may not cover every edge case of malformed or complex HTML; for strict parsing, consider a dedicated HTML parser. +- The method is private static; external callers cannot call it directly. If external reuse is needed, consider adding a public wrapper. +- The order of operations is intentional: first decode entities, then remove tags. This ensures encoded markup like <tag> is treated as text, not as an actual tag after decoding. + +--- + +## DetectAnomalyPage +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private static bool DetectAnomalyPage(string html) + => html.Contains("anomaly_modal", StringComparison.Ordinal) + || html.Contains("anomaly-modal", StringComparison.Ordinal) + || html.Contains("anomaly.js", StringComparison.Ordinal) + || html.Contains("Just a moment", StringComparison.Ordinal) + || html.Contains("cf-mitigated", StringComparison.Ordinal) + || html.Contains("cf-browser-verification", StringComparison.Ordinal) + || html.Contains("__cf_chl_", StringComparison.Ordinal) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `html` | `string` | — | + +**Returns:** `bool` + + +DetectAnomalyPage is a small helper that inspects an HTML page to determine whether the response is an anti-bot or CAPTCHA-style interstitial produced by Cloudflare and related services. It looks for known markers across multiple blocks (such as anomaly_modal, anomaly-modal, anomaly.js, Just a moment, cf-mitigated, cf-browser-verification, and __cf_chl_) and returns true when any are found. Callers can then fall back to an alternative endpoint or surface a clear diagnostic rather than treating the page as a normal query result. + +## Remarks +This predicate centralizes the detection logic for Cloudflare-style blocks, so callers don't need to duplicate string checks across code paths. Keeping the checks in one place makes it easier to evolve the markers as the upstream pages evolve, and it yields a simple boolean that downstream code can branch on. By consolidating the marker checks, it reduces duplication and clarifies intent. The method returns a boolean, serving as a gate for fallback or diagnostic behavior rather than content parsing. + +## Notes +- The function uses simple substring checks; it does not perform full HTML parsing, so it may miss block indicators not included in the marker list. +- The comparisons use StringComparison.Ordinal to enforce a culture-invariant, fast check and avoid accidental locale-sensitive mismatches. +- As Cloudflare and related pages evolve, you may need to extend the marker set to keep detection accurate. + +--- + +## EnsureSessionAsync +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private async Task EnsureSessionAsync(HttpClient http, CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `http` | `HttpClient` | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +Ensures the DuckDuckGo session is primed by performing a one-time GET of the homepage to establish cookies and set a session-wide User-Agent. The operation is guarded so it runs only once per session and is serialized with a lock to avoid races between concurrent callers; after the first successful (or attempted) warm-up, subsequent calls return immediately. + +During the warm-up, a UA is selected from the available pool and used for the request. The response body is ignored; cookies are collected by the underlying cookie jar and are then applied to subsequent requests for both the html.* and lite.* subdomains. A non-fatal outcome is intentional: if the warm-up fails, the real search request will still proceed, just without the session cookies, and a warning is logged to aid diagnoseability. + +## Remarks +This abstraction centralizes session priming so the rest of the search path can rely on a pre-initialized cookie-backed session and a stable User-Agent, without bogging down the main search flow with setup concerns. The UA is chosen once per session to resemble a consistent browser fingerprint, while the cookie jar is allowed to accumulate cookies across the initial navigation. The design prefers resilience: failures to warm up do not block or crash the search path, they merely omit the session cookies for that run. + +## Notes +- The warm-up is a best-effort, non-fatal operation; if it throws (other than OperationCanceledException), a warning is logged and the search proceeds without session cookies. +- The method uses a private lock to guarantee only one actual warm-up per session; other concurrent callers await the lock and then observe the warmed state. +- Cookies are captured from the homepage response and scoped to .duckduckgo.com so they apply across both html.* and lite.* endpoints. +- A canceled warm-up leaves _sessionWarmed unset, allowing a subsequent attempt to occur if the token is used again. + + +--- + +## FetchAsync +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private async Task FetchAsync(HttpClient http, string url, string query, CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `http` | `HttpClient` | — | +| `url` | `string` | — | +| `query` | `string` | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task` + + +FetchAsync is a private helper that retrieves the DuckDuckGo results for a given query. It first waits a short, randomized delay to mimic human interaction, then issues a GET request constructed from the provided URL and query parameters, and finally returns the response body as a string. If the request fails, the method logs a warning and throws HttpRequestException to surface the error to callers. + +## Remarks +This abstraction centralizes the HTTP invocation for the DuckDuckGo search flow, isolating request creation, timing, error handling, and content retrieval behind a single method. It ensures consistent behavior across requests and makes the higher-level search orchestration easier to reason about. The method honors a CancellationToken to allow callers to cancel the operation and relies on a BuildRequest helper to construct the HttpRequestMessage; the caller receives the raw response content as a string for subsequent parsing or rendering. + +## Notes +- The 200–1200 ms delay is implemented to resemble natural user pacing; tests should tolerate this nondeterminism or allow delay bypassing if deterministic behavior is required. +- On non-success HTTP responses, the method logs a warning and throws HttpRequestException including the URL and status code; callers should handle this exception to implement retry or user-facing failure handling. +- The request URL is built as fullUrl = url + "?q=" + Uri.EscapeDataString(query) + "&kl=us-en"; if the input URL already contains a query string, this naive concatenation could produce an invalid URL, so callers should ensure the base URL is URL-appropriate before invoking. + +## Dependencies +- HttpRequestException +- Task +- Random +- Shared +- Uri +- HttpMethod +- Content + +## Dependency APIs +- property Content (src/api/Gabriel.Core/Entities/Message.cs) + + +--- + +## FirstChars +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private static string FirstChars(string s, int n) + => s.Length <= n ? s : s[..n] +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `s` | `string` | — | +| `n` | `int` | — | + +**Returns:** `string` + + +FirstChars returns the prefix of length n from the input string s, or the entire string if s is shorter than n. It avoids allocation when the string already fits by returning the original string reference; otherwise it uses the range operator to produce the substring. + +## Remarks +This helper encapsulates a simple, explicit operation: obtain up to n characters from the start of s, avoiding allocations when the input already fits. It centralizes the common prefix logic so call sites don't need to reimplement range-based slicing and it makes the boundary behavior (returning the whole string if shorter) explicit. + +## Notes +- Negative values for n will throw due to the range operator; ensure n is non-negative before calling. +- When n >= s.Length, the method returns the original string reference without allocation, which can affect reference equality semantics in callers. +- The signature implies s is non-null; if nulls could appear, callers should guard accordingly in surrounding code. + +--- + +## ParseHtmlEndpoint +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private static List ParseHtmlEndpoint(string html, int limit) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `html` | `string` | — | +| `limit` | `int` | — | + +**Returns:** `List` + + +Parses the HTML produced by the DuckDuckGoWebSearch endpoint and constructs a `List` up to a specified limit. It iterates over result blocks matched by HtmlResultBlockRegex, extracts the block body, uses HtmlTitleLinkRegex to locate the link title and href, HTML-decodes the href, cleans the title text, and unwraps any redirects. If both a non-empty title and URL are produced, an optional snippet is captured via HtmlSnippetRegex (falling back to an empty string when absent) and a new WebSearchResult(title, url, snippet) is appended. The method stops once the limit is reached or no more blocks remain. + +## Remarks +Centralizes the HTML parsing for search results in a single, reusable helper. It encapsulates the sequence of regex-driven extraction, HTML decoding, and text normalization, so call sites don't need to repeat this logic. By operating on a per-call List, it avoids shared mutable state and makes behavior predictable during unit testing. + +## Notes +- The method is private and static, so changes to its parsing rules or to the static regex fields may require corresponding adjustments elsewhere in the class. +- If the HTML blocks frequently yield missing or invalid titles/URLs, the resulting list may be sparser than the input indicates. +- The limit parameter prevents unbounded allocations; callers should choose a sensible cap to balance performance and completeness. + +--- + +## ParseLiteEndpoint +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private static List ParseLiteEndpoint(string html, int limit) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `html` | `string` | — | +| `limit` | `int` | — | + +**Returns:** `List` + + +Parses the HTML produced by the lite endpoint and returns a list of WebSearchResult instances up to the specified limit. It uses LiteResultLinkRegex to locate link elements and LiteSnippetRegex to capture optional snippets, decodes HTML-encoded href values with WebUtility.HtmlDecode, and unwraps potential redirects via UnwrapRedirect. Titles are extracted via CleanText and entries with empty or whitespace-only titles or URLs are skipped. Results are appended in document order; when a snippet is unavailable, an empty string is used, but the corresponding title and URL remain aligned with their snippet. This private helper centralizes the translation of lite-endpoint HTML into a typed collection that callers can consume without parsing HTML themselves. + +## Remarks +Isolates the lite-endpoint parsing from higher-level search orchestration, enabling swapping in alternative lite layouts without changing consumer code. The index-based pairing of links and snippets ensures the URL/title relationship remains correct even when snippets drift or are missing. This abstraction also limits HTML parsing concerns to a single location, simplifying testing and maintenance. + +## Notes +- The limit parameter must be non-negative; passing a negative limit will throw when constructing the List. +- The method is private; external code must use a public wrapper to exercise lite-endpoint parsing. +- Reliance on LiteResultLinkRegex and LiteSnippetRegex means changes to the underlying lite HTML or regex patterns may require updates to this parser. + +--- + +## ResetSession +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private void ResetSession() +``` + +**Returns:** `void` + + +Resets internal session state after DDG hands an anomaly page. It clears the warmed flag and the current user agent so the next SearchAsync rebuilds the session with a freshly-picked UA, triggering another homepage round-trip and refreshing cookies and fingerprint. + +## Remarks +This private helper centralizes the session invalidation step in the anomaly-handling path, ensuring a clean slate for the subsequent request. By dropping the warmed state and UA, the flow guarantees a fresh browser context on the next cycle, helping adapt to evolving responses from DDG and maintain the integrity of the session lifecycle. + +## Notes +- Not thread-safe: concurrent invocations may race on _sessionWarmed and _sessionUserAgent. Synchronize or constrain usage to a single execution flow when needed. + +--- + +## SearchAsync +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +public async Task> SearchAsync(string query, int limit, CancellationToken ct) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `query` | `string` | — | +| `limit` | `int` | — | +| `ct` | `CancellationToken` | — | + +**Returns:** `Task>` + + +SearchAsync coordinates a two-endpoint DuckDuckGo web search. It starts by creating an HTTP client, ensuring a session is established, and querying the rich html/ endpoint to obtain results with snippets and redirect-wrapped URLs. If the html/ response is detected as an anomaly, it is logged and the method falls back to the lighter lite/ endpoint. When the html/ response is not anomalous, parsed results are returned immediately if any are found; otherwise an informational log notes the absence of parseable results. + +If the primary path yields no usable results, the method fetches from the lite/ endpoint. As with the primary path, anomalies are logged and non-empty parseable results are returned when available; otherwise an informational log records zero parseable results from lite/. + +If either endpoint reports an anomaly/block (detected via DetectAnomalyPage), the session is reset and an InvalidOperationException is thrown with guidance to switch to a managed toolchain (e.g., Brave or Tavily) and provide the corresponding API key for reliable web search. This aligns with the idea that free scraping endpoints can rate-limit or block residential/datacenter IPs, requiring a different approach for production reliability. + +When both endpoints deliver 200 responses but no parseable results, an empty `IReadOnlyList` is returned, signaling a genuine absence of results for the query. + +--- + +## UnwrapRedirect +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** method + +```csharp +private static string UnwrapRedirect(string href) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `href` | `string` | — | + +**Returns:** `string` + + +UnwrapRedirect centralizes the logic for recovering the final destination URL from a DuckDuckGo redirect wrapper. It searches for the uddg= parameter and, when found, decodes its value with Uri.UnescapeDataString to return the real target URL. If the wrapper pattern is not present, it preserves the original href; however, protocol-relative URLs (starting with //) are rewritten to https:// to yield an absolute, modern URL. If decoding fails for any reason, the original href is returned unmodified. This function is intentionally small and defensive, ensuring callers always receive a usable URL without throwing. + +## Remarks +UnwrapRedirect exists to centralize the logic for extracting the real URL from DuckDuckGo's redirect wrapper. It encapsulates detection of the uddg parameter, decoding of its value, and a safe fallback when the wrapper is absent or decoding fails. This keeps downstream code focused on URL processing rather than wrapper-specific quirks and ensures protocol-relative URLs are normalized to absolute HTTPS URLs when encountered. + +## Notes +- Uses ordinal string comparisons to avoid culture-specific quirks when detecting the wrapper and protocol-relative form. +- Decoding errors are swallowed; in such cases the original href is returned to maintain non-throwing behavior. + +--- + +## Homepage +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private const string Homepage = "https://duckduckgo.com/" +``` + + +Stores the base URL for the DuckDuckGo web search endpoint used by the internal web search logic. As a private const, it centralizes the endpoint value so the class can reliably build full request URLs without scattering the literal string throughout the codebase. + +## Remarks +Centralization reduces duplication and guards against typos when constructing URLs. The private visibility enforces encapsulation, while the const ensures the value is known at compile time and can be inlined for performance. + +## Notes +- Because it is private, external code cannot reference or override this value. +- Because it is a compile-time constant, any change requires recompilation of all code that references it; runtime reconfiguration is not possible through injection. + +--- + +## HtmlEndpoint +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private const string HtmlEndpoint = "https://html.duckduckgo.com/html/" +``` + + +HtmlEndpoint is a private constant string that stores the absolute URL of DuckDuckGo's HTML search endpoint. It is the primary target for obtaining rich HTML search results in the web search integration, pointing to https://html.duckduckgo.com/html/ regardless of any HttpClient.BaseAddress configuration. This explicit separation from the lite endpoint (https://lite.duckduckgo.com/lite/) prevents routing ambiguities and ensures the lite interface remains available as a fallback path; it also guards against the historical issue where both endpoints pointed to the HTML host, which silently broke the lite layout. + +--- + +## HtmlResultBlockRegex +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly Regex HtmlResultBlockRegex = new( + @"]*class=""[^""]*\bresult\b[^""]*""[^>]*>(?.*?)(?=]*class=""[^""]*\bresult\b|]*id=""bottom_spacing""|\Z)", + RegexOptions.Singleline | RegexOptions.Compiled) +``` + + +HtmlResultBlockRegex is a compiled, static readonly Regex used to extract individual result blocks from the HTML returned by the search endpoint. It locates a
with a class containing the word result and captures its inner content as body until the next result block, the bottom spacer, or end of input, enabling callers to iteratively process each result. + +## Remarks +This abstraction centralizes the parsing logic around the server-generated markup, ensuring a single, well-defined way to pull out each result's body. Because the Regex is compiled and reused, it minimizes allocations and improves performance in hot paths that scan many results. The approach also makes it easy to revert to a fallback path (e.g., a lite-mode parser) if the upstream markup changes and the current pattern no longer matches expected blocks. + +## Notes +- The pattern relies on server-side HTML structure; any change to the markup may require updating the regex and could break extraction. +- Due to being private and compiled, changes require code changes and recompilation; ensure tests cover both the pattern and its fallback behavior. + +--- + +## HtmlSnippetRegex +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly Regex HtmlSnippetRegex = new( + @"class=""[^""]*\bresult__snippet\b[^""]*""[^>]*>(?.*?)", + RegexOptions.Singleline | RegexOptions.Compiled) +``` + + +This private static readonly Regex centralizes the extraction of the visible snippet text from DuckDuckGo search result HTML. It matches an anchor tag whose class attribute includes result__snippet and captures the inner text in a capture group named text. The Regex is compiled and cached as a static field for performance, and Singleline is used so multiline snippet content is captured correctly. + +## Remarks +By encapsulating this pattern as a single shared, compiled Regex, the class avoids duplicating the parsing logic for each result and ensures consistent extraction of snippet text across invocations. The field being private static readonly guarantees thread-safe reuse after initialization and prevents accidental reassignment. + +## Notes +- HTML structure changes can break the regex-based parsing; prefer updating the pattern in one place. +- HtmlSnippetRegex is private; use public helpers in the class to access snippet text safely. +- The capture group name 'text' is part of the contract; renaming would break downstream usage. + +--- + +## HtmlTitleLinkRegex +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly Regex HtmlTitleLinkRegex = new( + @"]*class=""[^""]*\bresult__a\b[^""]*""[^>]*href=""(?[^""]+)""[^>]*>(?.*?)", + RegexOptions.Singleline | RegexOptions.Compiled) +``` + + +HtmlTitleLinkRegex is a private, precompiled Regex that targets the HTML anchor element used for a DuckDuckGo search result title. It captures two pieces of data from the matching anchor: the href attribute (named group href) and the displayed text inside the anchor (named group text). The field is stored as a static readonly instance to be reused across invocations, avoiding repetitive Regex construction and improving performance when parsing multiple search results. + +## Remarks +Because the Regex instance is static and readonly, it is safe to reuse concurrently across threads. The Regex is compiled for speed and uses Singleline to tolerate HTML where the title may span line breaks. If the underlying HTML structure changes (for example, a different class name or tag shape for the result title link), this pattern would need updating to restore correct matching. + +## Notes +- The regex returns only the first matching anchor; enumerate all occurrences with Regex.Matches if needed. +- The captured title text may contain HTML entities; decode them if the raw text is used directly. + + +--- + +## HttpClientName +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +public const string HttpClientName = "DuckDuckGoSearch" +``` + + +Defines the literal name used to create or retrieve the HttpClient instance configured for the DuckDuckGo web search integration. Centralizing the client name avoids hard-coded strings scattered across the codebase and ensures consistent registration and retrieval via IHttpClientFactory. + +Developers reach for this constant when they need a named HttpClient (e.g., when registering the client or obtaining it from DI) instead of constructing HttpClient directly. + +## Remarks + +This constant acts as an infrastructural glue between the DuckDuckGo web search component and its HTTP configuration. By naming the client, the app can apply per-client settings—such as base address, default headers, or timeouts—in one place, then reuse the same configured client wherever HTTP calls are made. Keeping the name in a single symbol prevents subtle bugs from string mismatches and makes the intent explicit across registration and usage sites. + +## Notes + +- Do not change HttpClientName's value without ensuring every registration and consumer that relies on the named client is updated. +- Ensure that there is a corresponding AddHttpClient(HttpClientName, ...) registration during startup; otherwise DI resolution may fail. + +--- + +## LiteEndpoint +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private const string LiteEndpoint = "https://lite.duckduckgo.com/lite/" +``` + + +Defines the base URL for the DuckDuckGo Lite search endpoint used by the web search logic. It is private and const, meaning the value is baked into the assembly and shared across the class's request construction, ensuring all internal calls consistently target the same endpoint. + +## Remarks +Centralizes the lite endpoint to a single source of truth within the DuckDuckGo integration. Keeping it private signals that consumers should not rely on or modify this value directly; it can help with readability and future refactoring by isolating the endpoint in one place. If the Lite URL needs to change by environment, consider making the endpoint configurable rather than hard-coded. + +## Notes +- Private const means the value is not accessible to external code and is compiled into the assembly. +- As a const field, it cannot be changed at runtime; modifying the endpoint would require recompiling the project. + +--- + +## LiteResultLinkRegex +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly Regex LiteResultLinkRegex = new( + @"]*href=""(?[^""]+)""[^>]*class='result-link'[^>]*>(?.*?)", + RegexOptions.Singleline | RegexOptions.Compiled) +``` + + +This private, precompiled Regex is used by the lite endpoint parser to extract the destination URL and the visible title from anchor tags representing search results. It targets anchor elements with class='result-link', capturing the href value and the inner text as named groups href and text. The pattern aligns with the lite endpoint's HTML shape, and the surrounding parsing logic routes these anchors through UnwrapRedirect when necessary (the helper is a no-op if the redirect marker isn't present). The field is static readonly to avoid repeated compilation and to share the same instance across parsing operations. + +## Remarks + +By centralizing the parsing pattern, this symbol provides a single, maintainable point of change for how lite results are converted from raw HTML into structured data. Compiling the regex at type initialization improves performance under high-load scenarios where many results are parsed. Because the field is private, its usage is confined to the hosting class, reducing risk of misuse and keeping the HTML parsing concerns encapsulated. The named capture groups (href and text) enable downstream code to access the target URL and display text directly without additional parsing. + +## Notes + +- The regex assumes a specific HTML shape: a link with class='result-link' and an href attribute using double quotes, with the class attribute using single quotes. If the markup changes, this pattern may need updating. +- It uses named capture groups href and text to feed downstream processing without additional parsing. +- The RegexOptions.Singleline and RegexOptions.Compiled options optimize matching across multi-line anchors and improve startup performance, respectively. + +--- + +## LiteSnippetRegex +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly Regex LiteSnippetRegex = new( + @"]*class='result-snippet'[^>]*>(?.*?)", + RegexOptions.Singleline | RegexOptions.Compiled) +``` + + +LiteSnippetRegex is a private, shared regular expression used to pull the visible snippet text from the HTML of a search results response. It targets a table cell element with class='result-snippet' and captures the inner content into a named group called "text". The pattern is initialized as a static readonly field and compiled for performance; the Singleline option allows the dot to match newline characters, enabling reliable extraction of multi-line snippets from results pages. + +--- + +## TagStripRegex +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly Regex TagStripRegex = new(@"<[^>]+>", RegexOptions.Compiled) +``` + + +TagStripRegex is a precompiled, shared Regex instance used to strip HTML-like tags from text. A developer would reach for it when needing a fast, centralized mechanism to remove tag markup (typically via Replace(input, "")) in content sourced from HTML responses, rather than building a new Regex each time. + +## Remarks +Consolidating tag stripping into a static, readonly Regex ensures a single compiled instance is reused, reducing allocations and improving performance in text-cleaning code paths. The <[^>]+> pattern targets basic tags but is not a comprehensive HTML sanitizer; complex constructs like comments or scripts may require extra handling or a proper parser. Being private to the class, the field encapsulates the logic and promotes consistent, thread-safe tag removal across all call sites within the class. + +## Notes +- The pattern is simplistic and may not cover all HTML edge cases (e.g., comments, CDATA, or script content) and could affect non-tag text in unusual input. +- The field is private; external code cannot reuse it directly—consider exposing a helper method if tag stripping should be shared. +- RegexOptions.Compiled improves performance on hot paths but can increase startup time and memory usage; ensure it aligns with your deployment constraints. + +--- + +## UserAgents +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly string[] UserAgents = + [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + ] +``` + + +A private static readonly array named UserAgents stores a small pool of recent real-browser User-Agent strings. It is not rotated per request; instead, one UA is selected during session warmup and used for the entire session, mirroring how real browsers behave and avoiding rapid UA switching that can signal automation. The pool is designed to spread fingerprints across deployments and restarts rather than per-request changes, providing stable headers while still offering environmental variety. + +## Remarks +This field acts as the centralized, session-scoped UA fingerprint source for HTTP headers used by the DuckDuckGo web search integration. By fixing a UA for the session, it reduces noise in fingerprints and lowers detection risk, while the pool itself allows re-seeding fingerprints across restarts. Because the array is private static readonly, the values can't be changed at runtime; updating the pool requires a code change and redeploy. + +## Notes +- Changing the pool requires a rebuild and redeploy; the values are baked into the binary due to static initialization. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/BuildRequest.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/BuildRequest.cs.md new file mode 100644 index 0000000..31bc25f --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/BuildRequest.cs.md @@ -0,0 +1,9 @@ +BuildRequest creates a HttpRequestMessage configured with a browser-like header set to mimic a real navigation. It selects a per-session User-Agent (falling back to the first entry if none is set), applies standard Accept headers, and uses Sec-Fetch semantics that distinguish an initial navigation from an in-site hop, enabling session pinning and correct referer handling. + +## Remarks +By centralizing header assembly, this method ensures that each outgoing request carries a consistent fingerprint and correct navigation context, without polluting HttpClient.DefaultRequestHeaders across multiple requests. The per-request approach makes it easier to rotate user agents and vary the referer as the user progresses from warmup to an actual search. + +## Notes +- Falls back to UserAgents[0] when _sessionUserAgent is not populated. +- Referer header is only set for non-initial navigations; ensure Homepage is a valid URL to be used as the Referer. +- Some servers may ignore or override browser-like headers; callers should not rely on these headers for strict security policies. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/CleanText.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/CleanText.cs.md new file mode 100644 index 0000000..a851958 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/CleanText.cs.md @@ -0,0 +1,9 @@ +Decodes HTML entities in the input, strips HTML tags, and trims surrounding whitespace to produce clean, plain-text output. Use this when you need to render or store text extracted from HTML content in a readable, markup-free form. + +## Remarks +This method centralizes HTML normalization, ensuring consistent results for strings sourced from HTML content that must be displayed or processed as plain text. By applying HtmlDecode first and then removing markup, it preserves legibility while discarding tags; its private scope indicates it is an internal utility used by higher-level web-search related logic. + +## Notes +- Null input will typically throw; ensure a non-null string is provided before calling. +- Relying on TagStripRegex means very malformed or complex HTML may not be perfectly sanitized. +- This is a private helper; changes to its behavior or signature can occur without affecting the public API. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/DetectAnomalyPage.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/DetectAnomalyPage.cs.md new file mode 100644 index 0000000..ed54bf2 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/DetectAnomalyPage.cs.md @@ -0,0 +1,11 @@ +Determin es whether a given HTML response is an anomaly page produced by bot-blocking services (such as Cloudflare interstitials) by searching for a curated set of markers. Callers can use this to surface a diagnostic or fall back to a sibling endpoint instead of treating the page as a normal result. + +## Remarks + +This predicate centralizes the detection logic for anti-bot / blocking pages, consolidating several brittle string checks into a single, reusable gate. It relies on ordinal (case-sensitive) string comparisons, minimizing culture-related surprises but making the checks sensitive to exact page fragments; updates may be needed if providers change their markers. + +## Notes + +- Null input will cause an exception; ensure the html argument is non-null before invoking. +- Marker set may become outdated as anti-bot providers evolve—update the list of strings as needed. +- This approach uses simple substring checks and may yield false positives or negatives on obfuscated or minified content; a more robust parser could improve reliability. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/EnsureSessionAsync.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/EnsureSessionAsync.cs.md new file mode 100644 index 0000000..3fe4773 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/EnsureSessionAsync.cs.md @@ -0,0 +1,9 @@ +Ensures a DuckDuckGo session is established by performing a one-time GET to the homepage for the current session. The method acts as a lazy initializer: on the first call it acquires a lock, selects a stable User-Agent from the pool, sends a request to the homepage, and relies on the CookieContainer to capture session cookies. The body of the response is ignored; cookies set by the round trip are the signal that the session is ready for subsequent searches across html.* and lite.* subdomains. If the warm-up fails due to non-cancelled exceptions, the failure is logged but the actual search proceeds without the cookies; the session is marked as warmed to avoid repeated attempts. + +## Remarks +The method centralizes the initial navigation effects (cookies, UA) so that subsequent search requests can assume a ready session. It helps mimic a real browser startup to reduce heuristics detection and keeps the one-time side effects isolated from higher-level search logic. The concurrency guard guarantees a single initialization per session even under concurrent invocations. + +## Notes +- Warm-up is best-effort: non-cancelled failures are swallowed after logging and do not abort the subsequent search. +- Cookies collected during this round-trip are scoped to .duckduckgo.com and apply automatically to both html.* and lite.* subdomains via the CookieContainer. +- A single initialization per session is enforced by the combination of the _sessionWarmed flag and the _sessionLock. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FetchAsync.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FetchAsync.cs.md new file mode 100644 index 0000000..e265898 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FetchAsync.cs.md @@ -0,0 +1,8 @@ +FetchAsync is a compact helper that retrieves the HTML content for a DuckDuckGo search by performing an HTTP GET after a short, randomized delay. It takes an HttpClient, a base URL, a search query, and a CancellationToken; it builds a request URL with the query encoded as q and a fixed locale parameter (kl=us-en), sends the request, and returns the response body as a string. If the response indicates failure, it logs a warning and throws HttpRequestException containing the HTTP status, enabling callers to respond to transient network conditions. + +## Remarks +This method centralizes the web-fetch logic used by the DuckDuckGo navigation flow, encapsulating URL construction, request sending, and error handling. The intentional random delay helps mimic human interaction cadence and reduces the likelihood of bot-detection signals when issuing rapid requests in sequence. By isolating these concerns behind a private helper, higher-level search logic remains concise, testable, and resilient to common HTTP errors. + +## Notes +- The delay is cancellable via the provided CancellationToken; a cancellation will surface as a TaskCanceledException. +- Non-success HTTP responses yield an HttpRequestException that includes the status code; callers should implement appropriate retry or fallback strategies. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FirstChars.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FirstChars.cs.md new file mode 100644 index 0000000..d72a376 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/FirstChars.cs.md @@ -0,0 +1,12 @@ +Returns the prefix of the input string s consisting of at most n UTF-16 code units. If s.Length is less than or equal to n, the method returns s unchanged. This private helper centralizes truncation logic so callers can safely obtain a substring of a maximum length without duplicating the boundary check at every call site. + +## Remarks + +This method consolidates the truncation behavior within the class to ensure consistent results wherever a short preview or label is needed. Truncation operates on UTF-16 code units; as such, cutting in the middle of a surrogate pair or a combining sequence may yield an invalid or visually odd string. For user-facing truncation that preserves full characters or grapheme clusters, consider a text-element-aware approach (e.g., System.Globalization.StringInfo). + +Because the method is private, it is an internal implementation detail rather than part of the public API. + +## Notes + +- If n < 0, the slicing s[..n] would throw; ensure n is non-negative before invoking (the method itself does not guard against negative values). +- If s is null, accessing s.Length will throw a NullReferenceException. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/Homepage.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/Homepage.cs.md new file mode 100644 index 0000000..8348769 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/Homepage.cs.md @@ -0,0 +1,14 @@ +Stores the canonical DuckDuckGo homepage URL as a private, compile-time constant used by the DuckDuckGoWebSearch implementation. By centralizing the endpoint in a single, immutable value, it avoids string duplication and typos when constructing requests or navigation logic inside the class. + +## Remarks +Private constants like this act as a small, self-contained configuration detail that the class relies on to form complete requests to the DuckDuckGo homepage. It helps keep the code readable by giving a named, obvious endpoint instead of scattering the literal URL across methods. + +## Example +```csharp +// Example: using the constant to create a request to the homepage +var request = new HttpRequestMessage(HttpMethod.Get, Homepage); +``` + +## Notes +- This value is baked into the consuming assembly; changing it requires recompilation of dependents. +- Being private means external code cannot reference it directly; if you need sharing or testing access, consider exposing an internal or public accessor or injecting the value via configuration. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlEndpoint.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlEndpoint.cs.md new file mode 100644 index 0000000..bfc0430 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlEndpoint.cs.md @@ -0,0 +1,18 @@ +# HtmlEndpoint + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private const string HtmlEndpoint = "https://html.duckduckgo.com/html/" +``` + + +HtmlEndpoint is a private const string that caches the absolute URL for DuckDuckGo's primary HTML search endpoint. It guarantees that requests retrieve the rich HTML output by hitting https://html.duckduckgo.com/html/ directly, independent of HttpClient.BaseAddress, with the lite endpoint used as a separate fallback. + +## Remarks +This abstraction centralizes the endpoint choice, ensuring consistent routing to the HTML service and preventing misrouting when clients configure different base addresses. By using an absolute URL, the code remains robust against base address changes and ensures the lite fallback remains functional. + +## Notes +- Private, compile-time constant: cannot be changed at runtime; inlining may occur. +- If the target endpoint changes, update this constant and recompile; otherwise calls may go to wrong host. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlResultBlockRegex.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlResultBlockRegex.cs.md new file mode 100644 index 0000000..6c03810 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlResultBlockRegex.cs.md @@ -0,0 +1,17 @@ +HtmlResultBlockRegex is a private, static Regex used to extract individual result blocks from the HTML endpoint response. It matches a div whose class contains the word 'result' and captures its inner content as the named group 'body', stopping before the next result block, the bottom spacing marker, or end-of-content. + +## Remarks +Centralizes the HTML parsing pattern for search results behind a single, reusable symbol, reducing duplication and making future HTML structure changes easier to accommodate. The pattern uses Singleline and RegexOptions.Compiled to balance correctness across multi-line HTML and runtime performance. The accompanying comment notes a graceful fallback to zero results if the server surface changes, which guides callers to handle missing matches without crashing. + +## Example +```csharp +foreach (Match m in HtmlResultBlockRegex.Matches(html)) +{ + string body = m.Groups["body"].Value; + // Process blockHtml here +} +``` + +## Notes +- The regex relies on the presence of a class containing the word 'result' in the HTML; if the markup changes (e.g., different class names or casing), the matches may be lost. +- Because the field is static readonly and compiled, it initializes once per AppDomain and requires recompilation to reflect changes; consider dependencies on this behavior when evaluating hot-reload scenarios. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlSnippetRegex.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlSnippetRegex.cs.md new file mode 100644 index 0000000..c1107b8 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlSnippetRegex.cs.md @@ -0,0 +1,31 @@ +# HtmlSnippetRegex + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private static readonly Regex HtmlSnippetRegex = new( + @"class=""[^""]*\bresult__snippet\b[^""]*""[^>]*>(?.*?)", + RegexOptions.Singleline | RegexOptions.Compiled) +``` + + +This private, precompiled Regex matches the anchor element in DuckDuckGo search result HTML whose class includes result__snippet and captures its inner text as the group 'text'. It is used when the code needs to extract the visible snippet from a page's HTML, avoiding ad hoc string handling and re-compiling the pattern on every call. + +## Remarks +Centralizes HTML-snippet extraction and improves performance by reusing a single compiled Regex. It is brittle to HTML structure changes; if DuckDuckGo alters their markup or class names, the pattern will likely need updating. + +## Example +```csharp +// Example usage (within the same class) +var m = HtmlSnippetRegex.Match(htmlFragment); +if (m.Success) +{ + string snippet = m.Groups["text"].Value; +} +``` + +## Notes +- Matches a single snippet per input; to process all results, iterate over Matches. +- The captured text may require decoding HTML entities. +- The Groups["text"] value may contain leading or trailing whitespace. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlTitleLinkRegex.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlTitleLinkRegex.cs.md new file mode 100644 index 0000000..4169c98 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HtmlTitleLinkRegex.cs.md @@ -0,0 +1,9 @@ +This private static readonly Regex consolidates the HTML pattern used to extract DuckDuckGo search result title links from the HTML returned by the search page. The pattern matches an anchor tag whose class contains result__a and captures two pieces of data: the destination URL from the href attribute (named group href) and the visible link text (named group text). The Regex is constructed with Singleline and Compiled options to perform a fast, shared parsing operation across the class. + +## Remarks +Centralizes the knowledge of DuckDuckGo's result link markup in one place, enabling consistent extraction without duplicating the regex across methods. Because the HTML structure of search results can change, this implementation should be accompanied by tests and simple fallback handling if the pattern no longer matches. + +## Notes +- This regex assumes the presence of an href attribute and a non-empty link text; if the HTML deviates, matches may fail. +- RegexOptions.Singleline makes the dot match newlines; ensure that HTML with line breaks doesn't break the pattern. +- The static readonly instance is thread-safe for concurrent use after initialization and should not be replaced at runtime. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HttpClientName.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HttpClientName.cs.md new file mode 100644 index 0000000..8eb1419 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/HttpClientName.cs.md @@ -0,0 +1,30 @@ +HttpClientName defines the canonical name used when registering and obtaining a named HttpClient for the DuckDuckGo web search integration. Use this constant to avoid hard-coded strings and keep all HttpClientFactory registrations and lookups in sync. + +## Remarks +This constant centralizes the client name so that multiple parts of the application don't drift apart in their DI usage. It aligns with the HttpClientFactory pattern and improves testability by making the dependency explicit and reusable across registration and consumption sites. + +## Example +```csharp +// Register a named HttpClient for DuckDuckGo-related calls +services.AddHttpClient(HttpClientName, client => { + client.BaseAddress = new Uri("https://api.duckduckgo.com/"); + // configure defaults (headers, timeouts, etc.) as needed +}); + +// Consume the named client via IHttpClientFactory +public class DuckDuckGoSearcher +{ + private readonly HttpClient _httpClient; + + public DuckDuckGoSearcher(IHttpClientFactory httpClientFactory) + { + _httpClient = httpClientFactory.CreateClient(HttpClientName); + } + + // Use _httpClient to perform requests +} +``` + +## Notes +- This is a compile-time constant; changing its value requires recompiling all referencing code. +- Ensure consistent use of HttpClientName in both AddHttpClient registrations and CreateClient calls to avoid runtime misconfigurations. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteEndpoint.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteEndpoint.cs.md new file mode 100644 index 0000000..308bcae --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteEndpoint.cs.md @@ -0,0 +1,11 @@ +# LiteEndpoint + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private const string LiteEndpoint = "https://lite.duckduckgo.com/lite/" +``` + + +LiteEndpoint is a private constant string that stores the URL for DuckDuckGo's Lite search endpoint, set to https://lite.duckduckgo.com/lite/. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteResultLinkRegex.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteResultLinkRegex.cs.md new file mode 100644 index 0000000..dd2dce0 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteResultLinkRegex.cs.md @@ -0,0 +1,9 @@ +Represents a precompiled regular expression used by the Lite endpoint parsing to extract result links from the simplified Lite HTML. The pattern matches anchor elements that have an href attribute and the class attribute set to result-link, capturing the URL as href and the displayed text as text. This regex assumes the Lite endpoint's flatter structure where a link is followed (one row down) by a snippet cell; even though such links are not wrapped in /l/?uddg=…, they are still passed through UnwrapRedirect for safety — the helper is a no-op when the marker is absent. + +## Remarks +The Regex is stored as a private static readonly field and is compiled to ensure fast matching in hot paths, avoiding repeated allocations during parsing of multiple Lite results. It focuses narrowly on anchors with class='result-link' so the rest of the HTML is ignored, and it exposes two named capture groups (href and text) that downstream code uses to obtain the destination URL and the link title. + +## Notes +- The named capture groups href and text are relied upon by the surrounding parsing logic; renaming them would break the extraction. +- Compiled Regex trades a small startup cost for faster repeated matches in tight loops. +- The Lite HTML structure is assumed to remain stable; changes to the anchor shape or class name would require updating the pattern. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteSnippetRegex.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteSnippetRegex.cs.md new file mode 100644 index 0000000..6d1f768 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/LiteSnippetRegex.cs.md @@ -0,0 +1 @@ +A precompiled private static readonly Regex that targets the snippet portion of a DuckDuckGo search result. It matches a td element with class='result-snippet' and captures the inner content into a named group called text. The field is cached as a static member for reuse across invocations and is compiled for performance, with Singleline enabled so the captured text may span multiple lines within the cell. This pattern is used by the class’s parsing workflow to extract the raw snippet text from the HTML response without reconstructing the regex on every parse. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseHtmlEndpoint.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseHtmlEndpoint.cs.md new file mode 100644 index 0000000..705cbae --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseHtmlEndpoint.cs.md @@ -0,0 +1,9 @@ +Parses the provided HTML to yield a collection of WebSearchResult objects up to the specified limit. It scans the HTML using HtmlResultBlockRegex to locate result blocks, extracts the body portion from each block, and then uses HtmlTitleLinkRegex to find the title link. The href is HTML-decoded, and the URL is obtained by UnwrapRedirect; the title text is cleaned with CleanText. If either the title or URL is empty, that block is ignored. If a snippet is present within the body, it is cleaned and stored; otherwise, the snippet is an empty string. Each valid block is turned into a WebSearchResult(title, url, snippet) and added to the results, which are returned once all blocks are processed or the limit is reached. + +## Remarks +By isolating HTML parsing into ParseHtmlEndpoint, this method provides a focused, testable step that translates raw web search page markup into the domain object WebSearchResult. It relies on a small set of regex-based extractors and a redirect unwrapper, enabling consistent handling of links and metadata across different blocks. + +## Notes +- The loop stops processing once the requested limit is reached, potentially ignoring remaining blocks. +- Blocks without a valid title or URL are skipped, so not all matched blocks contribute results. +- The snippet is optional; if absent, the resulting WebSearchResult stores an empty snippet. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseLiteEndpoint.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseLiteEndpoint.cs.md new file mode 100644 index 0000000..d19eeb6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ParseLiteEndpoint.cs.md @@ -0,0 +1,17 @@ +ParseLiteEndpoint is a private static helper that converts a chunk of lite DuckDuckGo HTML into a List. It scans the HTML for result links and, in parallel, for their corresponding snippets. For each link up to the provided limit, it HTML-decodes the href, cleans the visible text to produce a title, unwraps possible redirects to yield a stable URL, and attaches a snippet if one exists. Entries with missing title or URL are discarded. Snippets are matched by index to their links, and if no snippet is available for a given entry, the snippet is left empty. The resulting collection is ordered to reflect the original document order and can be used by higher-level search UI or processing code. + +## Remarks +This method encapsulates the brittle HTML parsing required to extract meaningful search results from the lite DuckDuckGo layout, exposing a strongly-typed collection to downstream code. By decoupling the parsing logic from presentation, it keeps the rest of the pipeline focused on rendering and interaction. The snippet alignment strategy is simple and relies on the document order; as long as the lite layout remains stable, the produced WebSearchResult items stay synchronized with their corresponding snippets. The URLs are normalized via UnwrapRedirect to ensure consumers receive clean, direct links. + +## Example +```csharp +// Example inside the same class that contains ParseLiteEndpoint +string html = GetLiteDuckDuckGoResultHtml(); +List results = ParseLiteEndpoint(html, 10); +// results contains up to 10 items with Title, Url, and optional Snippet +``` + +## Notes +- The method is private; to expose this functionality publicly, wrap it in another method or elevate access accordingly. +- If the HTML structure changes, the LiteResultLinkRegex or LiteSnippetRegex may fail to match, which would reduce or alter the results returned. +- If a link has no title or a blank URL, it is skipped; the snippet for that entry, if any, is ignored since the entry itself is discarded. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ResetSession.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ResetSession.cs.md new file mode 100644 index 0000000..384338b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/ResetSession.cs.md @@ -0,0 +1,9 @@ +Resets the current DuckDuckGo session state used during automated searches. When invoked after detecting an anomaly page, it clears the warmed flag and the cached User-Agent so the next search rebuilds the HTTP session with a freshly chosen UA. This triggers another homepage round-trip, refreshing the cookie jar provided by DDG and subtly altering the request fingerprint for subsequent interactions. + +## Remarks +Encapsulates the session-lifecycle recovery strategy in one place, decoupling anomaly handling from the normal search flow. It enables retry-with-new-session behavior instead of continuing with a potentially stale or blocked session; UA rotation and cookie refresh are tied to this reset to improve resilience against anti-bot measures. + +## Notes +- Private to the class; should be invoked only by internal anomaly-handling code. +- The reset does not immediately delete cookies; a subsequent network call must occur to refresh cookies. +- Calls are effectively idempotent; repeated invocations simply ensure the session is in a clean initial state before the next request. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/SearchAsync.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/SearchAsync.cs.md new file mode 100644 index 0000000..9e59ed4 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/SearchAsync.cs.md @@ -0,0 +1,14 @@ +SearchAsync orchestrates a resilient web search against DuckDuckGo by first attempting the rich HTML endpoint and then falling back to the lite endpoint if necessary. It returns up to the specified limit as a read-only list of WebSearchResult. If both endpoints indicate bot-blocking/anomaly, it resets the session and throws InvalidOperationException to signal that scraping is blocked and to guide the caller toward a supported API key-based approach. When results exist, it returns them; when neither endpoint yields results, it returns an empty list. + +## Remarks +This abstraction hides the endpoint selection, anomaly handling, and session management behind a single method. It ensures consistent logging and fallback behavior, and centralizes the bot-detection flow so callers don't duplicate that logic. By encapsulating the dual-endpoint strategy, it provides a stable, testable surface for consuming code while reacting gracefully to external scraping defenses. + +## Example +```csharp +var results = await duckDuckGo.SearchAsync("open source licenses", 10, cancellationToken); +``` + +## Notes +- This method may throw InvalidOperationException if both the html/ and lite/ endpoints return an anomaly/block page, signaling the caller to switch to a supported API-based approach. +- The limit parameter caps the number of results requested from the endpoints and influences parsing decisions; higher limits may incur more network usage. +- External factors (network conditions, bot protection, and endpoint availability) can cause the method to return an empty list even when relevant results exist. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/TagStripRegex.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/TagStripRegex.cs.md new file mode 100644 index 0000000..b68f436 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/TagStripRegex.cs.md @@ -0,0 +1,8 @@ +TagStripRegex is a precompiled regular expression field used to strip HTML-like tags from text within the DuckDuckGo web search component. The pattern <[^>]+> matches a '<' character, followed by one or more characters that are not '>', and a closing '>', enabling tag removal without incurring runtime regex compilation costs. + +## Remarks +TagStripRegex is private, static, and readonly, so a single instance is shared across the class and cannot be reassigned after initialization. This design minimizes allocations and improves performance in hot paths that perform repeated tag stripping against search results. Note that the pattern is intentionally simple and not a full HTML parser; for complex HTML handling, prefer a dedicated parser. + +## Notes +- The regex is simplistic and may not handle edge cases such as comments or CDATA sections, or angle brackets used in non-tag contexts. +- Because the field is compiled, changes require recompiling the assembly to take effect. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UnwrapRedirect.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UnwrapRedirect.cs.md new file mode 100644 index 0000000..f3ede4b --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UnwrapRedirect.cs.md @@ -0,0 +1,19 @@ +UnwrapRedirect extracts the true navigation target from a DuckDuckGo redirection wrapper. When a URL contains the uddg parameter (as in the /l/?uddg=ENCODED_URL&rut=... form), it decodes the embedded URL and returns it; if the wrapper is not present, it either prefixes https: for protocol-relative URLs (//example.com/...) or returns the original href unchanged. This helper is useful anytime you normalize or follow external links that may have been wrapped by a search proxy rather than using the literal href. + +## Remarks +By isolating this logic, the code centralizes the handling of DuckDuckGo's redirect wrapper, making link normalization safer wherever the application processes results from search pages. It also gracefully handles uncommon shapes of the wrapper and decoding failures by returning the original href, avoiding exceptions in the caller. This method assumes the use of a simple uddg wrapper and does not attempt to validate the decoded URL beyond decoding it. + +## Example +```csharp +// Common case: a DuckDuckGo redirect URL that wraps the real target +var wrapped = "https://duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.org%2Fpage%3Fq%3Dtest&rut=..."; +var real = UnwrapRedirect(wrapped); +// real == "https://example.org/page?q=test" + +// If there is no uddg parameter, the raw href is returned (or https: prefix is added for protocol-relative URLs) +``` + +## Notes +- Decoding errors are swallowed; if Uri.UnescapeDataString throws, the original href is returned. +- If href starts with "//" and there is no uddg, the URL is prefixed with https: +- Only the first occurrence of uddg is considered; subsequent occurrences are ignored. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UserAgents.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UserAgents.cs.md new file mode 100644 index 0000000..88880ca --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/UserAgents.cs.md @@ -0,0 +1,8 @@ +UserAgents is a small, hard-coded pool of real-browser User-Agent strings used to initialize the session's user agent fingerprint. It is not rotated per-request; a single UA is selected during session warmup and kept for the duration of the session, with the pool designed to spread fingerprints across deployments and restarts. + +## Remarks +Centralizes UA management to avoid per-request rotations that could trigger bot-detection while preserving fingerprint diversity across environments. By selecting a single UA at session warmup and keeping it for the session, it mirrors real browser behavior and reduces fingerprint noise within a session. The pool distributes fingerprints across deployments and restarts, preventing uniform UA usage across the system. + +## Notes +- Hard-coded UA strings may become outdated; update periodically to reflect current browser versions. +- The field is private and static readonly; it cannot be modified at runtime. If you need dynamic UA rotation, introduce a separate mechanism. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_httpFactory.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_httpFactory.cs.md new file mode 100644 index 0000000..c3145fd --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_httpFactory.cs.md @@ -0,0 +1,10 @@ +This field holds a reference to the IHttpClientFactory used to create HttpClient instances for outbound HTTP calls made by this class. It is private and readonly, set once in the constructor and never reassigned, which makes HTTP client creation predictable and easier to reason about in tests. In the DuckDuckGoWebSearch workflow, the factory is used to obtain configured HttpClient instances rather than instantiating HttpClient directly. + +## Remarks + +HttpClientFactory centralizes HttpClient lifetimes, configuration, and resilience policies, reducing socket exhaustion and enabling named or typed clients. Keeping a factory reference in this field helps decouple HTTP concerns from business logic and makes the class easier to test by allowing injection of different client configurations or mocks. + +## Notes + +- Always obtain HttpClient via _httpFactory.CreateClient(...) instead of new HttpClient(...) to ensure proper pooling and lifetime management. +- Prefer named or typed clients to reuse configuration; dispose of the returned HttpClient when finished with the operation. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_logger.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_logger.cs.md new file mode 100644 index 0000000..ca1bb57 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_logger.cs.md @@ -0,0 +1,14 @@ +The _logger field is a strongly-typed logger for the DuckDuckGoWebSearch class. It enables emitting structured log messages that provide observability into DuckDuckGoWebSearch operations, such as initiating requests or handling responses; the field being private and readonly signals that logging is an intrinsic, unchanging part of the class's behavior, typically wired in via constructor dependency injection. + +## Remarks +ILogger usage ties logs to the concrete class, which helps filter and correlate messages in diagnostics tools. By using dependency injection and a private readonly field, the class can emit meaningful diagnostics without introducing tight coupling to a concrete logging implementation. This abstraction supports testability, as a test can substitute a mock or in-memory logger to verify log emissions. It also encourages consistent logging practices across the codebase when used as a pattern for similar components. + +## Example +```csharp +_logger.LogInformation("Fetching results from DuckDuckGo for query '{Query}'", query); +``` + +## Notes +- Do not log sensitive user data or credentials; apply redaction if needed. +- Keep log messages lightweight and avoid expensive computations in message formatting. +- Use appropriate log levels (Information, Warning, Error) to reflect the significance of events. diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionLock.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionLock.cs.md new file mode 100644 index 0000000..390960c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionLock.cs.md @@ -0,0 +1,29 @@ +A private readonly SemaphoreSlim used to serialize the first-use initialization of per-session state on the HttpClientHandler (CookieContainer population and the committed User-Agent). It ensures only one thread performs the initial pre-warm for the current handler generation, while others await completion, avoiding duplicate work and race conditions during startup. + +## Remarks +This semaphore coordinates per-instance session initialization, preventing race conditions when warming the homepage and writing the User-Agent. It sits alongside the HttpClientHandler-based session state; by serializing the first-use path, it makes the initialization predictable and avoids redundant work across concurrent callers. + +## Example +```csharp +// Typical usage pattern +await _sessionLock.WaitAsync(); +try +{ + // assume some fields tracking initialization + if (!CookiesPrewarmedForCurrentSession) + { + // perform pre-warm: populate CookieContainer, set User-Agent + CookiesPrewarmedForCurrentSession = true; + // set UserAgent etc. + } +} +finally +{ + _sessionLock.Release(); +} +``` + +## Notes +- Always release the semaphore in a finally block to avoid deadlocks. +- Avoid holding the lock during long-running I/O; keep the critical section as small as possible and perform expensive work outside the guarded region if feasible. +- The field is per-instance; do not rely on it across handler generations or across different HttpClientHandler instances. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionUserAgent.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionUserAgent.cs.md new file mode 100644 index 0000000..d26177c --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionUserAgent.cs.md @@ -0,0 +1,9 @@ +Stores the User-Agent string for the current session. The field is nullable to indicate that it may not be initialized yet and will be populated lazily when first needed. By caching the User-Agent, subsequent HTTP requests made by the DuckDuckGo web search functionality can reuse the same header, avoiding repeated computation or retrieval. + +## Remarks +This field implements a lazy-cache pattern: compute or obtain the User-Agent once and reuse it for the lifetime of the owning object. It helps ensure consistent request headers and reduces overhead compared to recomputing the UA for every request. If the instance may be accessed from multiple threads, ensure that initialization is thread-safe to prevent race conditions. + +## Notes +- Nullability: the value may be null; code that reads it should either initialize it first or handle nulls gracefully. +- Thread-safety: lazy initialization, if used, should be synchronized to avoid race conditions. +- Mutability: changing the value after initial assignment can lead to inconsistent behavior across requests; prefer a single initialization point. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionWarmed.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionWarmed.cs.md new file mode 100644 index 0000000..d12d824 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch/_sessionWarmed.cs.md @@ -0,0 +1,28 @@ +# _sessionWarmed + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs` +> **Kind:** field + +```csharp +private bool _sessionWarmed +``` + + +A private boolean flag that indicates whether the DuckDuckGo web search session has been warmed up. It is used to guard one-time initialization logic so that the expensive warm-up is performed only once per instance, allowing subsequent operations to proceed under the assumption that the session is ready. + +## Remarks +This flag encapsulates the component's lifecycle state, isolating initialization concerns from normal operation. It helps avoid repeated initialization costs and keeps warm-up concerns localized to the class. If the class instance is long-lived and accessed concurrently, consider synchronization to avoid race conditions around first-time warm-up. + +## Example +```csharp +if (!_sessionWarmed) +{ + // perform one-time session warm-up + WarmUpSession(); + _sessionWarmed = true; +} +``` + +## Notes +- If multiple threads can reach the warm-up check concurrently, you may need synchronization to ensure the warm-up runs exactly once. +- Do not rely on this internal flag from outside the class; it represents internal state only and is not part of the public surface area. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch_overview.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch_overview.cs.md new file mode 100644 index 0000000..49d722e --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch_overview.cs.md @@ -0,0 +1,9 @@ +DuckDuckGoWebSearch is a no-API-key IWebSearch implementation that uses DuckDuckGo as a backend by posting the same search payload to two endpoints: the rich HTML interface (html.duckduckgo.com/html/) and, if necessary, the lightweight lite endpoint (lite.duckduckgo.com/lite/). It prefers the richer HTML results but gracefully falls back to lite when parsing yields no usable data, making it suitable for environments without a paid API. The class maintains a session-wide User-Agent and uses a semaphore to deduplicate concurrent first-use warmups, logging diagnostic details when anomalies are detected so failures are debuggable rather than silent. This design trades the reliability of a paid API for zero-quota convenience, with the caveat that HTML structure can change and the parsing is regex-driven, which can yield zero results on problematic responses. + +## Remarks +DuckDuckGoWebSearch acts as an abstraction layer around the two endpoints, encapsulating endpoint selection, session management, and basic anomaly handling behind the IWebSearch contract. It centralizes resilience concerns (fallback behavior and logging) so callers don't need to implement their own retry or parsing logic. By isolating this integration, the rest of the system can depend on a testable, consistent search backend without tying to a specific HTTP strategy or endpoint shape. + +## Notes +- Regex-based parsing makes results brittle to changes in DuckDuckGo's HTML; a change can yield zero results or mis-parsed data. +- The lite endpoint helps avoid aggressive bot flags but may still be rate-limited or blocked; consider a paid API for production use. +- This class relies on HttpClientFactory and a named client; ensure the DI container registers the appropriate client (e.g., DuckDuckGoSearch) to prevent misconfiguration. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs.md new file mode 100644 index 0000000..1f82afc --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs.md @@ -0,0 +1,36 @@ +# HttpUrlFetcher + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs` +> **Kind:** class + +```csharp +public sealed class HttpUrlFetcher : IUrlFetcher +``` + + +Fetches an HTTP(S) URL, returning a sanitized, size-capped text representation suitable for feeding into a model or other text-processing components. Use this implementation when you need a safe, predictable fetcher that defends against SSRF (rejecting non-public addresses and non-HTTP(S) schemes), enforces byte/character limits, and converts HTML pages into readable plain text rather than returning raw markup. + +## Remarks +HttpUrlFetcher centralizes three practical concerns for web-to-text ingestion: network safety, bounded reads, and HTML-to-text conversion. It obtains HttpClient instances from an IHttpClientFactory (client name "WebFetch"), enforces that URLs are absolute http/https, and calls an internal host-check to refuse hostnames that resolve to loopback, link-local, or RFC1918 ranges. Responses are accepted only when the upstream status is successful and the Content-Type is text-like; binary content is refused. + +To protect downstream consumers it performs a bounded read (MaxBytesToRead) from the response stream and then converts bytes to text using the declared charset with a UTF-8 fallback. HTML responses are cleaned (script/style/footer/nav removal, tag-stripping, entity decoding, whitespace collapse) and the final text is truncated at MaxContentChars with a visible "…[truncated]" marker. The returned UrlFetchResult contains the final URL, content type, cleaned content, a Truncated flag that indicates whether truncation occurred (either at the byte or character limit), and the cleaned content length. + +## Example +```csharp +// Typical usage within an application (DI or manual construction): +IHttpClientFactory httpFactory = /* obtain from DI */; +ILogger logger = /* obtain logger */; +var fetcher = new HttpUrlFetcher(httpFactory, logger); + +var result = await fetcher.FetchAsync("https://example.com/article", CancellationToken.None); +if (result.Truncated) +{ + // handle truncated content awareness +} +Console.WriteLine(result.Content); +``` + +## Notes +- The fetcher enforces SSRF protections: it requires absolute http/https URIs and will refuse hosts that resolve to local/private addresses (the internal AssertPublicHostAsync performs this check and may throw). +- Truncation is explicit: the Truncated flag is set if the byte-read cap or the character cap was hit, and character truncation appends "\n…[truncated]" to the returned Content. +- Charset handling uses the response's Content-Type charset when present; if decoding fails the implementation falls back to UTF-8, so callers should not assume the returned text exactly matches the original byte semantics for exotic encodings. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md new file mode 100644 index 0000000..321eb49 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md @@ -0,0 +1,20 @@ +# InstrumentedWebSearch + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs` +> **Kind:** class + +```csharp +public sealed class InstrumentedWebSearch : IWebSearch +``` + + +InstrumentedWebSearch is a decorator around IWebSearch that records a metric row for every call to SearchAsync. It wraps each provider when registered so per-provider telemetry is captured even in composite search setups, and it continues to support single-provider configurations. + +The payload contains the outcome, the query, the number of results, latency, and any error message. It uses a canonical system name in the form web_search.{provider} (lowercased) to ensure stable aggregation regardless of DI registration casing. On success, the outcome is "success" when results are returned, or "empty" if no results are produced. On failure, the outcome is "error" and the message from the thrown exception is recorded. Cancellations are not logged as failures; the exception is rethrown so the caller can handle it, while the decorator still emits a meaningful telemetry row for the attempted call. When recording a metric, the payload fields map to snake_case keys in the stored JSON (outcome, query, result_count, latency_ms, error_message). + +## Remarks +InstrumentedWebSearch cleanly separates telemetry concerns from search logic by implementing IWebSearch and delegating actual work to the wrapped provider. This keeps observability concerns centralized and consistent across providers, while preserving the existing error handling semantics (exceptions propagate to callers, and metrics are persisted regardless). The decorator pattern here enables rich operational insight without requiring changes to individual providers or to composite orchestration code. + +## Notes +- Cancellation is not treated as a provider failure; the operation is rethrown and no metric entry is created for the cancellation path. +- In error paths, the metric is recorded with CancellationToken.None to ensure the failure persists even if the original token is canceled, then the exception is rethrown to the caller. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md new file mode 100644 index 0000000..c9da0a6 --- /dev/null +++ b/docs/auriondocs/Code/src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md @@ -0,0 +1,19 @@ +# TavilyWebSearch + +> **File:** `src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs` +> **Kind:** class + +```csharp +public sealed class TavilyWebSearch : IWebSearch +``` + + +TavilyWebSearch implements IWebSearch and delegates search to Tavily’s API by POSTing a small JSON payload with a per-request Bearer token. The API returns an already-ranked list of results (title, url, content, score), where content is a snippet-sized field suitable for model consumption; the results are mapped to WebSearchResult objects for consumption by agents. + +## Remarks +TavilyWebSearch hides Tavily-specific HTTP details behind the IWebSearch interface, enabling swapping or testing alternate search providers without changing callers. It sends both the API key in the request body and a Bearer token in the Authorization header to support key rotation at runtime without restarting the application; by disabling raw content, images, and answers it preserves a compact payload and maintains per-result citation integrity for reasoning steps. + +## Notes +- The SearchAsync method throws an InvalidOperationException if Tavily is not configured; ensure TavilySearchOptions.IsConfigured is true or configure Tools:Web:Tavily:ApiKey. +- MaxResults is clamped to the range 1–20, so requests with higher limits will still return at most 20 results. +- The Authorization header is set per request, allowing API key rotation without restarting the host and avoiding unintended header persistence. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/App.tsx.md b/docs/auriondocs/Code/src/webapp/src/App.tsx.md new file mode 100644 index 0000000..86dda00 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/App.tsx.md @@ -0,0 +1,17 @@ +# App + +> **File:** `src/webapp/src/App.tsx` +> **Kind:** function + +```typescript +export function App() +``` + + +App is the root React component that bootstraps the UI by wrapping the entire app in the authentication context and rendering the router. Use App when you want to initialize the app with a global AuthProvider around the Router so all routes and components can access auth state and actions. + +## Remarks +By composing AuthProvider with RouterProvider, App centralizes the top-level providers and keeps the rest of the component tree focused on UI concerns. This design makes App the natural place to adjust global bootstrapping—such as adding other providers or tweaking the router configuration—while guaranteeing a consistent authentication context across the navigation graph. It acts as the single entry point for app-wide initialization and ensures components rendered via the router can rely on authentication data and actions. + +## Notes +- If the provider hierarchy is altered so AuthProvider is not the outermost wrapper, descendants that depend on auth context may not receive updates. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/api/authInterceptor.ts.md b/docs/auriondocs/Code/src/webapp/src/api/authInterceptor.ts.md new file mode 100644 index 0000000..4e2ebc8 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/api/authInterceptor.ts.md @@ -0,0 +1,31 @@ +# installAuthInterceptor + +> **File:** `src/webapp/src/api/authInterceptor.ts` +> **Kind:** function + +```typescript +export function installAuthInterceptor() +``` + + +Installs a global Axios response interceptor that automatically refreshes the authentication session when a 401 Unauthorized response is received, then retries the original request. It avoids refreshing during authentication calls to prevent a loop, and if the refresh cannot recover the session (or the request has already been retried once), it signals session expiry so the AuthContext can perform a logout cleanup. + +## Remarks +Centralizes the session refresh flow in one place, so individual API calls don't have to duplicate retry logic. It uses AUTH_PATHS to detect calls that target authentication endpoints, and it coordinates with refreshSession and signalSessionExpired to drive the client logout workflow. The per-request _retried flag prevents infinite retry loops, while a failed refresh leads to a clean logout by signaling session expiry. In multi-request scenarios, overlapping 401s may trigger concurrent refresh attempts unless a higher-level coordination is introduced; consider centralizing refresh state if that becomes a concern. + +## Example +```ts +// Initialize the interceptor once during app bootstrap +installAuthInterceptor(); + +// Any subsequent API call to a protected endpoint will automatically refresh +// the session on 401 and retry the original request when possible. +axios.get('/api/protected/resource') + .then(res => { /* handle success */ }) + .catch(err => { /* handle error, e.g. show login */ }); +``` + +## Notes +- Refreshing is attempted only for 401 responses that are not targeting authentication endpoints (to avoid refresh loops). Ensure AUTH_PATHS is properly configured to cover all token-refresh triggers. +- If refreshSession rejects or throws, the interceptor will surface the error; ensure you handle failures gracefully in your UI and state management. +- Multiple 401s arriving in parallel can trigger multiple refresh attempts; if this becomes undesirable, consider introducing a shared in-flight refresh mechanism or locking strategy. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/api/authRefresh.ts.md b/docs/auriondocs/Code/src/webapp/src/api/authRefresh.ts.md new file mode 100644 index 0000000..81abbf3 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/api/authRefresh.ts.md @@ -0,0 +1,91 @@ +# authRefresh.ts + +> **Source:** `src/webapp/src/api/authRefresh.ts` + +## Contents + +- [postRefresh](#postrefresh) +- [refreshSession](#refreshsession) +- [signalSessionExpired](#signalsessionexpired) + +--- + +## postRefresh +> **File:** `src/webapp/src/api/authRefresh.ts` +> **Kind:** function + +```typescript +async function postRefresh(): Promise +``` + +**Returns:** `Promise` + + +postRefresh is an asynchronous helper that triggers the server-side refresh-token flow by sending a POST to /api/auth/refresh with credentials included. It transmits a JSON body containing refreshToken: '' to satisfy the server's non-nullable body model while allowing the server to read the actual token from an HttpOnly cookie if present; the function resolves to true when the response status is OK (res.ok), and false if a network error occurs or the response is not OK. + +## Remarks +This function centralizes the client-side refresh behavior, exposing a simple boolean success signal to callers while hiding the HTTP details. It relies on the server’s cookie-based refresh flow: the HttpOnly cookie supplies the refresh token when available, and the body field is present merely to satisfy server-side validation when the cookie is used. By returning a boolean, callers can react to refresh success or failure without parsing response payloads. + +## Notes +- The request depends on cookies being available and the server allowing credentials; if cookies are blocked or CORS credentials are not permitted, the refresh will fail. +- The function swallows exceptions and returns false on error, so a false result should be interpreted as the refresh not yielding a new token. +- This helper is tightly coupled to the endpoint /api/auth/refresh and the server’s validation model; changes to the endpoint, cookie policy, or body shape could require corresponding adjustments to this code. + +--- + +## refreshSession +> **File:** `src/webapp/src/api/authRefresh.ts` +> **Kind:** function + +```typescript +export function refreshSession(): Promise +``` + +**Returns:** `Promise` + + +refreshSession returns a `Promise` and coordinates session refreshes so that a single in-flight refresh is shared among all callers. If a refresh is already in progress, subsequent calls reuse the same Promise; once the operation completes, the cache is cleared to allow future refreshes. + +## Remarks +This abstraction prevents multiple parallel requests to refresh the session, reducing redundant network calls and race conditions. It relies on a shared `refreshing` Promise and a final cleanup to reset the state, so callers only need to await the result rather than track concurrency themselves. The boolean result reflects the outcome from `postRefresh`, which this wrapper preserves for callers. + +## Example +```typescript +async function testRefreshCoalescing() { + const p1 = refreshSession(); + const p2 = refreshSession(); + const [r1, r2] = await Promise.all([p1, p2]); + // r1 and r2 are the same boolean outcome from the single refresh operation +} +``` + +## Notes +- This function relies on nullish coalescing assignment (??=) and Promise.finally; ensure your environment targets ES2021 or newer. +- If `postRefresh` rejects, the `finally` cleanup still clears the `refreshing` cache, allowing a retry on subsequent calls. +- The boolean return value comes from `postRefresh`; if you need a specific interpretation, rely on its contract rather than assuming a particular meaning of `true`/`false` here. + +--- + +## signalSessionExpired +> **File:** `src/webapp/src/api/authRefresh.ts` +> **Kind:** function + +```typescript +export function signalSessionExpired(): void +``` + +**Returns:** `void` + + +Signals that the user session has expired by dispatching a DOM Event named by SESSION_EXPIRED_EVENT. Use this function from your authentication/refresh flow when expiry is detected to notify any listeners in a decoupled way (e.g., redirect to login or prompt for re-authentication) without tying those reactions to the expiry-detection logic. + +## Remarks + +By isolating the event emission behind a single function, the codebase gains a single source of truth for signaling expiry. Listeners can subscribe to the SESSION_EXPIRED_EVENT on the global window to react in a centralized, decoupled fashion. + +## Notes + +- Relies on the browser's window and DOM Event API; in non-browser runtimes (e.g., server-side rendering or tests with a non-browser environment), window may be undefined. Guard or mock window in such contexts if needed. +- The emitted Event carries no payload; it is a simple, signal-only notification. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/api/conversationMode.ts.md b/docs/auriondocs/Code/src/webapp/src/api/conversationMode.ts.md new file mode 100644 index 0000000..3d4c06e --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/api/conversationMode.ts.md @@ -0,0 +1,141 @@ +# conversationMode.ts + +> **Source:** `src/webapp/src/api/conversationMode.ts` + +## Contents + +- [GabrielMode](#gabrielmode) +- [setConversationMode](#setconversationmode) +- [withRefresh](#withrefresh) + +--- + +## GabrielMode +> **File:** `src/webapp/src/api/conversationMode.ts` +> **Kind:** type alias + +```typescript +export type GabrielMode = 'chatty' | 'elaborative' | 'concise' | 'tutor' | 'critic' +``` + + +GabrielMode is a string literal union that defines the set of tonal styles Gabriel can adopt in its responses. Use GabrielMode to steer the bot's tone in a centralized way (e.g., 'concise' for brief answers or 'tutor' for guided instruction) rather than scattering tone-selection logic across components. + +## Remarks +GabrielMode acts as a contract between the UI and the response generator, enabling consistent behavior across parts of the system that render, log, or test Gabriel's replies. It makes it straightforward to add, remove, or rename modes in one place and have the rest of the system adapt via a switch or pattern-match. If the set of modes evolves, ensure the mode-handling paths are updated to reflect the new semantics. + +## Notes +- This is a TypeScript compile-time construct; at runtime there is no automatic enforcement of the union. If you receive input from users or external APIs, validate that the string matches one of the allowed literals before treating it as GabrielMode. +- Because GabrielMode is a union of literals, invalid assignments will be rejected by the TypeScript compiler. When values come from external data, always perform a guard/validation before using them as GabrielMode. + +--- + +## setConversationMode +> **File:** `src/webapp/src/api/conversationMode.ts` +> **Kind:** function + +```typescript +export async function setConversationMode( + conversationId: string, + mode: GabrielMode | null, + signal?: AbortSignal, +): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `conversationId` | `string` | — | +| `mode` | `GabrielMode | null` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +## Source Code + +The setConversationMode function centralizes the HTTP interaction required to update a conversation's mode. It performs a PUT request to the backend with the mode payload and relies on a refresh wrapper to handle authentication concerns, returning a resolved promise on success or throwing a descriptive error when the server responds with a non-OK status. Call this helper when you need to adjust a conversation's mode in a single, consistent API surface, rather than composing fetch calls in multiple places. Note that passing null for mode sends { mode: null } to the server, which in turn must interpret that as a clear or reset action. + +```typescript +export async function setConversationMode( + conversationId: string, + mode: GabrielMode | null, + signal?: AbortSignal, +): Promise { + const response = await withRefresh( + () => fetch(`/api/conversations/${encodeURIComponent(conversationId)}/mode`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ mode }), + signal, + }), + ); + if (!response.ok) { + const text = await response.text().catch(() => ''); + throw new Error(`Set conversation mode failed: ${response.status} ${text}`); + } +} +``` + +## Dependencies + +- GabrielMode +- AbortSignal +- Promise +- JSON +- Error + +## Dependency APIs (verified signatures) + +The REAL, parser-verified API surface of this symbol's collaborators: + +- enum `GabrielMode` (`src/api/Gabriel.Core/Entities/GabrielMode.cs`) +- AbortSignal (global DOM interface) +- Promise (global) +- JSON (global) +- Error (global) + +## Symbol To Document +- Name: `setConversationMode` +- Kind: function +- File: `src/webapp/src/api/conversationMode.ts` +- Language: typescript +- ID: e8b4a552-936f-47ba-b3b9-b91f385803cf + +--- + +## withRefresh +> **File:** `src/webapp/src/api/conversationMode.ts` +> **Kind:** function + +```typescript +async function withRefresh(doFetch: () => Promise): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `doFetch` | `() => Promise` | — | + +**Returns:** `Promise` + + +withRefresh orchestrates a fetch-like operation and transparently handles an expired session by attempting a refresh and retrying once. If the retry still yields 401, it signals session expiry and throws an error instructing the user to sign in again. + +## Remarks +Centralizes session refresh and retry logic for authenticated API calls. It decouples the refresh mechanics (refreshSession) from call-sites and provides a single, consistent error path when authentication cannot be restored. + +## Example +```typescript +// Basic usage: auto-refresh on 401 +const resp = await withRefresh(() => fetch('/api/conversations')); +``` + +## Notes +- It only handles the 401 path; the code will not retry on other statuses or errors from doFetch, and a thrown error from doFetch propagates. +- The second request is attempted only if refreshSession resolves to a truthy value. If the second call still results in 401, a session-expired error is thrown to prompt re-authentication. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/api/memories.ts.md b/docs/auriondocs/Code/src/webapp/src/api/memories.ts.md new file mode 100644 index 0000000..6ca7c63 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/api/memories.ts.md @@ -0,0 +1,284 @@ +# memories.ts + +> **Source:** `src/webapp/src/api/memories.ts` + +## Contents + +- [MemoryDto](#memorydto) +- [SaveMemoryRequest](#savememoryrequest) +- [MemoryScope](#memoryscope) +- [MemoryType](#memorytype) +- [deleteMemory](#deletememory) +- [listMemories](#listmemories) +- [saveMemory](#savememory) +- [urlFor](#urlfor) +- [withRefresh](#withrefresh) + +--- + +## MemoryDto +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** interface + +```typescript +export interface MemoryDto +``` + + +MemoryDto is the serializable shape used to transfer memory data between the client and server (e.g., when listing, creating, or updating memories). Use it whenever you need to read or write a memory payload from API responses or requests; the projectId being null signals a user-scoped memory, while a non-null value ties it to a project. + +## Remarks +MemoryDto serves as a boundary contract between the API layer and UI or business logic. It decouples transport data from domain models, allowing the server to evolve internal representations without breaking clients. The projectId field expresses memory scope, with null representing user-scoped memories and a string capturing project association. The ISO 8601 strings for createdAt/updatedAt ensure consistent temporal data across systems. + +## Notes +- projectId can be null; callers must handle both user-scoped and project-scoped memories. +- createdAt/updatedAt are ISO 8601 strings; consider time zone and parsing across environments. +- MemoryDto is an interface; you cannot instantiate it directly—provide a concrete object that matches the shape. + +--- + +## SaveMemoryRequest +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** interface + +```typescript +export interface SaveMemoryRequest +``` + + +Represents the payload required to save a memory entry in the system. It groups the memory’s metadata and content into a single object and enforces a consistent contract for persistence operations. The memory is associated with a project via projectId (which may be null to indicate no project), categorized by type using MemoryType, and described by a name, a brief description, and the body content. + +## Remarks + +DTO semantics: SaveMemoryRequest is a Data Transfer Object that decouples UI concerns from persistence details. It provides a stable boundary for API calls and helps centralize validation rules (e.g., required fields such as name and body) before reaching the backend. The MemoryType dependency signals how the memory should be treated, indexed, or surfaced by the UI and storage layer. + +## Notes + +- projectId is nullable; callers must handle the null case when saving a memory. +- The body field is a string; consider any backend length constraints and ensure input sanitation if the content will be rendered in the UI or stored in a text field. + +--- + +## MemoryScope +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** type alias + +```typescript +export type MemoryScope = + | { kind: 'user' } + | { kind: 'project'; projectId: string } + | { kind: 'all'; projectId?: string }; +``` + + +MemoryScope defines the subset of memories an API should operate on. It’s a discriminated union over three scopes: user, project, and all. Use MemoryScope instead of ad-hoc objects to get type-safe branching and clear intent when calling memory-related APIs. + +## Remarks +The "kind" field is the discriminant; each variant encodes its required data: 'user' has no extra data, 'project' requires a projectId, 'all' may include an optional projectId. + +In consumer code, you typically switch on scope.kind and handle 'user', 'project', and 'all' distinctly; the TypeScript compiler helps ensure you cover all cases. + +The 'all' variant's projectId?: string supports contexts where you want the global scope or optionally refine to a specific project in the same branch. + +## Example +```typescript +const scopeUser: MemoryScope = { kind: 'user' }; +const scopeProject: MemoryScope = { kind: 'project', projectId: 'proj-42' }; +const scopeAllGlobal: MemoryScope = { kind: 'all' }; +const scopeAllWithProject: MemoryScope = { kind: 'all', projectId: 'proj-99' }; +``` + +## Notes +- When consuming MemoryScope in a switch, cover all cases to get exhaustive checks, e.g., using a final 'never' check. +- Be mindful that the 'all' variant allows an optional projectId; APIs that require a projectId should not rely on presence of that field in 'all' without checking. + +--- + +## MemoryType +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** type alias + +```typescript +export type MemoryType = 'user' | 'feedback' | 'project' | 'reference' +``` + + +MemoryType is a TypeScript type alias that enumerates the categories a memory entity can belong to in the application. It restricts values to the four string literals: 'user', 'feedback', 'project', and 'reference'. This provides a compile-time contract for memory data, enabling safe tagging, filtering, and rendering based on memory kind without incurring runtime overhead. + +## Remarks +Using a literal union type keeps runtime overhead minimal while offering strong typing across memory-related APIs. It centralizes the domain concept of what a memory can be, allowing components to branch, format, and validate behavior consistently according to memory kind. + +## Example +```typescript +function describeMemoryType(type: MemoryType): string { + switch (type) { + case 'user': + return 'User-generated memory'; + case 'feedback': + return 'Feedback about a memory'; + case 'project': + return 'Project-associated memory'; + case 'reference': + return 'Reference material'; + } +} +``` + +## Notes +- MemoryType is a type-level construct; at runtime it is simply a string. Validate external data before assigning to a MemoryType-typed field. +- If you extend MemoryType with new literals, remember to update all consuming logic (e.g., switches) to maintain exhaustiveness. + +--- + +## deleteMemory +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** function + +```typescript +export async function deleteMemory(id: string, signal?: AbortSignal): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `id` | `string` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +Deletes a memory resource by ID using an HTTP DELETE request. It wraps the fetch call with a helper (withRefresh) to accommodate authentication refresh scenarios and includes credentials for session-based access. The memory ID is URL-encoded to avoid path issues, and the operation supports cancellation via an AbortSignal. If the server responds with 404, the function treats this as a non-error (the memory may already be gone); for any other non-success status, it throws an Error that includes the status code and text. Use this helper when you need a straightforward, consistent deletion of a memory by its identifier from the server, rather than composing the fetch logic yourself. + +--- + +## listMemories +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** function + +```typescript +export async function listMemories(scope: MemoryScope, signal?: AbortSignal): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `scope` | `MemoryScope` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +Fetches a list of MemoryDto records for a given MemoryScope by issuing an authenticated HTTP request. It delegates the network call to withRefresh, which implies a refresh flow may run prior to the actual fetch. The request is performed with credentials: 'include' and supports cancellation via an optional AbortSignal. If the server responds with a non-OK status, the function throws an Error containing the HTTP status and status text; on success, it returns the response parsed as MemoryDto[]. + +## Remarks +This function centralizes the retrieval of memories scoped by MemoryScope, abstracting away the details of the underlying HTTP call and error handling from callers. The use of withRefresh suggests that token or session refresh logic is applied before the request, keeping callers focused on data retrieval rather than authentication lifecycle. The AbortSignal parameter enables cancellation, which is useful for component unmounts or user-initiated cancellations. + +## Notes +- The returned value is a plain JSON interpretation cast to MemoryDto[]. If the server returns data that does not conform to MemoryDto, the cast may be unsafe at runtime. Consider adding runtime validation if strict guarantees are required. +- The request includes credentials, so cookies or other credentials are sent; ensure the server is configured for credentialed requests (CORS with credentials allowed). +- Aborting the request will reject the promise (likely with an AbortError); callers should handle cancellation accordingly. +- The exact behavior of withRefresh is implementation-defined; callers should not rely on specific token refresh timing beyond the fact that a refresh may occur before the fetch. + +--- + +## saveMemory +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** function + +```typescript +export async function saveMemory( + request: SaveMemoryRequest, + signal?: AbortSignal, +): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `request` | `SaveMemoryRequest` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +Saves a memory by sending a POST request to /api/memories with a JSON body derived from SaveMemoryRequest, returning the created MemoryDto. It is the canonical helper to persist memory data to the server instead of constructing fetch calls ad-hoc, and it handles JSON serialization, credentialed requests, and cancellation support via an optional AbortSignal. + +The internal withRefresh wrapper ensures authentication is refreshed as needed before the request, providing a smoother experience for callers who would otherwise implement token refresh logic themselves. On non-OK HTTP responses, the function reads any response text and throws an Error that includes the status code and server message. + +## Remarks +By encapsulating the HTTP interaction, this symbol enforces a stable contract for saving memories and reduces boilerplate at call sites. It also centralizes error semantics, surfacing a consistent Error message that combines the HTTP status with server-provided details when available. If the Memories API changes (path, payload shape, or success type), updating this single function keeps the rest of the UI code insulated from those changes. + +## Notes +- The AbortSignal parameter can cancel the request; callers should handle cancellation errors as part of their control flow. +- The response is parsed as MemoryDto; if the server returns non-JSON or a mismatched shape, a runtime error may occur at parse time. +- The request uses credentials: 'include'; ensure the backend supports cookies or other credentials for this endpoint. + +--- + +## urlFor +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** function + +```typescript +function urlFor(scope: MemoryScope): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `scope` | `MemoryScope` | — | + +**Returns:** `string` + + +Constructs the canonical API endpoint URL for listing memories filtered by a given MemoryScope. It encodes the scope's kind and optional projectId into a query string and returns the full path to fetch memories from the backend. Use this helper instead of composing the URL manually to ensure consistent parameter names and encoding. + +## Remarks +This helper centralizes how memory queries are requested from the client, ensuring uniform semantics across the UI. It relies on URLSearchParams to handle proper encoding and only adds scope-related parameters when they are present. If future scope options are added, this function is the single place to update the URL shape. It also ensures the base endpoint '/api/memories' is returned when no query parameters are necessary. + +## Example +```ts +urlFor({ kind: 'all' }) // '/api/memories?scope=all' +urlFor({ kind: 'all', projectId: '123' }) // '/api/memories?scope=all&projectId=123' +urlFor({ kind: 'project', projectId: '123' }) // '/api/memories?projectId=123' +``` + +--- + +## withRefresh +> **File:** `src/webapp/src/api/memories.ts` +> **Kind:** function + +```typescript +async function withRefresh(doFetch: () => Promise): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `doFetch` | `() => Promise` | — | + +**Returns:** `Promise` + + +withRefresh wraps a fetch-like operation and automatically handles a one-time re-authentication flow. It executes the provided doFetch, and if it returns 401, it tries to refresh the session via refreshSession; on success it retries the fetch. If the retried response is still 401, it signals session expiration and throws an error prompting the user to sign in again. Use this when you want a single, centralized retry-on-unauthorized pattern around authenticated API calls. + +## Remarks +This function centralizes the common "retry after refresh" pattern, preventing duplication across call sites. It ties together refresh, retry, and session expiration signaling; callers don't need to manage token refresh logic themselves. It assumes a boolean return from refreshSession indicates whether a refresh occurred and that signalSessionExpired will produce a user-visible session timeout flow. + +## Example +```typescript +// Example: fetch a protected resource with effortless session refresh +const res = await withRefresh(() => fetch('/api/memories')); +``` + +## Notes +- If the initial doFetch fails with 401 and the refresh fails, the function will throw; you should handle errors around withRefresh to surface to the user or redirect. +- It only performs a single refresh attempt; multiple attempts or backoff are not implemented. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/api/models.ts.md b/docs/auriondocs/Code/src/webapp/src/api/models.ts.md new file mode 100644 index 0000000..621291c --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/api/models.ts.md @@ -0,0 +1,200 @@ +# models.ts + +> **Source:** `src/webapp/src/api/models.ts` + +## Contents + +- [ModelDto](#modeldto) +- [ModelsResponse](#modelsresponse) +- [SelectedModelDto](#selectedmodeldto) +- [SetActiveModelRequest](#setactivemodelrequest) +- [fetchModels](#fetchmodels) +- [setActiveModel](#setactivemodel) +- [withRefresh](#withrefresh) + +--- + +## ModelDto +> **File:** `src/webapp/src/api/models.ts` +> **Kind:** interface + +```typescript +export interface ModelDto +``` + + +ModelDto is a lightweight data transfer object that captures a single model's identity, cost parameters, and UI state for selection. Use it when listing available models, transmitting per-model configuration, or persisting a choice between client and server layers without pulling in broader domain logic. + +## Remarks +ModelDto isolates model identity, economics, and selection state from the rest of the system. A null compactThreshold inherits the global compact policy from AgentOptions, while a numeric value imposes a per-model fraction of the context window for compaction. The price fields encode per-million-token costs for inputs, outputs, and cache operations, enabling simple budgeting per model. The isDefault and isSelected flags drive UI defaults and current user choice. + +## Notes +- Be explicit about compactThreshold: null means inheritance; do not omit the field. +- Ensure consistent use of isDefault and isSelected across the model list to avoid conflicting UI states. +- Serialization: keep null vs undefined distinct to preserve inheritance semantics when sending ModelDto across processes. + +--- + +## ModelsResponse +> **File:** `src/webapp/src/api/models.ts` +> **Kind:** interface + +```typescript +export interface ModelsResponse +``` + + +ModelsResponse is a lightweight interface that models the payload returned by the models API. It carries two related pieces of data: availableModels, an array of ModelDto objects describing each available model; and selected, a SelectedModelDto representing the model currently chosen. This shape lets callers fetch both the list of options and the active selection in a single response, which is convenient for populating UI controls and maintaining selection state. + +## Remarks +This interface acts as a compact boundary between backend data and frontend presentation. By pairing the options list with the selected item, it reduces the number of roundtrips needed for a model picker and eliminates the need for separate lookups to determine the current choice. The two collaborator DTOs—ModelDto and SelectedModelDto—keep model metadata and selection state distinct while still composing a coherent view of the model domain. + +--- + +## SelectedModelDto +> **File:** `src/webapp/src/api/models.ts` +> **Kind:** interface + +```typescript +export interface SelectedModelDto +``` + + +SelectedModelDto is a lightweight data transfer object that identifies a model chosen from a provider by its provider identifier and model name. It is used when API boundaries require a single, stable payload to convey which model should be used. + +## Remarks +By separating provider and model name, this interface enables generic handling of models from multiple providers without coupling to any specific provider API. It acts as a simple contract that supports routing, logging, and auditing decisions based on the selected model. The shape is resilient to provider-specific changes, so client-side code can stay stable while backends evolve the available models. + +--- + +## SetActiveModelRequest +> **File:** `src/webapp/src/api/models.ts` +> **Kind:** interface + +```typescript +export interface SetActiveModelRequest +``` + + +SetActiveModelRequest is a small data contract used to request changing the active model by supplying a provider and a model name. Both fields are nullable, so callers can omit or explicitly set a value to express their intent. + +## Remarks + +This interface defines the boundary between the client API and the server-side active-model management. It decouples the API surface from internal domain types, enabling the client to convey intent without exposing implementation details. The nullable properties support partial updates and clear intent when only one aspect (provider or model name) needs to be changed. + +## Notes + +- Nullable fields mean you must validate on the server and define how nulls are interpreted (no-op, reset, or error). +- Ensure that a meaningful combination of provider and name is validated against known models; otherwise the request may fail during backend validation. +- Be consistent with other API models that use nullable strings to represent optional fields. + +--- + +## fetchModels +> **File:** `src/webapp/src/api/models.ts` +> **Kind:** function + +```typescript +export async function fetchModels(signal?: AbortSignal): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +Fetches the list of available models from the backend by calling /api/models and returning it as a ModelsResponse. It delegates the actual HTTP work to withRefresh, ensuring a refreshed session before issuing the request. The function supports cancellation via an optional AbortSignal; if provided, the request will be aborted according to the signal. On success, it parses the response body as JSON and returns it; if the HTTP response indicates failure (non-OK status), it throws an Error that includes the HTTP status. + +## Remarks +Consolidates the API surface for retrieving models in one place, centralizing error handling and session refresh logic via withRefresh. The use of an AbortSignal enables responsive UIs to cancel in-flight requests when a component unmounts or a user navigates away. The return type ModelsResponse provides a typed contract for downstream processing, reducing the risk of misinterpreting the payload. + +## Example +```typescript +// Example usage: cancel pending fetch with AbortController +async function loadModelsWithTimeout() { + const controller = new AbortController(); + // Cancel after 5 seconds to demonstrate abort behavior + const timer = setTimeout(() => controller.abort(), 5000); + try { + const models = await fetchModels(controller.signal); + console.log('Retrieved models:', models); + return models; + } catch (err) { + if (err && (err as any).name === 'AbortError') { + console.log('FetchModels request was aborted'); + } else { + throw err; + } + } finally { + clearTimeout(timer); + } +} +``` + +## Notes +- Aborting the request will reject the returned promise with an AbortError; callers should be prepared to catch and handle this scenario. +- The function forwards the AbortSignal to the underlying fetch call, so cancellation behavior depends on the environment's fetch implementation. +- It uses credentials: 'include', which means cookies or other credentials are sent with the request; ensure the backend CORS configuration allows this. + + +--- + +## setActiveModel +> **File:** `src/webapp/src/api/models.ts` +> **Kind:** function + +```typescript +export async function setActiveModel( + request: SetActiveModelRequest, + signal?: AbortSignal, +): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `request` | `SetActiveModelRequest` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +Updates the active model on the server by sending a PUT request to /api/models/active with the provided SetActiveModelRequest payload. The operation is wrapped by withRefresh to ensure any necessary authentication refresh happens before the request, and it supports cancellation via an AbortSignal passed through to fetch. On success, it returns the server’s ModelsResponse; on failure it throws a descriptive error that includes the HTTP status and any response text. + +## Remarks +Centralizes the HTTP interaction for model activation and provides consistent error handling for callers. It relies on the withRefresh helper to keep authentication current and uses credentials: 'include' to permit cookie-based sessions. By accepting an AbortSignal, callers can cancel in-flight requests to avoid UI stalls during navigation or shutdown. + +## Notes +- If the server returns a non-2xx status, the function includes the status code and any textual body in the thrown Error; if the body cannot be read, the text portion defaults to an empty string. +- The function casts the successful response body to ModelsResponse; ensure the server response conforms to that shape to avoid runtime type issues. + +--- + +## withRefresh +> **File:** `src/webapp/src/api/models.ts` +> **Kind:** function + +```typescript +async function withRefresh(doFetch: () => Promise): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `doFetch` | `() => Promise` | — | + +**Returns:** `Promise` + + +withRefresh is an async wrapper that runs a provided fetch function and, if the response is 401, attempts to refresh the session and retry once. If the retry still returns 401, it signals that the session has expired and throws an error prompting the user to sign in again. Developers would reach for it to automatically recover from expired authentication on API calls, centralizing 401 handling and session refresh logic. + +## Remarks +This wrapper encapsulates the authenticated request flow: execute the call, refresh on unauthorized, retry, and fail-fast if the session cannot be refreshed, promoting a consistent, centralized approach to session management. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/api/sequence.ts.md b/docs/auriondocs/Code/src/webapp/src/api/sequence.ts.md new file mode 100644 index 0000000..0d7deb0 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/api/sequence.ts.md @@ -0,0 +1,187 @@ +# sequence.ts + +> **Source:** `src/webapp/src/api/sequence.ts` + +## Contents + +- [GabrielSequence](#gabrielsequence) +- [GabrielSequenceMetadata](#gabrielsequencemetadata) +- [SequenceSource](#sequencesource) +- [doFetch](#dofetch) +- [fetchGabrielSequence](#fetchgabrielsequence) +- [urlFor](#urlfor) + +--- + +## GabrielSequence +> **File:** `src/webapp/src/api/sequence.ts` +> **Kind:** interface + +```typescript +export interface GabrielSequence +``` + + +GabrielSequence describes the data structure for a Gabriel animation sequence used by the rendering pipeline; you would create or consume this object whenever you need to package or read the sequence's color palette, per-frame indices, version, and associated metadata in a single, strongly typed container. + +## Remarks + +By separating the color palette from the per-frame indices, this shape enables efficient storage and reuse of a single palette across all frames. Each frame is represented as a 256-length array of palette indices, allowing light-weight, index-based pixel data that can be mapped to colors via the palette. The metadata field (GabrielSequenceMetadata) carries additional descriptors about the sequence, decoupling presentation concerns from the raw color-data payload. The interface acts as a cohesive contract that ties together versioning, color data, per-frame references, and descriptive metadata for downstream rendering or processing. + +## Notes + +- The interface does not itself enforce runtime invariants (for example, that frames.length === 64 or that every frame has length 256); callers should validate shapes before processing. + +--- + +## GabrielSequenceMetadata +> **File:** `src/webapp/src/api/sequence.ts` +> **Kind:** interface + +```typescript +export interface GabrielSequenceMetadata +``` + + +GabrielSequenceMetadata is a small data contract that describes the provenance of a Gabriel sequence. It exposes the numeric seed used to initialise generation, the exact time of generation as an ISO 8601 string, and an optional textual snapshot of the generator's state. + +## Remarks +This interface separates concerns between sequence generation and its consumption by clients or APIs, allowing callers to display provenance or reproduce results by reusing the same seed. It also makes the generatedAt timestamp explicit and serialisable, avoiding runtime Date objects in payloads and ensuring consistent cross-system logging. + +## Example +```typescript +const meta: GabrielSequenceMetadata = { + seed: 12345, + generatedAt: "2024-11-05T14:23:45Z", + stateSummary: "initialized with seed 12345" +}; +``` + +## Notes +- The seed is a numeric value used to initialise the sequence; using the same seed should yield the same sequence from the generator (assuming deterministic behaviour). +- generatedAt must be a valid ISO 8601 timestamp; using a UTC timestamp (ending with Z) avoids timezone misalignment. +- stateSummary is optional and may be null if no human-readable state is available. + +--- + +## SequenceSource +> **File:** `src/webapp/src/api/sequence.ts` +> **Kind:** type alias + +```typescript +export type SequenceSource = + | { kind: 'conversation'; conversationId: string } + | { kind: 'project'; projectId: string }; +``` + + +SequenceSource is a discriminated union that encodes the origin of a sequence. It has two variants: a conversation variant carrying a conversationId, and a project variant carrying a projectId. This type lets API bodies or UI logic accept a single source value while preserving a strong separation of concerns through the kind discriminator. + +## Remarks +This abstraction centralizes the concept of a sequence's origin, enabling safe branching and consistent handling across components that consume or produce sequence-related data. By switching on kind, you can reliably access the corresponding id (conversationId for kind: 'conversation', or projectId for kind: 'project'), which helps keep payloads aligned and reduces the chance of mixing identifiers from different contexts. If new origins are added in the future, the union can be extended without scattering origin-specific fields across call sites. + +## Notes +- Always narrow by kind before reading the id: if source.kind === 'conversation', read source.conversationId; if source.kind === 'project', read source.projectId. +- Treat the sequence source as a single payload wrapper: its shape enforces that only the relevant id field is used for a given origin, aiding serialization and validation. + +--- + +## doFetch +> **File:** `src/webapp/src/api/sequence.ts` +> **Kind:** function + +```typescript +function doFetch(source: SequenceSource, signal?: AbortSignal): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `source` | `SequenceSource` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +doFetch constructs a fetch call for the provided SequenceSource by deriving the endpoint with urlFor(source) and issuing a request with credentials included. It returns a `Promise` and supports cancellation via the optional AbortSignal. + +## Remarks +This small wrapper centralizes the common HTTP pattern used to retrieve sequence data, so callers don't need to know how the URL is formed or the credentials policy. By encapsulating the fetch invocation, it becomes easier to swap in a different transport (e.g., a test double) or add shared behavior (like global error handling or response normalization) in one place. It also communicates intent clearly: this is the standard path for obtaining a sequence resource from the backend. + +## Notes +- Using credentials: 'include' means cookies and HTTP authentication data are sent; ensure the server's CORS policy allows credentials. +- AbortSignal support allows callers to cancel in-flight requests; callers should handle DOMException when aborted. +- urlFor(source) must produce a valid URL; miswiring can cause runtime errors; validate inputs at call sites. + +--- + +## fetchGabrielSequence +> **File:** `src/webapp/src/api/sequence.ts` +> **Kind:** function + +```typescript +export async function fetchGabrielSequence( + source: SequenceSource, + signal?: AbortSignal, +): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `source` | `SequenceSource` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +FetchGabrielSequence retrieves a GabrielSequence for the specified SequenceSource. If the initial request returns 401, it triggers a session refresh and retries once before failing with a session-expired error; for other HTTP failures it throws a descriptive error, and on success it returns the parsed GabrielSequence. + +## Remarks +This function acts as a focused wrapper around the underlying fetch logic, centralizing authentication handling and error semantics for sequence retrieval. By encapsulating the refresh-and-retry pattern, it keeps callers free from boilerplate and ensures a consistent user experience when sessions expire. It coordinates with refreshSession and signalSessionExpired to maintain a single, predictable flow for expired credentials. + +## Notes +- It retries only once after a 401; subsequent 401 triggers a SessionExpired signal and a thrown error. +- Non-OK responses (e.g., 500) produce a generic fetch failure error that includes the status code and status text. +- If the service returns invalid JSON for GabrielSequence, parsing will throw a runtime error. + +--- + +## urlFor +> **File:** `src/webapp/src/api/sequence.ts` +> **Kind:** function + +```typescript +function urlFor(source: SequenceSource): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `source` | `SequenceSource` | — | + +**Returns:** `string` + + +Constructs the correct API URL for a SequenceSource by inspecting its kind. It returns a conversation sequence URL when the source represents a conversation, otherwise it returns the project sequence URL. + +## Remarks +Pure and stateless, urlFor centralizes endpoint URL construction to avoid duplicating string literals across the codebase. It relies on a discriminated union (SequenceSource) to choose the appropriate path and uses encodeURIComponent to safely encode IDs in the URL. + +## Example +```typescript +// Example usage demonstrating both branches +const convoSrc: SequenceSource = { kind: 'conversation', conversationId: '123/abc' }; +urlFor(convoSrc); // "/api/conversations/123%2Fabc/sequence" + +const projSrc: SequenceSource = { kind: 'project', projectId: 'proj-42' }; +urlFor(projSrc); // "/api/projects/proj-42/sequence" +``` + +## Notes +- If a new SequenceSource kind is added, this function will need to be updated to handle the new path. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/api/streamChat.ts.md b/docs/auriondocs/Code/src/webapp/src/api/streamChat.ts.md new file mode 100644 index 0000000..6c07787 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/api/streamChat.ts.md @@ -0,0 +1,241 @@ +# streamChat.ts + +> **Source:** `src/webapp/src/api/streamChat.ts` + +## Contents + +- [StreamChatOptions](#streamchatoptions) +- [AgentEvent](#agentevent) +- [doFetch](#dofetch) +- [streamChat](#streamchat) +- [streamRegenerate](#streamregenerate) +- [streamSse](#streamsse) + +--- + +## StreamChatOptions +> **File:** `src/webapp/src/api/streamChat.ts` +> **Kind:** interface + +```typescript +export interface StreamChatOptions +``` + + +StreamChatOptions defines optional configuration for streaming chat calls. By supplying a signal, callers can cancel an in-progress stream using an AbortSignal instead of managing custom cancellation logic. + +## Remarks +This option follows the standard AbortSignal cancellation pattern used widely in web APIs. It decouples cancellation from the operation's implementation, allowing consumers to initiate cancellation from their own controllers. Because the interface is minimal (only signal), it keeps the surface area small while enabling cooperative cancellation across collaborators that participate in the streaming process. + +--- + +## AgentEvent +> **File:** `src/webapp/src/api/streamChat.ts` +> **Kind:** type alias + +```typescript +export type AgentEvent = + | { type: 'userMessagePersisted'; messageId: string } + | { type: 'textDelta'; delta: string } + | { type: 'reasoningDelta'; delta: string } + | { type: 'toolCall'; messageId: string; toolCallId: string; name: string; argumentsJson: string } + | { type: 'toolResult'; messageId: string; toolCallId: string; content: string } + | { type: 'assistantMessage'; messageId: string; content: string; reasoningContent?: string | null } + | { type: 'compactStart'; messageCount: number; currentTokens: number; thresholdTokens: number } + | { type: 'compactDone'; messageCount: number; summaryTokens: number } + | { type: 'error'; message: string } + | { type: 'done' }; +``` + + +AgentEvent is a discriminated union that models all streaming events emitted during a send-driven chat turn. It encompasses persistence, incremental updates, tool interactions, assistant content, and lifecycle signals that drive the client UI in real time. + +## Remarks + +These events provide a single, typed stream that decouples the UI from the underlying streaming protocol. They include: persisting the user message (where the real DB id is exposed as messageId to swap optimistic UI ids), delta updates (textDelta and reasoningDelta), tool invocations and results (toolCall and toolResult), and the lifecycle signals for compaction (compactStart and compactDone) as well as error and completion (error, done). The compact lifecycle enables the UI to show a "Compacting…" overlay during the rolling summary, and to clear it reliably on completion or failure. + +## Example + +```typescript +function handleEvent(e: AgentEvent) { + switch (e.type) { + case 'userMessagePersisted': + // e.messageId is the real persisted ID; replace tmp UI id + break; + case 'textDelta': + // apply e.delta to the current text + break; + case 'reasoningDelta': + // apply e.delta to the reasoning portion + break; + case 'toolCall': + // begin tool invocation identified by e.toolCallId + break; + case 'toolResult': + // render tool result content + break; + case 'assistantMessage': + // display assistant content, including optional reasoningContent + break; + case 'compactStart': + // show "Compacting…" overlay with counts + break; + case 'compactDone': + // update summary and hide overlay + break; + case 'error': + // surface error.message + break; + case 'done': + // stream finished + break; + } +} +``` + +## Notes + +- Multiple events (for example, textDelta and reasoningDelta) can arrive for the same turn; the UI should apply updates in a way that preserves overall coherence. +- toolCallId in toolCall and toolResult must be used to correlate requests with their corresponding results; mismatched IDs should be treated as out-of-band data. +- argumentsJson is carried as an opaque JSON string; decode or pass through as needed without assuming a particular shape unless you know the schema from your tool definition. + +--- + +## doFetch +> **File:** `src/webapp/src/api/streamChat.ts` +> **Kind:** function + +```typescript +function doFetch(url: string, body: unknown, signal?: AbortSignal): Promise +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `url` | `string` | — | +| `body` | `unknown` | — | +| `signal` | `AbortSignal` | — | + +**Returns:** `Promise` + + +doFetch is a small fetch wrapper that issues a POST request to the provided URL and returns the resulting Response promise. It always includes credentials to ensure cookies are sent across origins, and it only sets Content-Type when a body is actually sent, JSON-stringifying the body when present. The function also sets Accept to text/event-stream to align with endpoints that stream data (SSE). Call it when you need to post data and begin consuming a streaming response while keeping the user’s cookies in sync with the API. + +## Remarks +doFetch centralizes a couple of cross-cutting concerns: cookie propagation across origins and the streaming-oriented Accept header, so callers don’t have to repeat these defaults. It also encapsulates the conditional body handling in one place, reducing the risk of mismatched headers and bodies across the codebase. + +## Example +```typescript +const controller = new AbortController(); + +doFetch('/api/streamChat', { room: 'general', since: 0 }, controller.signal) + .then(res => { + if (!res.ok) throw new Error(`Request failed with status ${res.status}`); + // For streaming endpoints, you typically read from res.body or use EventSource on a separate URL. + console.log('Content-Type:', res.headers.get('Content-Type')); + }) + .catch(err => { + if (err.name === 'AbortError') { + console.log('Request aborted'); + } else { + console.error(err); + } + }); + +// Cancel after 5 seconds as an example +setTimeout(() => controller.abort(), 5000); +``` + +## Notes +- Passing undefined as the body can inadvertently cause a Content-Type header to be sent without an actual payload; pass null to indicate no body. +- Aborting the request throws an AbortError; ensure callers handle this case if cancellation is part of the UX. +- This is a thin wrapper around fetch; to actually consume streaming data you’ll typically read from response.body or switch to a streaming/event mechanism on the caller side. + +--- + +## streamChat +> **File:** `src/webapp/src/api/streamChat.ts` +> **Kind:** function + +```typescript +export function streamChat( + conversationId: string, + content: string, + opts: StreamChatOptions +``` + + +Streams chat content into a running conversation, identified by conversationId, by delivering the provided content chunk through a streaming channel. Use this when you want incremental delivery of messages (for example, rendering a bot response as it's produced) rather than waiting for the full message before displaying it. The opts parameter accepts a StreamChatOptions object to tailor the streaming behavior (such as lifecycle events, error handling, or progress callbacks) without changing the call site; this keeps the UI code clean and decoupled from transport details. + +## Remarks +This symbol acts as a streaming primitive that decouples transport concerns from chat composition. It coordinates with a conversation-scoped stream and with the consumer of the stream to present content as it arrives. By taking content as a separate parameter and exposing a configurable opts object, it supports flexible streaming strategies—from streaming partial chunks of a reply to delivering live updates in a chat interface. + +## Notes +- When multiple streams share the same conversationId, ensure you preserve the order of chunks to avoid interleaved content. +- Cancel/clean up the stream when the consumer is finished to prevent resource leaks and unnecessary network activity. + +--- + +## streamRegenerate +> **File:** `src/webapp/src/api/streamChat.ts` +> **Kind:** function + +```typescript +export function streamRegenerate( + conversationId: string, + messageId: string, + opts: StreamChatOptions +``` + + +streamRegenerate initiates a regeneration operation for the streaming updates associated with a specific message in a given conversation. It requires the conversationId and messageId to identify the target, and accepts an optional StreamChatOptions to tailor the regeneration behavior. Call this when you need to refresh or recover the live stream for a message, for example after a transient error or to re-sync the stream in the UI. + +## Remarks + +streamRegenerate acts as a focused abstraction around the regeneration workflow for a single message within a conversation. By capturing the target identifiers (conversationId and messageId) and the optional configuration, it decouples UI concerns from the underlying streaming infrastructure and enables consistent retry or refresh semantics across components that subscribe to the chat stream. + +## Notes + +- The opts parameter defaults to an empty object, so callers may omit it entirely. +- IDs are strings; ensure they come from trusted sources to avoid mis-targeting messages. +- Return type and side effects depend on the implementation; this documentation does not expose the return value. + +--- + +## streamSse +> **File:** `src/webapp/src/api/streamChat.ts` +> **Kind:** function + +```typescript +async function* streamSse( + url: string, + body: unknown, + opts: StreamChatOptions +``` + + +streamSse is an asynchronous generator that initiates a server-sent events (SSE) stream from the specified URL, sending the provided body and applying the optional StreamChatOptions. It yields each incoming event as soon as it arrives, letting consumers process updates incrementally with a for-await-of loop. Use it when you need real-time updates from the server (for example, chat messages or presence events) without buffering the entire payload in memory. + +## Remarks +streamSse provides a focused, typed surface for consuming SSE in this chat-oriented API. By encapsulating the transport and options handling behind a single function, it promotes consistent configuration and error handling across all callers that need streaming data. It also centralizes how StreamChatOptions influence streaming behavior, reducing duplication across modules. + +## Example +```typescript +// Example +async function listen() { + const url = "/api/chat/stream"; + const body = {}; + const opts = {} as StreamChatOptions; + + for await (const event of streamSse(url, body, opts)) { + console.log("event:", event); + } +} +``` + +## Notes +- The body parameter is typed as unknown; ensure the server accepts and processes the payload you send. +- Errors during streaming propagate to the consumer; wrap the for-await-of loop in try/catch to handle network or parsing errors. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/auth/AuthContext.tsx.md b/docs/auriondocs/Code/src/webapp/src/auth/AuthContext.tsx.md new file mode 100644 index 0000000..1132dcf --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/auth/AuthContext.tsx.md @@ -0,0 +1,147 @@ +# AuthContext.tsx + +> **Source:** `src/webapp/src/auth/AuthContext.tsx` + +## Contents + +- [AuthState](#authstate) +- [AuthProvider](#authprovider) +- [formatError](#formaterror) +- [handler](#handler) +- [useAuth](#useauth) + +--- + +## AuthState +> **File:** `src/webapp/src/auth/AuthContext.tsx` +> **Kind:** interface + +```typescript +export interface AuthState +``` + + +AuthState defines the runtime shape of the authentication state exposed by the app's authentication context. It exposes the current user (undefined while the initial /me request is resolving, null when unauthenticated, or a MeResponse when authenticated) and three asynchronous actions: login, register, and logout. Use AuthState when you need to read authentication status and trigger authentication flows in a type-safe way across UI components or providers. + +## Remarks +AuthState serves as a contract that decouples UI from the specifics of how authentication is performed, centralizing user state and authentication actions. The undefined sentinel for user represents the loading phase during the initial user resolution, helping avoid flicker in the first render before the /me call completes. This shape makes it straightforward to implement or consume a shared auth context without baking in implementation details into every component. + +## Example +```typescript +// Minimal, valid AuthState instance demonstrating proper typing +const exampleAuthState: AuthState = { + user: undefined, + login: async (email: string, password: string) => { + // perform login + }, + register: async (email: string, password: string) => { + // perform registration + }, + logout: async () => { + // perform logout + }, +}; +``` + +## Notes +- Treat undefined as a loading state for the current user; do not assume authentication status until it becomes either null or a MeResponse. +- Distinguish between null (unauthenticated) and a populated MeResponse to drive UI states (e.g., show login form vs. show user menu). +- Since login/register/logout return Promises, callers should await these operations and handle potential errors, while the host implementation updates the user field accordingly to reflect the new authentication status. + +--- + +## AuthProvider +> **File:** `src/webapp/src/auth/AuthContext.tsx` +> **Kind:** function + +```typescript +export function AuthProvider( +``` + + +AuthProvider is a React functional component that serves as the authentication context provider for the application. It accepts a children prop and renders those children within the authentication context, enabling descendant components to access authentication state and actions without prop drilling. By wrapping the app in AuthProvider, you centralize authentication logic and ensure consistent access to user data and authentication operations across the UI. + +## Remarks +AuthProvider centralizes authentication concerns and is intended to be mounted near the root of the component tree. It works with a shared context to expose data such as the current authentication status and methods for signing in or signing out, so all consumers can react to auth changes without needing to pass callbacks through props. This abstraction simplifies testing and future refactors by isolating the authentication mechanism from UI components. + +--- + +## formatError +> **File:** `src/webapp/src/auth/AuthContext.tsx` +> **Kind:** function + +```typescript +function formatError(e: unknown, fallback: string): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `unknown` | — | +| `fallback` | `string` | — | + +**Returns:** `string` + + +formatError converts an unknown error into a user-facing string, using a provided fallback when needed. It preferentially surfaces API-provided details for ApiError by reading body.detail, then falls back to the error's message, and finally to the fallback. If the value is a plain Error, it returns its message; otherwise, it returns the fallback. + +## Remarks + +This function centralizes how errors are presented in the UI, especially within authentication flows where API responses may carry structured error details. By surfacing detail when available, callers avoid exposing uncertain internal state and still provide meaningful feedback to users. The instanceof ApiError check relies on ApiError being a runtime class; if not, the function gracefully handles plain Errors and other values. + +## Notes + +- If you pass an ApiError with no detail and no message, the fallback is used. +- The check uses e instanceof ApiError; ensure ApiError is available in the runtime environment. +- The function only reads body.detail; other fields are ignored. + +--- + +## handler +> **File:** `src/webapp/src/auth/AuthContext.tsx` +> **Kind:** function + +```typescript +const handler = () => +``` + + +A tiny wrapper that delegates to logout() and discards its return value. You’d reach for it when wiring a logout action to an event handler where you neither need the return value nor to await completion, such as a simple onClick handler. + +## Remarks +This symbol acts as a thin adapter between UI events and the central logout logic. By using the void operator, it guarantees the caller's interest is limited to triggering the logout side effect, not consuming any value or promise it may produce. This keeps the call site clean and explicit about ignoring the logout result, while still delegating the actual logout work to the shared implementation. + +## Example +```typescript +// Example: wire up the handler to a logout button via DOM +document.querySelector('#logoutBtn')?.addEventListener('click', handler); +``` + +## Notes +- If logout returns a promise, not awaiting it means the logout may still be in flight after the event handler returns; consider awaiting inside the handler or handling the promise at call site. +- If logout can throw synchronously, wrap in try/catch to avoid surprising propagation from an event handler. + +--- + +## useAuth +> **File:** `src/webapp/src/auth/AuthContext.tsx` +> **Kind:** function + +```typescript +export function useAuth(): AuthState +``` + +**Returns:** `AuthState` + + +useAuth is a React hook that retrieves the current authentication state from AuthContext and returns it to the caller. It enforces correct usage by throwing an error if used outside an AuthProvider, ensuring you always work with a valid AuthState. + +## Remarks +Provides a single typed entry point to the authentication state, abstracting away direct useContext calls. The runtime guard helps fail fast during development when a provider is missing, guiding proper app wiring. Because it uses useContext, components consuming useAuth will re-render as the AuthState changes. + +## Notes +- If you call useAuth outside of an AuthProvider, you'll get an Error with message 'useAuth must be used inside '. +- The guard relies on a non-null AuthState being provided; if the context value is ever null/undefined, the hook will throw. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/Avatar.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/Avatar.tsx.md new file mode 100644 index 0000000..e30c59d --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/Avatar.tsx.md @@ -0,0 +1,107 @@ +# Avatar.tsx + +> **Source:** `src/webapp/src/components/Avatar.tsx` + +## Contents + +- [PulseConfig](#pulseconfig) +- [PulseState](#pulsestate) +- [Avatar](#avatar) +- [PulsePlane](#pulseplane) +- [createPulse](#createpulse) + +--- + +## PulseConfig +> **File:** `src/webapp/src/components/Avatar.tsx` +> **Kind:** interface + +```typescript +interface PulseConfig +``` + + +PulseConfig is a small configuration object that drives the pulsing effect used by the avatar UI. It requires a seed to seed any per-instance randomness and exposes two optional knobs: patternName, which selects a named pulse pattern from PatternName, and paletteName, which selects the color palette for the pulse when supplied. Use PulseConfig when you want to parameterize the avatar's pulse behavior from data rather than hard-coding values, so different themes or instances can share the same rendering logic. + +## Remarks +PulseConfig acts as a data carrier that decouples the animation details from the Avatar component. By elevating the pattern and palette choices into a separate type, the system can reuse the same pulsing logic across multiple avatars and themes. The seed ensures that each configuration can produce a deterministic variation, which is helpful for UI previews, testing, or any scenario that benefits from repeatable visuals. The Avatar component (in this repository at src/webapp/src/components/Avatar.tsx) consumes PulseConfig to render its pulsing effect, enabling consistent visuals across the app while keeping the rendering logic driven by configuration. + +--- + +## PulseState +> **File:** `src/webapp/src/components/Avatar.tsx` +> **Kind:** interface + +```typescript +interface PulseState +``` + + +PulseState is a compact container that represents a single snapshot of the avatar’s pulsing effect. It groups the current animation pattern, its parameters, the color palette, the raw pulse data buffer, and the texture used by the renderer. Use PulseState when you need to pass or swap all aspects of the pulse together, rather than juggling them separately, for example when reconfiguring the avatar’s glow in response to user input or game state. + +## Remarks +PulseState abstracts the rendering concerns behind a stable surface: a pattern describes the animation, while the palette provides the colors and data/texture carry the visual payload. Keeping these together ensures that a pattern change and its visual incarnation (data and texture) stay in lockstep. It fits alongside other avatar rendering state by acting as a single transfer object between the logic that computes pulse data and the pipeline that renders it. + +## Notes +- Be mindful that mutating `data` or the underlying texture typically requires signaling the renderer to refresh the texture (e.g., updating the DataTexture in use) to render the new pulse. +- The `params` field is typed as `unknown`; consumers should validate and cast it before use to avoid runtime errors. + +--- + +## Avatar +> **File:** `src/webapp/src/components/Avatar.tsx` +> **Kind:** function + +```typescript +export function Avatar( +``` + + +This Avatar component renders a deterministic avatar for a given seed, allowing a selectable pattern and color palette. It is useful when you want stable, seed-based avatars across the app without relying on uploaded images or external services. + +## Remarks +This abstraction centralizes avatar generation so a single change to the seed-to-visual mapping or default styling affects all call sites without changing their usage. It helps enforce a consistent visual language across the UI by decoupling avatar visuals from the data or content being represented. + +## Example +```typescript + +``` + +## Notes +- Provide a stable seed to preserve avatar identity across renders and navigations. +- The exact visuals depend on the underlying generation algorithm; swapping pattern or palette will alter the appearance while keeping the same seed. + +--- + +## PulsePlane +> **File:** `src/webapp/src/components/Avatar.tsx` +> **Kind:** function + +```typescript +function PulsePlane( +``` + + +PulsePlane is a React function component that accepts a props object containing a config property and returns JSX describing a plane-like visual element associated with the Avatar UI. By encapsulating the pulse rendering in a dedicated component, it keeps the Avatar layout focused while enabling configurable pulse behavior via the config prop. + +## Remarks +Encapsulating the pulse logic in PulsePlane decouples styling and behavior from the Avatar container, making it easier to reuse this visual cue across different avatar variants. The abstraction also provides a centralized place to adjust animation parameters (size, color, duration) through the config without altering the Avatar's core rendering. This pattern promotes composability as the UI evolves, enabling consistent visuals and easier testing. + +## Notes +- The actual visuals depend on the config; if certain fields are missing, default styling should apply. +- Treat PulsePlane as a presentational component; there are no side effects within this component, and props should remain immutable. + +--- + +## createPulse +> **File:** `src/webapp/src/components/Avatar.tsx` +> **Kind:** function + +```typescript +function createPulse( +``` + + +I will proceed once I retrieve the symbol metadata and, if needed, the implementation to ensure the narrative is accurate and grounded in the actual code. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/Chat.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/Chat.tsx.md new file mode 100644 index 0000000..14f059f --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/Chat.tsx.md @@ -0,0 +1,638 @@ +# Chat.tsx + +> **Source:** `src/webapp/src/components/Chat.tsx` + +## Contents + +- [ChatProps](#chatprops) +- [EntryActions](#entryactions) +- [ChatEntry](#chatentry) +- [VariantMeta](#variantmeta) +- [Chat](#chat) +- [Reasoning](#reasoning) +- [Thought](#thought) +- [ToolResult](#toolresult) +- [VariantPicker](#variantpicker) +- [advance](#advance) +- [applyAgentEvent](#applyagentevent) +- [hasActiveAssistantStream](#hasactiveassistantstream) +- [historyToEntries](#historytoentries) +- [lastIndexWhere](#lastindexwhere) +- [onKeyDown](#onkeydown) +- [onSubmit](#onsubmit) +- [prettyArgs](#prettyargs) +- [prev](#prev) +- [renderEntry](#renderentry) +- [send](#send) +- [toolCallEntry](#toolcallentry) +- [variantMetaOf](#variantmetaof) + +--- + +## ChatProps +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** interface + +```typescript +interface ChatProps +``` + + +ChatProps is the props contract for the Chat component used by the web UI. It bundles the identifiers and callbacks that configure a chat session: the conversationId to load and persist context, an avatarSeed that deterministically drives the thinking-pulse animation, and an optional paletteStops override to align the pulse colors with the active Gabriel Sequence. Optional callbacks surface user actions and conversation lifecycle events to the parent: onMessageSent notifies when the user sends a message, onBusyChange communicates loading state, onConversationLoaded fires after metadata is loaded for a new conversation, and onConversationMissing signals that the conversation no longer exists (e.g., 404) so the parent can navigate away. + +## Remarks +This interface acts as a stable boundary between the Chat component and its host, encapsulating the essential theming, identity, and lifecycle hooks needed for a chat session. It cleanly separates UI concerns (avatar visuals, pulse colors) from data-fetching logic and provides explicit hooks for hosts to react to events like message submission and conversation switches. The paletteStops option enables server-driven branding by overriding the seed-based palette to match the active Gabriel Sequence. + +## Notes +- paletteStops is optional; when provided (and non-null), the thinking-pulse bars color scheme comes from these stops instead of the seed-derived palette, ensuring visual continuity with the active Gabriel Sequence. +- onConversationLoaded fires after the conversation metadata loads during a switch, enabling parents to sync state (e.g., avatarSeed) without re-fetching. +- onConversationMissing fires when the initial history fetch returns 404; the parent typically navigates away from the stale URL. + +--- + +## EntryActions +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** interface + +```typescript +interface EntryActions +``` + + +EntryActions defines a compact contract for the actionable controls associated with a chat entry in the Chat.tsx UI. It groups a busy flag with four callbacks that drive user interactions: delete, regenerate, switch variants, and remember. Implementers supply concrete behavior for these actions, while UI components consume the interface to render and wire controls without depending on a specific implementation. + +## Remarks +This interface encapsulates the interaction concerns for a single chat entry. The busy flag signals in-flight work so the UI can disable actions appropriately, avoiding overlapping operations. The onDelete, onRegenerate, onVariantSwitch, and onRemember callbacks all receive an identifier (messageId or targetId) that scopes the action to the correct entry, facilitating loose coupling between the UI and the underlying business logic. + +## Example +```typescript +// Example: simple action handlers for a chat entry +const exampleActions: EntryActions = { + busy: false, + onDelete: (messageId) => { + console.log(`Deleting message ${messageId}`); + // trigger deletion logic here + }, + onRegenerate: (messageId) => { + console.log(`Regenerating response for ${messageId}`); + // trigger regeneration logic here + }, + onVariantSwitch: (targetId) => { + console.log(`Switching to variant ${targetId}`); + }, + onRemember: (messageId) => { + console.log(`Remembering message ${messageId}`); + }, +}; +``` + +## Notes +- When busy is true, consumers should disable related UI controls to prevent concurrent operations on the same entry. +- The handlers expect string identifiers and should rely on a consistent ID scheme across the UI to avoid misrouting actions. + +--- + +## ChatEntry +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** type alias + +```typescript +type ChatEntry = + | { kind: 'text'; key: string; id: string; role: 'user' | 'assistant'; content: string; streaming?: boolean; variant?: VariantMeta } + | { kind: 'thought'; key: string; id: string; content: string; streaming?: boolean } + | { kind: 'reasoning'; key: string; id: string; content: string; streaming?: boolean } + | { kind: 'toolCall'; key: string; id: string; toolCallId: string; name: string; argumentsJson: string } + | { kind: 'toolResult'; key: string; id: string; toolCallId: string; content: string }; +``` + + +ChatEntry represents the various forms a single chat item can take in the UI, modeled as a discriminated union so callers can treat every entry uniformly while branching on kind to render differently. It covers user/assistant text messages, internal thoughts, streams of reasoning, and tool interactions, all sharing common identifiers but exposing shape-specific fields as needed. This type is typically consumed by the chat rendering layer and storage components to keep a cohesive history of dialogue events. + +## Remarks + +The union’s discriminant, kind, enables exhaustive handling and clear rendering paths for each entry kind (text, thought, reasoning, toolCall, toolResult). All variants share the generic identifiers (key and id) while presenting shape-specific data—such as role and content for text entries, toolCallId and argumentsJson for tool interactions, or content for reasoning streams—so a single consumer can switch on kind and access the appropriate fields without casting. This abstraction is designed to accommodate the evolution of the chat pipeline (e.g., adding new entry kinds) without breaking existing renderers. + +## Example + +```typescript +const userEntry: ChatEntry = { kind: 'text', key: 'u1', id: '1', role: 'user', content: 'Hello' }; +const toolCallEntry: ChatEntry = { kind: 'toolCall', key: 't1', id: '2', toolCallId: 'tc1', name: 'Search', argumentsJson: '{"query":"openai"}' }; +``` + +## Notes + +- Treat ChatEntry as a discriminated union; ensure exhaustive handling when switching on kind to avoid missing a variant. +- All variants share the common metadata (key, id), but their required fields differ; handle narrowing carefully when consuming a value. + + +--- + +## VariantMeta +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** type alias + +```typescript +type VariantMeta = { + variantGroupId: string; + variantIndex: number; + variantCount: number; + variantSiblingIds: readonly string[]; +}; +``` + + +Defines the shape of metadata for a single variant within a group of related variants. It carries the group identifier, the variant’s position within that group, the total number of variants in the group, and the identifiers of the other variants in the same group. This lightweight descriptor is used by UI layers (for example in the chat interface) to render and navigate variant options without loading the full variant payload. + +## Remarks + +VariantMeta isolates variant-collection concerns from the full variant data, enabling lightweight transfer and predictable UI behavior when presenting variant selectors. By carrying only grouping and positional metadata, components can reason about enabled states, navigation order, and sibling relationships without depending on concrete variant details. Treat this object as an immutable descriptor that can be freely composed and passed through UI boundaries. + +## Example + +```typescript +const example: VariantMeta = { + variantGroupId: 'color', + variantIndex: 1, + variantCount: 4, + variantSiblingIds: ['color-red', 'color-green', 'color-blue'] +}; +``` + +## Notes + +- The variantSiblingIds array is readonly; avoid mutating it in place. +- The type does not enforce invariants like variantIndex < variantCount; callers should validate as needed. + +--- + +## Chat +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +export function Chat( +``` + + +Chat is a React function component that renders the chat UI for a specific conversation and reports user actions and lifecycle events back to its parent via callbacks. It accepts a conversationId to identify the conversation, avatarSeed for deterministic avatar visuals, and paletteStops to drive theming; the onMessageSent, onBusyChange, onConversationLoaded, and onConversationMissing props are used to communicate user actions and data-loading states upward. + +## Remarks +Chat encapsulates the presentation and interaction logic of a single conversation, decoupling it from the data-loading and persistence concerns handled by the parent. By exposing explicit callbacks for message sending and lifecycle events, it gives the rest of the app flexibility to manage server requests, loading indicators, and error handling while reusing a consistent chat surface across conversations. The avatarSeed and paletteStops props enable stable, conversation-specific visuals, helping users recognize and stay oriented within a long-running thread. + +--- + +## Reasoning +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function Reasoning( +``` + + +Reasoning is a TypeScript React function component defined in the chat UI (src/webapp/src/components/Chat.tsx). It accepts a props object with two fields: content and streaming. From this partial signature, the symbol appears to be involved in handling the reasoning portion of a chat interaction, though the actual rendering implementation is not visible in the provided excerpt. + +--- + +## Thought +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function Thought( +``` + + +Thought is a React function component that takes a props object with a content property. As suggested by its declaration and its location in src/webapp/src/components/Chat.tsx, it serves as a presentation primitive for rendering the provided content within the chat interface, encapsulating the rendering concerns of a 'thought' so callers can render it consistently without duplicating markup. + +## Remarks +Thought acts as a small, reusable UI building block for displaying thought content in chat conversations. By isolating its rendering, you can swap visuals, adjust styling, or apply accessibility considerations in one place, while leaving higher-level chat composition unchanged. + +## Notes +- The code snippet is incomplete—the function body and return value are not shown, so exact rendering, props shape, and behavior are not visible. +- The actual prop types for content are not included in the fragment; rely on the implementation to determine accepted types (e.g., string vs. ReactNode). +- If this component is part of a larger chat theming system, ensure it is wired to the theme and layout conventions used by Chat.tsx. + +--- + +## ToolResult +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function ToolResult( +``` + + +ToolResult is a React function component in Chat.tsx that destructures a single prop named content. The snippet here shows only the function signature and does not reveal how content is rendered, so the exact output or styling cannot be inferred from this excerpt. A developer would reach for ToolResult when they need to display or integrate the content produced by a tool within the chat interface, as implied by its name. + +--- + +## VariantPicker +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function VariantPicker( +``` + + +VariantPicker is a small React functional component that presents a user interface for selecting among content variants in the chat UI. It takes three props: variant (the currently selected variant), disabled (a boolean that disables user interaction), and onSwitch (a callback invoked with the new variant when the user changes selection). Use VariantPicker when you want a reusable, testable control to switch between variants instead of embedding variant-switching logic directly in Chat.tsx. + +## Remarks +VariantPicker abstracts the variant-switching behavior behind a clean, controlled API. It allows the parent component to own the variant state and respond to user input via onSwitch, which makes it straightforward to reuse and to test in isolation. It also promotes a consistent UX for variant selection across the chat UI by providing a single, centralized control. + +## Notes +- When disabled is true, the component should not respond to user input or invoke onSwitch. +- If variant is a complex object, ensure its identity is stable to avoid unnecessary re-renders; prefer primitive or memoized values when possible. + +--- + +## advance +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +const advance = () => +``` + + +Advances to the next variant in a circular list by computing the next index and switching to that variant’s ID. It uses (variantIndex + 1) % variantCount to wrap around, then calls onSwitch with variantSiblingIds[next] to perform the transition. This is the kind of tiny navigator you wire to a Next control to let users cycle through available chat variants without manually handling bounds. + +## Remarks +Encapsulates wrap-around and ID lookup in a single, reusable handler, keeping UI event wiring simple and less error-prone. It relies on surrounding state (variantIndex, variantCount, variantSiblingIds) and a callback (onSwitch), making it a lightweight navigation primitive that can be swapped or mocked in tests without touching the UI logic. + +## Notes +- variantCount must be > 0 to avoid a division-by-zero in the modulo operation. +- Keep variantIndex, variantCount, and variantSiblingIds in sync with the actual variant list; a mismatch could yield an undefined ID. +- Since this function invokes onSwitch, ensure the callback can handle rapid or reentrant calls without causing inconsistent state. + +--- + +## applyAgentEvent +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function applyAgentEvent( + evt: import('../api/streamChat').AgentEvent, + setEntries: React.Dispatch>, +) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `evt` | `import('../api/streamChat').AgentEvent` | — | +| `setEntries` | `React.Dispatch>` | — | + + +applyAgentEvent updates the chat entries state in response to AgentEvent messages, orchestrating live streaming bubbles for assistant text, interleaved reasoning, and tool invocations. It appends to an active streaming text bubble on textDelta, maintains a single streaming reasoning entry for reasoningDelta, and on toolCall, freezes the current reasoning, converts the trailing streaming content into a collapsed 'thought', and then inserts the tool invocation entry. + +## Remarks +Centered as the UI glue between the agent stream and the chat display, this function encapsulates the rules for when to show ongoing thinking versus finished content. By using synthetic streaming IDs and stable keys, it lets React preserve DOM nodes during typing without remounting. The special handling of tool calls—promoting the trailing streaming text to a 'thought' and appending a tool entry—enables a clean ReAct-like narrative: think, act, observe. + +## Notes +- Be aware that it relies on browser crypto APIs to generate streaming IDs; in environments without crypto, ensure a polyfill or fallback ID strategy. +- The function mutates state via a functional updater; callers must ensure setEntries is the React state setter from useState. + +## Dependencies +- React +- ChatEntry + +## Dependency APIs +- type ChatEntry (src/webapp/src/components/Chat.tsx) + +--- + +## hasActiveAssistantStream +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function hasActiveAssistantStream(entries: ChatEntry[]): boolean +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `entries` | `ChatEntry[]` | — | + +**Returns:** `boolean` + + +hasActiveAssistantStream reports whether the most recent chat entry is a streaming text message authored by the assistant. Call it to detect an in-progress assistant reply and drive UI decisions (e.g., showing a streaming indicator) instead of inspecting the entries array manually. + +## Remarks +Its role is to centralize the streaming-detection logic to the last-entry condition, preventing duplicated checks across components. It also guards against false positives by requiring the entry to be a text-type with role 'assistant' and a truthy streaming flag, ensuring only genuine streaming outputs are treated as active. + +## Notes +- The function only inspects the last entry; if a non-streaming entry is appended after a streaming one, the result will be false until that entry becomes the last element. +- If the entries array is empty, the function returns false; callers should handle empty input gracefully. + +--- + +## historyToEntries +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function historyToEntries(messages: MessageResponse[]): ChatEntry[] +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `messages` | `MessageResponse[]` | — | + +**Returns:** `ChatEntry[]` + + +historyToEntries translates a list of MessageResponse objects into a renderable sequence of ChatEntry items for the chat UI. It iterates the API history and emits entries that reflect each message's role, streaming state, and any associated tool calls, including a dedicated reasoning entry when reasoningContent is present and separate toolResult entries for tool calls; for history-built messages, key and id are identical, while in-flight entries may have their id patched later during streaming. + +## Remarks +The function decouples the API payload from UI rendering by converting the raw message stream into a consistent, UI-friendly sequence of entries. It explicitly handles reasoning streams, tool interactions, and the final textual content, ensuring the chat UI can render partial thinking and tool usage in the correct order. By emitting distinct entry kinds (reasoning, text, thought, toolResult), it provides the UI with rich structure to progressively display messages as they arrive. + +## Notes +- System messages are not rendered by this transformer; surface any system-level context in a separate UI layer if needed. +- A message without content yields no entry; user/assistant turns contribute entries only when content exists, and tool-calls are appended as separate toolResult entries in the order they appear. + +--- + +## lastIndexWhere +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function lastIndexWhere(arr: readonly T[], pred: (e: T) => boolean): number +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `arr` | `readonly T[]` | — | +| `pred` | `(e: T) => boolean` | — | + +**Returns:** `number` + + +lastIndexWhere finds the last index in a read-only array where the provided predicate evaluates to true. It walks from the end of the array toward the start and returns the index of the first element that satisfies pred, or -1 if none match. This is useful when you need the most recent element meeting a condition without scanning from the front. + +## Remarks +Viewed as a tiny backwards-search utility, it complements forward-search patterns like findIndex by performing a single backward pass and never mutating the input. The generic T keeps it usable for any element type, and the readonly arr emphasizes that no mutation occurs during the search. If your environment offers Array.prototype.findLastIndex, lastIndexWhere provides a self-contained alternative with the same backward-search semantics. + +## Example +```ts +// Basic backward search on a plain array +const xs = [1, 2, 3, 4, 2]; +const lastIndex = lastIndexWhere(xs, x => x === 2); +console.log(lastIndex); // 4 + +// If no element matches +console.log(lastIndexWhere([1, 3, 5], x => x > 10)); // -1 +``` + +## Notes +- Return value -1 when no element matches; callers must handle this case. +- The predicate is evaluated from the end; predicates with side effects will run for elements from the tail until a match is found. + +--- + +## onKeyDown +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +const onKeyDown = (e: KeyboardEvent) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `KeyboardEvent` | — | + + +This onKeyDown handler is attached to the chat input textarea to enable sending messages with Enter. When Enter is pressed without Shift, it prevents the default newline behavior and calls send(); pressing Shift+Enter inserts a newline as usual. + +## Remarks +This small abstraction centralizes Enter-to-send behavior in one place, decoupling the UI from the sending logic. It ensures Shift+Enter still yields a newline, preserving common chat UX expectations, while Enter alone reliably submits the current message. + +## Notes +- Using void send() intentionally ignores the returned value (often a Promise). If you rely on send()'s result or handle errors, consider awaiting or adding error handling at the call site. +- Ensure this handler is bound to the textarea's onKeyDown; associating it with a different key event or element may alter UX expectations. + + +--- + +## onSubmit +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +const onSubmit = (e: FormEvent) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `FormEvent` | — | + + +onSubmit is a compact form-submission handler that prevents the default browser submission and delegates to send() to perform the actual submission. It’s designed to be attached to a form’s onSubmit, so the UI stays responsive and the page does not reload when a user submits a message; the result of send() is intentionally not awaited, as indicated by the explicit void call. + +## Remarks +onSubmit acts as a thin bridge between the UI layer and the sending workflow, decoupling the event handling from the business logic. It relies on a send function available in scope; by using void, it signals that the asynchronous result is intentionally ignored, which keeps the handler small but places responsibility for error handling elsewhere if needed. It is a common pattern in React/TypeScript forms where the submission should trigger async work without blocking the UI. + +## Notes +- The promise returned by send is not awaited; if you need error feedback to users, handle the promise elsewhere or modify the handler to await with try/catch. + +--- + +## prettyArgs +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function prettyArgs(json: string): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `json` | `string` | — | + +**Returns:** `string` + + +Transforms a string that should be JSON into a compact, canonical JSON representation. If the input is empty or exactly '{}', it returns an empty string to indicate 'no arguments'. Otherwise it attempts to parse the string as JSON and immediately stringify the result; if parsing fails, it returns the original input unchanged. This makes it safe to render or log user-provided argument strings without throwing, while normalizing valid JSON. + +## Remarks +This is a small, pure helper that centralizes how UI code renders argument payloads. It relies on the native JSON.parse / JSON.stringify behavior, so it benefits from the language's standard JSON semantics and avoids reimplementing formatting rules. Returning the original input for invalid JSON preserves user input rather than producing misleading or broken displays. + +## Example +```typescript +// Common case: normalize a valid JSON string +prettyArgs("{\"a\":1,\"b\":2}"); + +// Empty input yields an empty string +prettyArgs(""); + +// Non-JSON input is returned as-is +prettyArgs("not json"); +``` + +--- + +## prev +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +const prev = () => +``` + + +prev is a small navigation helper that moves the UI to the previous variant in a circular list. It wraps around from the first variant to the last by computing the target index as (variantIndex - 1 + variantCount) % variantCount and then switching to the corresponding variant using onSwitch with the ID from variantSiblingIds. + +## Remarks +Encapsulates wrap-around navigation logic and keeps the variant-switching flow focused in one place. It depends on surrounding state (variantIndex, variantCount, variantSiblingIds) and the callback onSwitch to perform the actual switch. This abstraction makes wiring a Previous control in the Chat.tsx variant selector straightforward without duplicating boundary checks in the UI code. + +## Notes +- If variantCount is 0 this will yield NaN; guard against zero variants or disable the control when there are none. + +--- + +## renderEntry +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function renderEntry(e: ChatEntry, actions: EntryActions) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `ChatEntry` | — | +| `actions` | `EntryActions` | — | + + +Renders a single chat entry as a React node, selecting the appropriate visual rendering and interaction controls based on the entry's kind and state. It handles text messages (with special behavior for live, streaming assistant entries), tool interactions, and non-message entries by delegating to specific subcomponents. This centralizes per-entry UI decisions so the chat UI can consistently compose the conversation feed. + +## Remarks + +renderEntry acts as the central renderer for chat rows, coordinating how different entry kinds are displayed and interacted with. It couples the content with UI affordances (such as variant switching, regeneration, memory saving, and deletion) in a way that respects the entry's persistence and streaming status. By encapsulating this logic in a single function, the surrounding chat container can render a heterogeneous stream of messages without duplicating conditional rendering logic, while still enabling fine-grained behavior (e.g., hiding actions for ephemeral streaming messages and enabling regeneration only on the latest assistant variant). + +## Example + +```typescript +// Example usage demonstrating a persisted assistant text entry with a single variant +const entry: ChatEntry = { + kind: 'text', + role: 'assistant', + streaming: false, + id: 'msg-1', + content: 'Hello, world!', + variant: { variantIndex: 0, variantCount: 1 } +}; + +const actions: EntryActions = { + busy: false, + onVariantSwitch: (_v: any) => {}, + onRegenerate: (_id: string) => {}, + onRemember: (_id: string) => {}, + onDelete: (_id: string) => {} +}; + +renderEntry(entry, actions); +``` + + +--- + +## send +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +const send = async () => +``` + + +The send function is an asynchronous handler that submits the current user input as a new chat message, creates an optimistic chat entry, and then streams the backend reply to progressively render the assistant’s message. It guards against empty submissions and concurrent sends, clears the input, raises a busy state, and marks the UI to stay scrolled to the bottom. It establishes an AbortController to cancel the streaming session if needed, assigns a temporary id for the optimistic entry, and patches that entry with the real database id as server events arrive. If the stream completes or errors, it finalizes the UI state and invokes onMessageSent on success, or cleans up placeholders on failure. Finally, it resets compacting and busy indicators and clears the abort handle to avoid leaks, ensuring cleanup even when the stream is interrupted. The function relies on streamChat, applies agent events to update the chat log, and uses auxiliary state like isAtBottomRef and entries to coordinate the live, streaming user experience. + +--- + +## toolCallEntry +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function toolCallEntry(messageId: string, tc: MessageToolCall): ChatEntry +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `messageId` | `string` | — | +| `tc` | [`MessageToolCall`](../../../api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md) | — | + +**Returns:** `ChatEntry` + + +Converts a MessageToolCall into a ChatEntry that represents a tool invocation inside a chat transcript. Given a messageId and a ToolCall descriptor tc, it computes a stable identifier by composing messageId and tc.id for both the key and the id, and returns a ChatEntry with kind 'toolCall' and fields populated from the descriptor: toolCallId from tc.id, name from tc.name, and argumentsJson from tc.argumentsJson. + +## Remarks +By encapsulating the transformation from tool payload to chat entry, this helper decouples tool interactions from UI rendering and guarantees a uniform representation of tool calls across the chat interface. It also centralizes the deterministic key generation so the same tool call yields the same entry wherever constructed. + +## Notes +- The function is pure; it does not mutate inputs and returns a new ChatEntry object. +- The key and id are derived from messageId and tc.id; ensure those values remain stable to avoid collisions or duplicate entries. + +--- + +## variantMetaOf +> **File:** `src/webapp/src/components/Chat.tsx` +> **Kind:** function + +```typescript +function variantMetaOf(m: MessageResponse): VariantMeta +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `m` | [`MessageResponse`](../../../api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md) | — | + +**Returns:** `VariantMeta` + + +Maps a MessageResponse to a VariantMeta by extracting the variantGroupId, variantIndex, variantCount, and variantSiblingIds. Use it whenever you need a concrete VariantMeta derived from a message payload, avoiding repetition of the field-mapping logic. + +## Remarks + +Centralizes the mapping so that if the shape or source of variant data changes, update happens in one place. It also makes the intent explicit: this function is solely responsible for projecting variant details from a MessageResponse into a VariantMeta, decoupling UI concerns from the API model. + +## Example + +```typescript +// Example: derive VariantMeta from an existing MessageResponse `m` +const meta: VariantMeta = variantMetaOf(m); +``` + +## Notes + +- The function copies references for nested structures (e.g., variantSiblingIds). If you mutate the input array after obtaining the Meta, those changes will be visible through the Meta as well. Consider cloning if a defensive copy is required: `variantSiblingIds: [...m.variantSiblingIds]`. +- The mapping is intentionally narrow: only the four variant-related fields are carried forward; any additional fields on MessageResponse are ignored by this helper. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/CompactingOverlay.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/CompactingOverlay.tsx.md new file mode 100644 index 0000000..bdc17b2 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/CompactingOverlay.tsx.md @@ -0,0 +1,106 @@ +# CompactingOverlay.tsx + +> **Source:** `src/webapp/src/components/CompactingOverlay.tsx` + +## Contents + +- [CompactingOverlayProps](#compactingoverlayprops) +- [SwirlProps](#swirlprops) +- [CompactingOverlay](#compactingoverlay) +- [Core](#core) +- [Swirl](#swirl) + +--- + +## CompactingOverlayProps +> **File:** `src/webapp/src/components/CompactingOverlay.tsx` +> **Kind:** interface + +```typescript +interface CompactingOverlayProps +``` + + +CompactingOverlayProps is the props interface for the CompactingOverlay component. It exposes two optional fields: paletteStops, which is a readonly array of RGB values or null, used for color matching the swirl to the avatar; and messageCount, a number indicating how many messages are folded and displayed in the caption. + +## Remarks + +CompactingOverlayProps exists to keep presentation details decoupled from the component's behavior. It acts as a small data carrier that callers can populate to influence visuals without changing logic. paletteStops ties the overlay's color palette to the conversation's avatar swirl, enabling a consistent visual identity. The messageCount value controls how many messages are represented as folded in the caption, providing a concise indicator of chat activity. + +--- + +## SwirlProps +> **File:** `src/webapp/src/components/CompactingOverlay.tsx` +> **Kind:** interface + +```typescript +interface SwirlProps +``` + + +SwirlProps is a TypeScript interface that defines the props contract for a swirl‑style overlay component. Its optional paletteStops property accepts a readonly array of RGB color specifications; when provided, these stops configure the color progression used by the swirl rendering. Because the property is optional, consumers may omit it to rely on the component's default color palette. + +## Remarks +SwirlProps encapsulates color configuration separate from rendering logic, enabling reuse of the swirl overlay with different color schemes without mutating internal state. The `ReadonlyArray` type communicates that the consumer should treat the palette stops as an immutable input and signals to downstream code that the palette is provided from outside. This abstraction makes it easy to swap color schemes by swapping props rather than mutating internal state of the rendering component. + +## Notes +- Mutability trap: ReadonlyArray prevents structural mutations but does not freeze the RGB objects themselves. If the RGB instances are shared, changes to their properties could affect other consumers. +- For React usage, pass a stable array reference to avoid unnecessary re-renders when the prop changes are not meaningful. + +--- + +## CompactingOverlay +> **File:** `src/webapp/src/components/CompactingOverlay.tsx` +> **Kind:** function + +```typescript +export function CompactingOverlay( +``` + + +CompactingOverlay is a React functional component that accepts a props object with paletteStops and messageCount and renders a compact overlay UI. It is intended to be used when you need a lightweight overlay that conveys color-palette context alongside a small numeric indicator rather than a full, multi-pane component. + +## Remarks +A UI primitive for overlay presentation that consolidates color-stop visualization with a message count. It helps centralize the styling and behavior of compact overlays across the app, reducing duplication and ensuring visual consistency. + +--- + +## Core +> **File:** `src/webapp/src/components/CompactingOverlay.tsx` +> **Kind:** function + +```typescript +function Core( +``` + + +Core renders the central portion of the CompactingOverlay UI by consuming the paletteStops prop. It encapsulates the color-stop visuals, so callers can provide palette configuration without pulling in the overlay's surrounding layout. + +## Remarks +Core isolates the color-stop rendering logic from the overlay chrome, enabling easier testing and potential reuse in other components that share the same color-stop visuals. By concentrating palette-driven rendering in one component, changes to how color stops are represented stay local to Core and do not affect the overlay structure. + +## Notes +- The snippet exposes only the function signature fragment; confirm the full prop types and implementation in the surrounding module to avoid misusing Core. + +--- + +## Swirl +> **File:** `src/webapp/src/components/CompactingOverlay.tsx` +> **Kind:** function + +```typescript +function Swirl( +``` + + +Swirl is a small function component defined in CompactingOverlay.tsx that renders a swirl-shaped visual using a paletteStops prop. By encapsulating the swirl rendering in its own helper, the overlay can reuse this decorative element without duplicating drawing logic, keeping the overlay's layout focused on structure. + +## Remarks + +Swirl serves as a presentational helper that isolates the swirl-drawing concern from the surrounding layout. This separation makes the overlay easier to test and maintain, and aligns with a UI pattern of composing complex visuals from small, focused components. + +## Notes + +- Be mindful that paletteStops can trigger re-renders; avoid creating new arrays on every render. If the swirl visual is static, consider memoization or stable references to paletteStops to minimize unnecessary updates. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/ContextStats.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/ContextStats.tsx.md new file mode 100644 index 0000000..7b18280 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/ContextStats.tsx.md @@ -0,0 +1,161 @@ +# ContextStats.tsx + +> **Source:** `src/webapp/src/components/ContextStats.tsx` + +## Contents + +- [ContextStatsProps](#contextstatsprops) +- [Category](#category) +- [ContextStats](#contextstats) +- [buildGridCells](#buildgridcells) +- [formatTokens](#formattokens) + +--- + +## ContextStatsProps +> **File:** `src/webapp/src/components/ContextStats.tsx` +> **Kind:** interface + +```typescript +interface ContextStatsProps +``` + + +ContextStatsProps defines the shape of the props expected by the ContextStats UI component. It requires a conversationId string that identifies the current conversation and optionally accepts a refreshKey number that, when provided and changed, triggers a refetch or re-render to reflect updated context size. + +## Remarks +ContextStatsProps isolates identity data (conversationId) from refresh semantics (refreshKey), enabling callers to signal updates without mutating the identity. It is intended for use by a UI component that renders context statistics for a chat conversation and should be supplied by a parent component that manages the conversation state and its refresh lifecycle. + +## Example +```typescript +// Example usage: constructing props for a ContextStats component +const propsNoRefresh: ContextStatsProps = { + conversationId: "conv-1234" +}; + +const propsWithRefresh: ContextStatsProps = { + conversationId: "conv-1234", + refreshKey: 1 +}; +``` + +## Notes +- The refreshKey is optional; omit it if you do not need the refresh signaling. +- Treat refreshKey as a signal to trigger a refresh; changing it should produce a new value to activate the update. +- This interface is a small descriptor of props and does not perform data fetching itself; it simply communicates identity and refresh semantics to the consuming UI. + +--- + +## Category +> **File:** `src/webapp/src/components/ContextStats.tsx` +> **Kind:** type alias + +```typescript +type Category = { + key: 'system' | 'project' | 'memory' | 'summary' | 'tools' | 'conversation'; + label: string; + color: string; + tokens: (m: ContextMetricsResponse) => number; +}; +``` + + +Category is a TypeScript type that encapsulates a UI-facing category for contextual metrics used by ContextStats.tsx. Each category carries a fixed key (one of system, project, memory, summary, tools, conversation), a human-friendly label, a color string, and a tokens function that, given a ContextMetricsResponse, returns the numeric value that category contributes to the overall metrics. This abstraction enables the UI to render categories consistently—mapping semantic meaning (the key) to presentation (label and color) and to the actual metric extraction logic (tokens). + +## Remarks +This type centralizes the metadata and extraction logic for metric categories, facilitating consistent rendering and easy extension. It helps decouple the presentation from the data extraction strategy, allowing new categories to be added without scattering UI logic across the codebase. The tokens function provides a pluggable hook for category-specific metric calculations. + +## Notes +- Be mindful that tokens relies on the shape of ContextMetricsResponse; changes to ContextMetricsResponse may require updating the corresponding tokens implementations. +- If you introduce a new category key, ensure the label, color, and tokens mapping stay in sync across the UI and any enumeration that consumes the keys. + +--- + +## ContextStats +> **File:** `src/webapp/src/components/ContextStats.tsx` +> **Kind:** function + +```typescript +export function ContextStats( +``` + + +ContextStats is a React function component that renders statistics related to the contextual aspects of a specific conversation. It accepts a conversationId and an optional refreshKey (defaulting to 0); changing refreshKey prompts a refresh of the statistics without remounting the component. + +## Remarks + +By encapsulating context statistics into its own component, this symbol isolates data-fetching and presentation concerns from surrounding UI, making it reusable and easier to test. The refreshKey prop provides a lightweight mechanism for parent components to trigger a refresh in response to external events without reconstructing the component tree. + +## Notes + +- The actual implementation may fetch data from an API; ensure you handle loading and error states in the UI that hosts this component. +- If refreshKey changes frequently, consider memoization or batching to avoid unnecessary re-fetches. +- The snippet shows only the two props visible in the signature; the real component may accept additional props or rely on context. + +--- + +## buildGridCells +> **File:** `src/webapp/src/components/ContextStats.tsx` +> **Kind:** function + +```typescript +function buildGridCells(metrics: ContextMetricsResponse): (Category['key'] | null)[] +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `metrics` | [`ContextMetricsResponse`](../../../api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md) | — | + + +The function builds a fixed-size grid that represents how the tokens in a contextual window are distributed across predefined categories. It takes a ContextMetricsResponse, derives a token window, and divides that window into GRID_CELLS equal parts, then assigns each non-empty category a consecutive block of cells proportional to the category’s token count. Each category with tokens > 0 gets at least one cell, ensuring visibility in the grid; the resulting array is length GRID_CELLS and contains either a category key or null for unassigned cells. This abstraction is useful for rendering compact visualizations (such as a grid heatmap) of how much context space each category consumes, independent of the raw metrics calculation. + +## Remarks +This abstraction translates raw contextual metrics into a deterministic, UI-friendly grid representation. It uses a simple linear scaling (tokens per cell) with ceil rounding to guarantee visibility for any non-zero token count, accepting modest over-allocation in exchange for a stable layout. The trailing null entries effectively form a neutral “free” bucket for any rounding slack, and categories with zero tokens contribute no cells. + +## Notes +- The allocation is order-sensitive: categories are filled in the sequence defined by CATEGORIES, so changing that order changes the resulting grid. +- If GRID_CELLS is small or one category consumes most of the window, later categories may not appear in the grid, since processing stops once the grid is full. + + +--- + +## formatTokens +> **File:** `src/webapp/src/components/ContextStats.tsx` +> **Kind:** function + +```typescript +function formatTokens(n: number): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `n` | `number` | — | + +**Returns:** `string` + + +Converts a numeric token count into a compact, human-friendly string that uses a trailing 'k' to denote thousands. For values below 1000 it returns the plain integer as a string; for 1000 and above it expresses the value in thousands by dividing by 1000. If the resulting thousands value is 100 or more, it is rounded to a whole number and suffixed with 'k' (e.g., 125000 -> "125k"). Otherwise, it is shown with one decimal place (e.g., 12400 -> "12.4k"). The boundary around 100k yields a subtle nuance: 99,999 would format as "100.0k" due to the single-decimal rule before the integer-branch. + +## Remarks + +Centralizes token-count formatting so user interfaces display counts consistently without duplicating logic across components. It encapsulates the decision of when to show a decimal versus a whole-number thousands value, making future tweaks to formatting localized to this function. + +## Example + +```typescript +formatTokens(950); // "950" +formatTokens(1500); // "1.5k" +formatTokens(125000); // "125k" +formatTokens(99999); // "100.0k" (edge-case boundary demonstrates the decimal-before-integer rule) +``` + +## Notes + +- Be aware of the 100k boundary behavior: values just below 100000 may still render with a decimal if the thousands portion is less than 100, which can look inconsistent if you expect strict 100k+ formatting. Consider post-processing if strict bounds are required for a given UI. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/GabrielSequenceView.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/GabrielSequenceView.tsx.md new file mode 100644 index 0000000..d3640f7 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/GabrielSequenceView.tsx.md @@ -0,0 +1,71 @@ +# GabrielSequenceView.tsx + +> **Source:** `src/webapp/src/components/GabrielSequenceView.tsx` + +## Contents + +- [GabrielSequenceViewProps](#gabrielsequenceviewprops) +- [GabrielSequenceView](#gabrielsequenceview) +- [draw](#draw) + +--- + +## GabrielSequenceViewProps +> **File:** `src/webapp/src/components/GabrielSequenceView.tsx` +> **Kind:** interface + +```typescript +interface GabrielSequenceViewProps +``` + + +GabrielSequenceViewProps is the props contract for GabrielSequenceView. It lets you choose which sequence to render via source (SequenceSource), optionally force a refresh with refreshKey, control the square display size in pixels with size (defaulting to 200 to match the prior Three.js avatar), and receive the raw GabrielSequence after a successful load through onSequenceLoaded so the parent can derive a server-driven palette for UI accents. + +## Remarks +GabrielSequenceViewProps exists to decouple the rendering of a Gabriel sequence from the data-fetching concerns and to present a compact, typed surface for consumers. The refreshKey acts as a cache-busting knob; changing its value triggers a refetch of the sequence, which is useful after mutations such as sending a message. If you need visuals to react to the loaded data, implement onSequenceLoaded to receive the raw GabrielSequence and drive shared accents consistently across components. + +## Notes +- The size prop is square in pixels and defaults to 200 to align with the prior Three.js avatar. +- The onSequenceLoaded callback is optional; omit it if you don't need to surface the raw data. +- If you rely on refreshKey, ensure you provide a new value whenever you intend to trigger a fetch; passing the same value won't cause a refresh. + +--- + +## GabrielSequenceView +> **File:** `src/webapp/src/components/GabrielSequenceView.tsx` +> **Kind:** function + +```typescript +export function GabrielSequenceView( +``` + + +GabrielSequenceView is a React function component that renders a visualization of a Gabriel sequence derived from the provided source data. It accepts a source input, a refreshKey to force reinitialization, an optional size (default 200) to control the rendered dimension, and an onSequenceLoaded callback that fires when the sequence visualization has finished loading. + +## Remarks +GabrielSequenceView encapsulates the visualization concern for Gabriel sequences, exposing a simple prop surface so callers don't need to manage drawing logic directly. The refreshKey prop is a deliberate hook for reloading the visualization when the underlying data or view requirements change, without mutating props. The onSequenceLoaded callback provides a hook for post-load actions (e.g., analytics, tooltips, or synchronization with other components). This component typically renders into a container whose size is controlled by the size prop and may coordinate with responsive layout behavior elsewhere in the app. + +## Notes +- Be mindful that large or rapidly changing 'source' data may cause expensive re-renders; consider debouncing or memoization strategies to mitigate work. +- If the parent layout does not constrain width/height, provide a sensible size or wrap the component in a container that enforces layout to avoid overflow. + +--- + +## draw +> **File:** `src/webapp/src/components/GabrielSequenceView.tsx` +> **Kind:** function + +```typescript +const draw = (now: number) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `now` | `number` | — | + + +draw is the per-frame animation callback responsible for rendering the GabrielSequenceView canvas. On each invocation, it blends the colors for every pixel by linearly interpolating between two consecutive frames of the underlying sequence, based on the current position within a looping cycle derived from a shared start time and fixed frame duration. The interpolated RGB values are written into the canvas ImageData; the alpha channel is left unchanged per frame (initialized elsewhere). The resulting image data is then painted to the canvas and the callback re-schedules itself with requestAnimationFrame to continue the animation. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/GalacticTypewriter.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/GalacticTypewriter.tsx.md new file mode 100644 index 0000000..81d6ecc --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/GalacticTypewriter.tsx.md @@ -0,0 +1,108 @@ +# GalacticTypewriter.tsx + +> **Source:** `src/webapp/src/components/GalacticTypewriter.tsx` + +## Contents + +- [Props](#props) +- [GalacticTypewriter](#galactictypewriter) +- [toGalactic](#togalactic) + +--- + +## Props + +> **File:** `src/webapp/src/components/GalacticTypewriter.tsx` +> **Kind:** interface + +Configuration for the GalacticTypewriter component's two-pass typewriter animation. Use this interface to provide the text to render, to control timing (per-character speed and inter-pass pause, expressed in milliseconds), and to receive a completion callback when the animation finishes. + +## Remarks +This interface keeps animation-related configuration separate from rendering logic: charMs specifies the per-character delay applied to both the initial "galactic" pass and the subsequent "english" pass so pacing is consistent, while pauseMs controls the gap between those two passes. onDone is an optional hook consumers can use to react after the component's animated sequence completes. + +## Example +```typescript +// Typical usage in JSX + console.log('animation finished')} +/> +``` + +## Notes +- charMs and pauseMs are measured in milliseconds. +- Both charMs and pauseMs are optional; this interface does not define defaults — check the component implementation for the actual fallback values. +- onDone is optional; if provided, it will be invoked by the component when its animated sequence completes. + +--- + +## GalacticTypewriter + +> **File:** `src/webapp/src/components/GalacticTypewriter.tsx` +> **Kind:** function + +Renders a typewriter-style animated reveal of the provided text, exposing simple timing controls and an optional completion callback. Use this component when you want incremental, character-by-character text animation without wiring up timers yourself — control the per-character delay with charMs, the post-animation pause with pauseMs, and receive a notification via onDone. + +## Remarks +This is a small presentational component that encapsulates the typewriter animation behavior (timing, incremental reveal, and completion notification). It exists to keep callers focused on what to show and when to respond to completion events, rather than how to schedule character-by-character rendering. + +## Example +```typescript +// Typical usage in a React render function + console.log('Typewriter finished')} +/> +``` + +## Notes +- If the component unmounts before the animation completes, onDone may never be called; ensure parent logic accounts for that if needed. +- Very small charMs values can make the animation appear instantaneous and increase render activity; choose a value that balances smoothness and performance. +- Consider accessibility: screen readers may not announce progressively revealed text as expected — test with assistive tech and provide alternatives if necessary (for example, exposing the full text to assistive-only elements). + + +--- + +## toGalactic + +> **File:** `src/webapp/src/components/GalacticTypewriter.tsx` +> **Kind:** function + +```typescript +function toGalactic(ch: string): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `ch` | `string` | — | + +**Returns:** `string` + + +Converts a single character into its corresponding "galactic" glyph by looking up an uppercase key in the global GAL_MAP. If no mapping exists for the character (after being uppercased), the original character is returned unchanged — use this helper when rendering or transforming text into the galactic script for UI components such as a typewriter effect. + +## Remarks +This is a small, pure helper that centralizes the per-character mapping logic used by the typewriter component. It performs a case-insensitive lookup by uppercasing the input before checking GAL_MAP, so callers do not need to normalize character case themselves. The function expects that a GAL_MAP object is available in the surrounding scope. + +## Example +```typescript +// Given a GAL_MAP like: +// const GAL_MAP = { A: "⍺", B: "β" }; + +toGalactic('a'); // returns "⍺" (lookup uses 'A') +toGalactic('B'); // returns "β" +toGalactic('?'); // returns "?" (no mapping, falls back to original) +``` + +## Notes +- The function uppercases the input, so mapping is effectively case-insensitive. +- Passing a multi-character string or a grapheme cluster will attempt to lookup the whole string in GAL_MAP; it is intended for single characters. +- It does not mutate state and has no side effects; behavior depends on the contents of the global GAL_MAP. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/Markdown.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/Markdown.tsx.md new file mode 100644 index 0000000..985df4c --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/Markdown.tsx.md @@ -0,0 +1,275 @@ +# Markdown.tsx + +> **Source:** `src/webapp/src/components/Markdown.tsx` + +## Contents + +- [Props](#props) +- [Inline](#inline) +- [MarkdownImpl](#markdownimpl) +- [buildComponents](#buildcomponents) +- [expandText](#expandtext) +- [languageOf](#languageof) +- [remarkInlineEnrichments](#remarkinlineenrichments) +- [remarkUnliftEmptyOrderedList](#remarkunliftemptyorderedlist) +- [toAstNode](#toastnode) +- [toGalactic](#togalactic) + +--- + +## Props +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** interface + +```typescript +interface Props +``` + + +Props is the interface that defines the shape of the properties passed to the Markdown rendering component. It carries the text to render and an optional streaming flag that indicates whether the parent typewriter is actively revealing characters. When streaming is true, the renderer disables expensive post-processing steps (syntax highlighting, KaTeX math rendering, and the mermaid diagram renderer) to avoid costly updates during per-tick mutations, snapping back to full rendering once streaming ends. If you render static history messages or other non-streaming content, you can omit the streaming flag. + +## Remarks + +The streaming flag encapsulates a performance-oriented concern: it communicates to the Markdown renderer when it should trade richer visual processing for streaming responsiveness. By centralizing this control in Props, the component remains agnostic about how the content is produced, while callers that stream text can rely on the renderer to defer heavy work until the stream completes. This separation helps keep interactive typing smooth without intertwining rendering logic with content generation. + +## Example + +```ts +const sample: Props = { text: "Loading…", streaming: true }; +``` + +## Notes + +- If streaming is omitted, the component follows a non-streaming rendering path where all features may render upfront. +- This flag is intended for live-typing scenarios; passing an inappropriate value may cause unnecessary postponement or premature rendering of features like rich plugins. + +--- + +## Inline +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** type alias + +```typescript +type Inline = + | { type: 'text'; value: string } + | { type: 'galactic'; value: string } + | { type: 'hexChip'; value: string }; +``` + + +Inline defines the set of inline fragments used by the Markdown renderer in Markdown.tsx. It is a discriminated union with three variants: text, galactic, and hexChip. Each variant carries a string value that holds the content to be rendered. + +## Remarks +Inline provides a type-safe way to represent mixed inline content. The discriminated union makes rendering exhaustive and extensible: you can handle each kind explicitly and add new variants in the future without changing the surrounding code path. + +## Notes +- Adding a new variant requires updating all rendering branches and any serialization logic that consumes Inline[]. +- Keep the meaning of each variant distinct (e.g., treat 'text', 'galactic', and 'hexChip' differently in rendering) to avoid ambiguous output. + +--- + +## MarkdownImpl +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +function MarkdownImpl( +``` + + +MarkdownImpl is a React functional component that renders Markdown content provided via its text prop. The optional streaming flag, when true, enables progressive rendering as content becomes available; when false or omitted, the entire Markdown input is rendered in a single pass. + +## Remarks +MarkdownImpl encapsulates the Markdown rendering behavior behind a simple, predictable API. It separates concerns by letting callers supply text content without worrying about the parsing or rendering details. The streaming option offers a way to improve perceived latency in scenarios where Markdown content is generated or loaded incrementally. + +--- + +## buildComponents +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +function buildComponents(streaming: boolean): Components +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `streaming` | `boolean` | — | + +**Returns:** `Components` + + +Factory function buildComponents(streaming) returns a Components object that customizes how Markdown is converted to React elements for this app. It exposes renderers for anchors, horizontal rules, and code blocks, wiring in mermaid rendering, inline hex chips, and standard syntax-highlighted code, all controlled by the streaming flag to balance responsiveness with dynamic rendering. + +## Remarks + +By centralizing these renderers in one place, this abstraction isolates presentation concerns from content. Mermaid diagrams are only rendered when streaming has stopped to avoid heavy, incremental rendering during live reveal, and inline hex chips provide a compact visual cue for color values without altering the underlying text. The approach complements the surrounding Markdown rendering pipeline by ensuring consistent styling (md-link, md-hr, md-hex) across all blocks. + +## Notes + +- Mermaid rendering is gated by the streaming flag; if you rely on an instant diagram during streaming, it will not appear until streaming finishes. +- The code path that renders Mermaid diagrams relies on detecting language markers from className; if language detection fails, the block falls back to default rendering. +- Inline hex chips require a data-hex attribute on the code element to activate; without it, normal code rendering is used. + +--- + +## expandText +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +function expandText(value: string): RootContent[] +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `value` | `string` | — | + +**Returns:** `RootContent[]` + + +Expands a plain string into a structured AST-like sequence by recognizing two inline constructs: galactic blocks delimited by GAL_OPEN and GAL_CLOSE, and hex escapes defined by HEX_RE. It starts from a single text stage containing the input value, then, if the galactic sentinels are configured, splits the text into galactic and text fragments. Galactic fragments become galactic nodes, and text around them remains as text. After that, every text fragment is scanned for hex escapes and transformed into hexChip nodes while preserving surrounding text. The resulting sequence of Inline pieces is finally mapped to RootContent nodes via toAstNode and returned. When GAL_OPEN and GAL_CLOSE are not configured (or are empty), galactic expansion is skipped entirely and the input is treated as plain text. + + +--- + +## languageOf +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +function languageOf(className: string | undefined): string | null +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `className` | `string | undefined` | — | + +**Returns:** `string | null` + + +Extracts a programming language tag from a code block's className by looking for a token that starts with language- and returns the captured language name, or null if none is found. It is useful when rendering or processing code blocks where the language is conveyed via a CSS class rather than a data attribute, allowing you to determine the appropriate syntax highlighting without hard-coding mappings. + +## Remarks +This helper centralizes the language extraction logic behind a tiny, well-scoped regex. It tolerates multiple CSS classes and returns the first match found in the string. If the input is undefined or doesn't contain a language- token, it returns null rather than throwing; callers should handle the null case accordingly. + +## Example +```typescript +// Common usage: extract language from className +languageOf("foo language-python bar"); // "python" + +languageOf("language-js"); // "js" +``` + +## Notes +- Returns null if input is falsy or no language- token is present. +- If the input contains multiple language- tokens, only the first is used because the regex is executed once. + +--- + +## remarkInlineEnrichments +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +function remarkInlineEnrichments() +``` + + +remarkInlineEnrichments is a Remark-compatible plugin factory that traverses Markdown text nodes and replaces inline text with enriched content produced by expandText. It deliberately avoids mutating nodes that live inside code or inlineCode to preserve code samples. When expandText returns multiple replacements (or a non-text replacement), the plugin replaces the original text node by splicing the new nodes into the parent’s children and advances the visitor past the newly inserted nodes. If expandText yields a single text replacement, the original node is left as-is. Use this plugin when you want inline tokens or patterns to be expanded into richer Markdown/HTML constructs during the Markdown-to-AST transformation rather than as a post-render step. + +## Remarks +By performing inline enrichment at the AST level, this symbol isolates enrichment logic from rendering concerns and allows reuse across different Markdown processing pipelines. It also centralizes the safe handling of replacements and the skip-behavior, ensuring enrichments are applied deterministically while preserving code blocks. + +## Notes +- Mutates the AST in place; be mindful in multi-pass pipelines where other plugins rely on stable node indices. +- Relies on expandText; changes to its contract may require updates to this plugin. +- Only touches inline text nodes; block-level content and fenced/code blocks are unaffected. + +--- + +## remarkUnliftEmptyOrderedList +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +function remarkUnliftEmptyOrderedList() +``` + + +Transforms empty ordered lists in a Markdown Abstract Syntax Tree into a single paragraph containing the list's starting marker. It is intended for use in a Markdown processing pipeline to normalize documents by removing visually empty ordered lists while preserving their numbering context. + +Specifically, when visiting a node of type 'list' that is ordered, and every item is empty (an item with no children or with only empty paragraphs), the transformer replaces that list node with a paragraph node whose text is the list's start value followed by a period (for example, '1.'). The start value defaults to 1 when undefined. After replacement, the transformer signals the traversal to skip ahead to avoid re-processing the new node. + +## Remarks +This small transformer encapsulates a normalization rule into a reusable plugin, keeping the Markdown.tsx processing pipeline modular and predictable. It preserves the numbering context by emitting a textual marker ('start.') instead of rendering an empty list, which helps downstream renderers maintain layout consistency without introducing empty structural elements. It operates as a transformer in the Markdown AST pipeline and relies on the shape of list-related nodes (ordered lists with optional start) to decide when to apply the replacement. + +## Notes +- Empty means: a list item has no meaningful content (the item has no children), or all its children are paragraphs with no content. +- Only ordered lists with this emptiness predicate are transformed; non-empty lists are left intact. +- The transformation mutates the AST in place and uses traversal control (SKIP) to avoid re-processing the newly inserted paragraph. + +--- + +## toAstNode +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +function toAstNode(inline: Inline): RootContent +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `inline` | `Inline` | — | + +**Returns:** `RootContent` + + +toAstNode transforms a small Inline fragment into a RootContent node for the Markdown renderer. It handles plain text, galactic-styled text, and inline code: text becomes a text node, galactic becomes a styled span, and other types render as inlineCode with a data-hex attribute. + +## Remarks +This function centralizes the mapping from Inline variants to the concrete RootContent node shapes the renderer expects. The galactic variant is rendered as a span element (className 'gtw-galactic') with a data-galactic attribute, enabling styling without mutating the underlying text; the default path emits an inlineCode node with the original value stored in data-hex for potential styling or tooling use. + +## Notes +- The implementation uses an explicit cast to RootContent in the non-text branches; this relaxes type checking and may require updates if RootContent or Inline shapes evolve. +- The function relies on specific data attributes (data-galactic, data-hex) and a CSS class (gtw-galactic) for styling; changing these breaks downstream styling/tests. + +--- + +## toGalactic +> **File:** `src/webapp/src/components/Markdown.tsx` +> **Kind:** function + +```typescript +export function toGalactic(s: string): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `s` | `string` | — | + +**Returns:** `string` + + +toGalactic converts each character of the input string into its galactic counterpart by consulting a mapping named GAL_MAP. It iterates over the string, uppercases every character to perform a case-insensitive lookup, and appends the mapped value when available; otherwise it keeps the original character. The result is a new string that reflects the galactic substitutions while preserving characters that have no mapping. + +## Remarks +This function encapsulates the substitution logic behind the galactic transformation, allowing callers to apply a consistent mapping without duplicating iteration or lookup code. By normalizing keys with toUpperCase, it treats 'a' and 'A' the same, ensuring deterministic substitutions. It is robust to unmapped characters, leaving them intact in the output. + +## Notes +- Relies on a globally defined GAL_MAP; if GAL_MAP is undefined at runtime, this function will throw. +- If GAL_MAP contains a mapping to an empty string for a character, that character will be omitted from the result due to the nullish coalescing behavior. +- The implementation runs in O(n) time relative to the input string length and uses a straightforward per-character substitution. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/MemoryList.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/MemoryList.tsx.md new file mode 100644 index 0000000..0d702e5 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/MemoryList.tsx.md @@ -0,0 +1,152 @@ +# MemoryList.tsx + +> **Source:** `src/webapp/src/components/MemoryList.tsx` + +## Contents + +- [MemoryEditorProps](#memoryeditorprops) +- [MemoryListProps](#memorylistprops) +- [MemoryEditor](#memoryeditor) +- [MemoryList](#memorylist) +- [MemoryRow](#memoryrow) +- [stopToggle](#stoptoggle) + +--- + +## MemoryEditorProps +> **File:** `src/webapp/src/components/MemoryList.tsx` +> **Kind:** interface + +```typescript +interface MemoryEditorProps +``` + + +MemoryEditorProps defines the props contract for the Memory Editor UI used within the Memory List view. It provides a projectId to scope the editor, an optional initial MemoryDto to prepopulate fields when editing, and two callbacks—onSaved and onCancel—for communicating user actions back to the parent component. Use this interface to keep the editor decoupled from persistence and navigation concerns, enabling a single editor component to support both creation and editing workflows. + +## Remarks +MemoryEditorProps acts as a lightweight boundary between the UI and the data model. By accepting an optional initial MemoryDto, it supports both create and edit modes without the editor needing to know where data comes from. The onSaved callback lets the parent react to a successful save (for example, by refreshing a list or closing a dialog), while onCancel provides a clean way to exit editing without side effects. The projectId allows scoping or associating the memory with a particular project, and it may be null to indicate the absence of a specific project context. + +## Notes +- Do not mutate the initial MemoryDto object passed via initial; derive local editor state from it instead, to avoid mutating props. + +--- + +## MemoryListProps +> **File:** `src/webapp/src/components/MemoryList.tsx` +> **Kind:** interface + +```typescript +interface MemoryListProps +``` + + +MemoryListProps defines the props for a MemoryList component by specifying the scope of memories that should be displayed and persisted when new entries are added. The scope is expressed as MemoryScope and determines whether memories live in a user-wide space or within a specific project (e.g., { kind: 'user' } or { kind: 'project', projectId }). + +## Remarks + +By centralizing the memory scope in MemoryListProps, the same MemoryList component can be reused in both personal and project contexts without changing its implementation. MemoryScope provides a discriminated union that enforces correct shapes at compile time, helping prevent accidental mixing of scopes. This abstraction also makes it straightforward to introduce additional scopes in the future (e.g., group or organization) without touching the component's internal logic. + +## Example + +```typescript +// Example usage of MemoryListProps with user scope +const userMemoryListProps: MemoryListProps = { + scope: { kind: 'user' } +}; + +// Example usage with project scope +const projectMemoryListProps: MemoryListProps = { + scope: { kind: 'project', projectId: 'proj-123' } +}; +``` + +## Notes + +- Do not mutate the scope object; MemoryListProps.scope is a value that should be treated as immutable from the component's perspective. +- Switching scopes typically requires a new prop object to trigger data reload; rely on the parent to provide a new MemoryList instance or key when changing scope. + +--- + +## MemoryEditor +> **File:** `src/webapp/src/components/MemoryList.tsx` +> **Kind:** function + +```typescript +export function MemoryEditor( +``` + + +MemoryEditor is a React functional component that renders a form to edit a memory item for a specific project. Use it when adding or editing a memory inside MemoryList.tsx; it reports changes via onSaved and lets users cancel via onCancel. + +## Remarks +By encapsulating the editing UI, MemoryEditor keeps the MemoryList component focused on listing and orchestration rather than per-item editing details. It accepts an initial value to prefill the form, a projectId to scope changes, and callbacks to let the parent control persistence and navigation. This separation improves reusability: the same editor can be invoked for both creating a new memory and updating an existing one, with the parent providing the actual persistence strategy. + +## Notes +- Do not mutate the incoming initial prop; always work with a local state copy. +- Ensure onSaved is called after validation and with the correct memory shape; handle errors gracefully. +- If the editor is used within a modal/dialog, ensure onCancel reliably closes it and does not trigger navigation. + +--- + +## MemoryList +> **File:** `src/webapp/src/components/MemoryList.tsx` +> **Kind:** function + +```typescript +export function MemoryList( +``` + + +MemoryList is a React functional component that takes a scope prop and renders a list associated with that scope. Use MemoryList when you want a reusable UI unit to present items tied to a particular context, rather than inlining the list rendering in multiple pages. + +## Remarks +MemoryList serves as a presentation boundary between data provisioning and UI rendering. By encapsulating the list rendering in a dedicated component, it becomes reusable across parts of the UI that share the notion of a scope and its related memory items, and it can be styled independently from surrounding layout. + +## Example +```tsx + +``` + + +--- + +## MemoryRow +> **File:** `src/webapp/src/components/MemoryList.tsx` +> **Kind:** function + +```typescript +function MemoryRow( +``` + + +MemoryRow is a small, stateless React function component that renders a single memory item within the MemoryList UI. It accepts a memory object along with onEdit and onDelete callbacks and delegates user-initiated actions to these callbacks rather than mutating state directly. This keeps the row presentation separate from the list’s data management, allowing the parent to decide how edits and deletions are handled (e.g., opening editors, triggering API calls). Use MemoryRow when you want a reusable, bottom-level row renderer that can be composed across the memory list without pulling in list-wide logic. + +--- + +## stopToggle +> **File:** `src/webapp/src/components/MemoryList.tsx` +> **Kind:** function + +```typescript +const stopToggle = (fn: () => void) => (e: React.MouseEvent) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `fn` | `() => void` | — | + + +stopToggle is a tiny higher-order utility that converts a plain void callback into a React mouse-event handler. It guarantees that when the handler is invoked, the default action is prevented and the event does not bubble upward before the callback runs. Use it for UI controls inside MemoryList.tsx when a click should not trigger parent click handlers or navigation. + +## Remarks +By encapsulating preventDefault and stopPropagation, this function keeps event-management concerns out of business logic and promotes reuse across similar controls. It is a simple, dependency-light pattern that relies only on React's event type. It expects a callback with no event parameter; the wrapped function receives no arguments, and the event is consumed entirely by the wrapper. + +## Notes +- The wrapper always calls preventDefault and stopPropagation, so be mindful that it will suppress default actions and any ancestor click handlers for the event. +- If you need access to event data inside the callback, capture it outside or pass it through a differently designed wrapper; stopToggle does not pass the event to the inner callback. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/MemoryQuickSave.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/MemoryQuickSave.tsx.md new file mode 100644 index 0000000..9bdc139 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/MemoryQuickSave.tsx.md @@ -0,0 +1,72 @@ +# MemoryQuickSave.tsx + +> **Source:** `src/webapp/src/components/MemoryQuickSave.tsx` + +## Contents + +- [MemoryQuickSaveProps](#memoryquicksaveprops) +- [MemoryQuickSave](#memoryquicksave) +- [onKey](#onkey) + +--- + +## MemoryQuickSaveProps +> **File:** `src/webapp/src/components/MemoryQuickSave.tsx` +> **Kind:** interface + +```typescript +interface MemoryQuickSaveProps +``` + + +MemoryQuickSaveProps defines the props contract for the MemoryQuickSave UI component. It provides seedBody as the initial draft to pre-fill the memory's body, an optional projectId that scopes the save to a project (null hides the project scope in user-level flows), and an onClose callback invoked when the quick-save UI is dismissed. + +## Remarks +This interface is a lightweight data transfer object that encapsulates both UI state and behavior. It enables the MemoryQuickSave component to present a starting point to the user while respecting project-scoping rules, without hard-coding workflow decisions into the component itself. + +## Notes +- seedBody is a starting point for the memory body and may be edited by the user before saving. +- If projectId is null, the UI should hide or disable the project-scoping option, reflecting a user-scope context rather than a project-scoped workflow. +- onClose is a callback intended to be invoked when the quick-save UI is closed, allowing callers to perform cleanup or dismissal actions. + +--- + +## MemoryQuickSave +> **File:** `src/webapp/src/components/MemoryQuickSave.tsx` +> **Kind:** function + +```typescript +export function MemoryQuickSave( +``` + + +MemoryQuickSave encapsulates a focused quick-save flow for a seedBody associated with a projectId and offers an onClose callback to notify the caller when the dialog or action completes. Use MemoryQuickSave when you need a consistent, reusable quick-save experience across the UI instead of duplicating save/seeding logic in multiple components. + +--- + +## onKey +> **File:** `src/webapp/src/components/MemoryQuickSave.tsx` +> **Kind:** function + +```typescript +const onKey = (e: KeyboardEvent) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `KeyboardEvent` | — | + + +When activated, this function acts as a keyboard event handler that watches for the Escape key and triggers the close action. It provides a compact, reusable way to dismiss the related UI (such as a memory quick-save overlay) via keyboard input, rather than requiring a mouse interaction. + +## Remarks +Encapsulating the Escape-to-close behavior in onKey centralizes keyboard dismissal logic and keeps the close behavior decoupled from the specific event source. Attach this handler to a keyboard event (for example, onKeyDown) on the Memory Quick Save UI so that pressing Escape consistently closes the UI by invoking onClose. + +## Notes +- Uses e.key === 'Escape' to detect the Escape key; this is the most explicit and cross-browser-friendly check, but older code might use 'Esc' or keyCode. Prefer 'Escape' for clarity. +- Relies on onClose existing in scope; if onClose is undefined or not in scope, calling it will fail at runtime. +- Should be wired to an appropriate keyboard event (such as onKeyDown) on the element representing the UI; using the wrong event or propagating incorrectly could lead to missed dismissals or multiple triggers. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/Mermaid.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/Mermaid.tsx.md new file mode 100644 index 0000000..27b86fa --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/Mermaid.tsx.md @@ -0,0 +1,233 @@ +# Mermaid.tsx + +> **Source:** `src/webapp/src/components/Mermaid.tsx` + +## Contents + +- [MermaidProps](#mermaidprops) +- [ModalProps](#modalprops) +- [MermaidModule](#mermaidmodule) +- [MaximizeIcon](#maximizeicon) +- [Mermaid](#mermaid) +- [MermaidModal](#mermaidmodal) +- [loadMermaid](#loadmermaid) +- [nextId](#nextid) +- [onKey](#onkey) + +--- + +## MermaidProps +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** interface + +```typescript +interface MermaidProps +``` + + +MermaidProps specifies the props for the Mermaid diagram renderer component used in the React UI. It requires a single string field named source that contains the Mermaid diagram DSL to be rendered. Use this interface when wiring Mermaid.tsx into a UI and providing the diagram text from state, props, or data sources. + +## Remarks +By isolating the diagram text in a dedicated prop type, the rendering component remains focused on visualization while the rest of the application supplies the content. This design enables easy reuse of the Mermaid renderer for different diagrams and makes prop-validation straightforward at compile time. + +--- + +## ModalProps +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** interface + +```typescript +interface ModalProps +``` + + +ModalProps describes the properties consumed by a modal component that displays a Mermaid diagram. It requires an SVG string to render the diagram, a textual source describing the diagram (Mermaid source), and an onClose callback that is invoked to dismiss the modal. + +## Remarks +By grouping these three concerns, ModalProps externalizes the modal’s contract: the parent controls what is shown (svg and source) and how to close it (onClose) without needing to know internal rendering details. This promotes reuse: different components can render identical modals by supplying the same prop shape. + +## Example +```typescript +const demoProps: ModalProps = { + svg: '', + source: 'graph TD; A-->B;', + onClose: () => { /* close the modal */ } +}; +``` + +## Notes +- Sanitize or trust the svg content before injecting it into the DOM if it will be rendered as raw HTML to prevent XSS. +- Prefer a stable onClose reference to avoid unnecessary re-renders in consuming components (e.g., wrap in useCallback in React components). + +--- + +## MermaidModule +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** type alias + +```typescript +type MermaidModule = typeof import('mermaid') +``` + + +MermaidModule is a type alias for the module object produced by a dynamic import of the mermaid package. It allows you to type values or parameters that will hold the Mermaid library at runtime without performing a top-level import. + +## Remarks +"MermaidModule" isolates type information from the actual runtime import, enabling lazy loading and easier testing/mocking. It helps keep consumer code flexible against changes in the mermaid package, since the type reflects the module's shape rather than a concrete import site. + +## Example +```typescript +async function loadMermaidModule(): Promise { + const mod = await import('mermaid'); + return mod as MermaidModule; +} +``` + +## Notes +- MermaidModule is a type, not a value. At runtime there is no "MermaidModule" object; you must dynamic-import the module to obtain a usable value. + +--- + +## MaximizeIcon +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** function + +```typescript +function MaximizeIcon() +``` + + +MaximizeIcon is a small, stateless React component that renders an inline SVG representing a window-maximize action. It serves as a reusable presentational icon for UI controls that trigger expanding content to fill available space, typically used in toolbars or panels where a maximize action is available. + +## Remarks +This icon component centralizes the exact SVG used to depict the maximize action, promoting visual consistency across the UI and enabling easy color and size adjustments via CSS since the stroke uses currentColor. It is decorative (aria-hidden) and does not convey information on its own, so consumers should pair it with an accessible label (for example on the surrounding button) to communicate the action to assistive technologies. + +## Notes +- The SVG is marked aria-hidden to indicate it is purely decorative. If accessibility requires a spoken label, ensure the actionable container (e.g., a button) provides an explicit accessible name (aria-label or visible text). + + +--- + +## Mermaid +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** function + +```typescript +export function Mermaid( +``` + + +Mermaid renders a diagram from a Mermaid syntax string supplied through the source prop, acting as a lightweight adapter that embeds Mermaid charts into a React UI. You would reach for Mermaid when you want diagrams defined in Mermaid syntax to be rendered inline in your app rather than using static images. + +## Remarks +By encapsulating Mermaid rendering, this component decouples diagram logic from business UI, making theming and styling consistent across diagrams. It provides a single surface for diagram input (a Mermaid text block), so changes to how diagrams are rendered can be made here without touching all call sites. This isolation also makes it easier to swap or upgrade the underlying diagram-rendering strategy in one place. + +## Example +```tsx + +``` + +## Notes +- Invalid Mermaid syntax may fail to render or show an error depending on the environment. + +--- + +## MermaidModal +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** function + +```typescript +function MermaidModal( +``` + + +MermaidModal is a React functional component that renders a dismissible modal showing a Mermaid diagram. It accepts three props: svg (the rendered diagram content), source (the Mermaid source text), and onClose (callback invoked when the modal should close). Use MermaidModal when you want to present Mermaid diagrams in a focused overlay rather than inline, enabling users to view the diagram and its source in a dedicated view with an explicit close action. + +## Remarks +MermaidModal encapsulates the diagram presentation and the modal chrome so you can reuse this pattern across different parts of the app without duplicating layout or styling. By taking the diagram content (svg) and its source as props, it remains decoupled from the page-level layout and state, making it easy to render any Mermaid diagram in a consistent, accessible overlay. + +## Example +```tsx +// Typical usage within a React component + +``` + +## Notes +- Ensure onClose is provided and wired to dismiss the modal; without it, the dialog may be hard to close. +- If the diagram or source is large, consider proper overflow handling and responsive sizing to maintain usability. +- For accessibility, ensure the modal provides appropriate focus management and ARIA attributes, and that the close control is keyboard accessible. + + +--- + +## loadMermaid +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** function + +```typescript +function loadMermaid(): Promise +``` + +**Returns:** `Promise` + + +loadMermaid lazily loads the Mermaid diagram library and returns a promise that resolves to the library's default export, initializing Mermaid with a consistent, security-conscious configuration. It caches the import so subsequent calls reuse the same instance instead of re-importing Mermaid on every use. + +## Remarks + +This abstraction centralizes Mermaid integration into the web UI, ensuring a single initialization and consistent styling across components. By caching the loading promise, components can await loadMermaid() and render diagrams without worrying about repeated imports or multiple initializations, while the strict security settings help prevent unintended SVG script execution. + +## Notes + +- The function returns a Promise; callers must await or chain a .then to access the Mermaid API. +- If the dynamic import fails, the promise rejects; you may retry by invoking loadMermaid() again. +- The Mermaid module is configured only once per page load; subsequent calls reuse the same instance through the cached promise. + +--- + +## nextId +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** function + +```typescript +const nextId = () => `md-mermaid-$ +``` + + +nextId is a small, stateful helper that returns a unique string identifier for Mermaid-related elements by incrementing a shared counter and prefixing the result with 'md-mermaid-'. Use it whenever you need to attach a stable, collision-free ID to a new Mermaid diagram block or node in the DOM, instead of crafting IDs manually or relying on randomness. + +## Remarks +By centralizing ID generation in nextId, the code ensures consistent prefixes and avoids collisions when rendering multiple Mermaid blocks within a page. It relies on a mutable idCounter defined in the same module (or shared scope), so the sequence is predictable and fast. This approach is efficient for client-side rendering but means IDs are not guaranteed to be unique across separate runtimes (for example, across separate tabs or workers). + +## Notes +- It mutates a shared, module-scoped counter; IDs are sequential within a runtime, not globally across contexts. +- It requires idCounter to be initialized in the surrounding scope; otherwise the first call may yield NaN or throw. + +--- + +## onKey +> **File:** `src/webapp/src/components/Mermaid.tsx` +> **Kind:** function + +```typescript +const onKey = (e: KeyboardEvent) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `KeyboardEvent` | — | + + +This is a compact keyboard event handler that triggers a close action whenever the Escape key is pressed. It calls onClose() in response to the Escape key, so developers typically attach it to a modal, dialog, or other focusable container to provide a standard Escape-to-close behavior without duplicating the check across components. + +## Remarks +By isolating the Escape-key logic into onKey, the code reuses a single, testable closure for dismissal behavior, improving consistency across surfaces that support closing via Escape. It sits at the boundary between input handling and UI lifecycle, delegating the actual close action to onClose while handling the key comparison here. + +## Notes +- Ensure the listener is attached to a context that can receive keyboard events (document level or a focused element). +- Remember to unregister when appropriate to avoid memory leaks or multiple bindings. +- Some environments may not provide onClose in scope; ensure onClose is defined and accessible where onKey is used. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/ModeSelector.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/ModeSelector.tsx.md new file mode 100644 index 0000000..8f52129 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/ModeSelector.tsx.md @@ -0,0 +1,58 @@ +# ModeSelector.tsx + +> **Source:** `src/webapp/src/components/ModeSelector.tsx` + +## Contents + +- [ModeSelectorProps](#modeselectorprops) +- [ModeSelector](#modeselector) + +--- + +## ModeSelectorProps +> **File:** `src/webapp/src/components/ModeSelector.tsx` +> **Kind:** interface + +```typescript +interface ModeSelectorProps +``` + + +This interface defines the props contract for the ModeSelector component. It exposes the conversationId to scope the mode selection to a specific conversation, a value that represents the current GabrielMode or null to indicate the default (Chatty) behavior, an onChanged callback for reporting user-driven changes, and an optional disabled flag to render the control non-interactive when needed. + +## Remarks + +This abstraction decouples the UI from state management by standardizing how a caller supplies the current mode and receives updates. The initial mode is sourced from on-load data (ConversationResponse.mode), ensuring consistency with persisted conversation state. By tying the mode to GabrielMode and scoping it with a conversationId, this interface supports per-conversation configuration and straightforward wiring to higher-level state managers. + +## Example + +```tsx + { /* update local/global state with the next GabrielMode or null to reset to default */ }} + disabled={false} +/> +``` + +## Notes + +- value={null} denotes the default mode (treated as Chatty); supply a GabrielMode to override the default. +- onChanged is required and should update the parent state so the ModeSelector remains a controlled component. +- The disabled flag should be used to reflect non-editable states (e.g., during loading or when permissions prevent changes). + + +--- + +## ModeSelector +> **File:** `src/webapp/src/components/ModeSelector.tsx` +> **Kind:** function + +```typescript +export function ModeSelector( +``` + + +ModeSelector is an exported React function component from ModeSelector.tsx that destructures its props to include conversationId, value, onChanged, and disabled. The snippet provides only the function signature, so its rendering logic and side effects are not visible here. Developers would reach for it when they need a control tied to a specific conversation that exposes a value and an onChanged callback, with an optional disabled flag. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/ModelSelector.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/ModelSelector.tsx.md new file mode 100644 index 0000000..59f9545 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/ModelSelector.tsx.md @@ -0,0 +1,79 @@ +# ModelSelector.tsx + +> **Source:** `src/webapp/src/components/ModelSelector.tsx` + +## Contents + +- [ModelSelector](#modelselector) +- [labelFor](#labelfor) + +--- + +## ModelSelector +> **File:** `src/webapp/src/components/ModelSelector.tsx` +> **Kind:** function + +```typescript +export function ModelSelector() +``` + + +ModelSelector is a React functional component that renders an labeled dropdown to choose the active model from a server-provided list. On mount, it fetches the available models via fetchModels and stores them locally, showing a loading state until data arrives and surfacing non-Abort errors if the load fails. When a user selects a model, it updates the server with setActiveModel and then refreshes its local state with the server’s canonical response so the dropdown reflects exactly what the server stored. The currently selected option is derived from data.selected as provider::name, and the control is disabled while a save is in progress. A brief hint explains that changes apply on the next message and that the default option comes from appsettings.json. It also uses an AbortController to cancel the fetch if the component unmounts, avoiding potential leaks or state updates after unmount. + +## Remarks +ModelSelector encapsulates the UI and synchronization logic for choosing the active model behind a simple dropdown. It hides the asynchronous fetch and server-update details, coordinating with fetchModels, setActiveModel, and appsettings-backed defaults to present a coherent, server-backed selection experience. The component employs an optimistic-ish pattern by re-reading the server’s canonical state after a successful update, ensuring the UI mirrors the authoritative server data and reducing drift between client and server. + +## Notes +- AbortController is used to cancel the in-flight fetch on unmount, preventing memory leaks or state updates after the component is removed. +- If the initial load fails, the component renders an error state; if an update fails, the error is surfaced below the picker. +- The select is disabled during saving to prevent concurrent updates; the displayed value switches to the server-confirmed provider::name only after the update resolves. + +--- + +## labelFor +> **File:** `src/webapp/src/components/ModelSelector.tsx` +> **Kind:** function + +```typescript +function labelFor(m: ModelDto): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `m` | `ModelDto` | — | + +**Returns:** `string` + + +Computes a concise display label for a ModelDto used in the model selector UI. It assembles the model’s provider and name, reports the context window length in thousands of tokens, and conditionally appends annotations for a default model and for a compact threshold override. Use this function when rendering a list of models to present a stable, human-friendly summary rather than raw object properties. + +## Remarks + +Centralizes label formatting so the UI stays consistent across components. It encodes decisions about default status and compact-override visibility; when compactThreshold is present, the label includes an explicit ", compact @ XX%" hint. By deriving the final string in one place, changes to how models are presented require updating only this function. It also documents the intended semantics: "default" marks the preset default model, and the "compact" annotation signals that the model trims context to fit pricing or performance constraints. + +## Example + +```typescript +// Example: a non-default model with a 200k context and a compact threshold +const m: ModelDto = { + provider: 'grok', + name: 'grok-4.3', + isDefault: false, + contextWindowTokens: 200000, + compactThreshold: 0.75 +}; + +const label = labelFor(m); +// label is: "grok / grok-4.3 — 200k ctx, compact @ 75%" +``` + +## Notes + +- compactThreshold is optional; if null/undefined, the label omits the 'compact' annotation. +- contextWindowTokens is displayed as an integer number of thousands of tokens (e.g., 200000 => 200k). +- If isDefault is true, the label includes a trailing " (default)" tag. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/ProjectPicker.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/ProjectPicker.tsx.md new file mode 100644 index 0000000..ee235ab --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/ProjectPicker.tsx.md @@ -0,0 +1,291 @@ +# ProjectPicker.tsx + +> **Source:** `src/webapp/src/components/ProjectPicker.tsx` + +## Contents + +- [MenuAnchor](#menuanchor) +- [ProjectPickerProps](#projectpickerprops) +- [ProjectPicker](#projectpicker) +- [close](#close) +- [handleNew](#handlenew) +- [loadActiveProjectId](#loadactiveprojectid) +- [onDown](#ondown) +- [onKey](#onkey) +- [openSettings](#opensettings) +- [persistActiveProjectId](#persistactiveprojectid) +- [selectProject](#selectproject) +- [toggleMenu](#togglemenu) + +--- + +## MenuAnchor +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** interface + +```typescript +interface MenuAnchor +``` + + +MenuAnchor is a compact data contract that describes where a contextual menu should anchor itself within the UI. It exposes three numeric fields—top, left, and width—that together represent the anchor's vertical offset, horizontal offset, and visual width. This simple shape lets the rendering logic align a popover or dropdown with its triggering element without embedding measurements or layout logic in the menu component. + +## Remarks +Separating geometry from rendering reduces coupling between the ProjectPicker's measurement logic and the menu's presentation. It provides a stable contract that can be produced by different layout calculations (e.g., from DOM measurements or responsive logic) and consumed by the menu to render in the correct place. This pattern makes testing easier and supports reusability of the menu component across layouts. + +## Notes +- Ensure updates reflect current layout; stale values cause misalignment. +- This is a plain data container; there is no behavior. +- Coordinate space must be consistent between producer and consumer (e.g., same container viewport basis). + + +--- + +## ProjectPickerProps +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** interface + +```typescript +interface ProjectPickerProps +``` + + +ProjectPickerProps defines the props accepted by the ProjectPicker component. It carries the identity of the currently active project (as a string ID or null when none is active), a callback invoked when the user selects or clears the active project, an optional callback that provides the full active-project metadata whenever the picker resolves the active project, and an optional refreshKey that the parent can bump to trigger a refresh. + +## Remarks +Acts as a contract between the UI and its container, enabling the parent to coordinate routing and diagnostics behavior around the active project without the picker needing to know those rules. The onActiveProjectMetaChange callback surfaces rich metadata (ProjectResponse) to support higher-level decisions, such as which URL or view to open. The nullable activeProjectId supports both selection and explicit clearing, which is important for initial loads and user-driven resets. + +## Example +```typescript +// Example illustrating a props object that conforms to the interface +const exampleProps: ProjectPickerProps = { + activeProjectId: "proj-42", + onActiveProjectChange: (id: string | null) => { + // handle change + }, + onActiveProjectMetaChange: (meta: ProjectResponse | null) => { + // handle metadata changes + }, + refreshKey: 2 +} +``` + +## Notes +- The activeProjectId field is nullable to represent "no selection." Callers should handle null gracefully. +- Bumping refreshKey triggers the picker to refetch its data; avoid unnecessary or frequent changes to prevent excessive refreshes. + +--- + +## ProjectPicker +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +export function ProjectPicker( +``` + + +ProjectPicker is a React function component that renders a user interface for selecting or switching the active project within the web app. It takes the current activeProjectId and exposes callbacks for when the user changes the active project or when the active project's metadata changes, plus a refreshKey to trigger a data refresh. + +## Remarks +Design-wise, ProjectPicker acts as a boundary between page-level state and the domain concept of a project. By delegating the selection and metadata change events to the provided callbacks, it keeps the UI decoupled from how projects are loaded or what metadata is associated with a project. Additionally, refreshKey gives the parent a simple mechanism to force the picker to re-evaluate its data and UI state, which is useful when the available projects or their metadata may change outside the component's usual lifecycle. + +## Notes +- If refreshKey changes too frequently, the component may re-fetch data frequently; consider debouncing or batching updates. +- Ensure onActiveProjectChange remains a stable function (memoized) to prevent unnecessary re-renders of the picker. + +--- + +## close +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +const close = () => setMenu(null) +``` + + +Closes the ProjectPicker menu by resetting the component's menu state to null. This tiny helper centralizes the common action of dismissing the menu, so developers can attach it as an event handler (for example, to a Close button) without duplicating setMenu(null) in multiple places. + +## Remarks +Close acts as a single point of truth for how the ProjectPicker is dismissed. It decouples the open/close semantic from the exact UI markup, enabling reuse wherever the menu should be hidden and simplifying future changes to the close behavior. It also helps keep unit tests focused on the close behavior rather than the surrounding rendering code. + +--- + +## handleNew +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +const handleNew = async () => +``` + + +Prompts the user for a new project name and, if the input is non-empty after trimming, creates the project via ProjectsService.postApiProjects. + +On success, it increments the local refresh counter, persists the new project's id as the active project, and calls onActiveProjectChange(project.id) and onActiveProjectMetaChange?.(project); on failure, it surfaces the error via notifyError with the message 'Failed to create project.' + +--- + +## loadActiveProjectId +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +export function loadActiveProjectId(): string | null +``` + +**Returns:** `string | null` + + +Reads the active project identifier from localStorage under the ACTIVE_PROJECT_KEY and returns it as a string. If the key is missing or localStorage access throws, it returns null instead of raising an error. + +## Remarks + +By centralizing the retrieval and guarding against environments where localStorage is unavailable, this function allows the UI to gracefully handle the absence of an active project without crashing. It returns a string when a value is present and non-null; otherwise, callers should treat null as indicating that no active project is currently selected. + +## Notes + +- Returns null when the item is absent or when localStorage access fails; callers must handle null. +- Safe for environments without a DOM (e.g., during server-side rendering); the catch block prevents exceptions from propagating. + +--- + +## onDown +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +const onDown = (e: globalThis.MouseEvent) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `globalThis.MouseEvent` | — | + + +OnDown is a mouse-down event handler that closes the project picker when the user clicks outside of the dropdown UI. It resolves the event target and, if the click did not occur inside the menu (menuRef) or the trigger element (triggerRef), it calls close() to hide the panel. This centralizes outside-click dismissal logic and prevents accidental closures when interacting with the dropdown itself. + +--- + +## onKey +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +const onKey = (e: globalThis.KeyboardEvent) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `globalThis.KeyboardEvent` | — | + + +The onKey function is a focused keyboard event handler that detects when the Escape key is pressed and invokes close(). Use this to implement Escape-to-close behavior for UI elements like a modal or picker, ensuring a consistent user experience without requiring mouse interaction. + +## Remarks +This abstraction centralizes Escape-key handling and delegates the actual closing action to close(), which keeps the component's input handling separate from its dismissal logic. It also helps keep the UX consistent across components that should be dismissible via Escape. If you integrate this into different environments (e.g., React synthetic events), ensure the event type aligns with the host framework or adjust the signature accordingly. + +## Notes +- This relies on the standard KeyboardEvent.key value "Escape"; in very old browsers, you might also encounter "Esc". +- The handler assumes close() is in scope and callable; ensure it is defined in the consumer's context. +- Attach this handler to the appropriate key event stream (commonly keydown) so Escape is captured reliably and before focus shifts. + +--- + +## openSettings +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +const openSettings = () => +``` + + +Opens the settings view for the currently active project by navigating to the project-specific settings route. It first checks for an active project and returns early if none is present, ensuring no navigation occurs in that state. When a project is active, it routes to /p/{id}/settings using a URL-encoded project id to guarantee a valid path regardless of special characters in the id. + +## Remarks +This function centralizes the navigation logic from the Project Picker to the per-project settings screen, so callers don’t need to know the exact URL structure. Encoding the project id with encodeURIComponent prevents malformed URLs when IDs contain special characters and keeps the route pattern consistent. The early guard against a missing active project protects against unexpected navigations in edge UI states. + +## Notes +- If activeProject exists but its id is absent or not a string, the resulting URL may be malformed; ensure activeProject.id is a string and truthy before calling openSettings. +- The function relies on the external activeProject and navigate collaborators; changes to those shapes or imports may require updates to this helper. + +--- + +## persistActiveProjectId +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +function persistActiveProjectId(id: string | null) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `id` | `string | null` | — | + + +Persist the active project identifier to the browser's localStorage. This small helper encapsulates the storage interaction so the rest of the UI can save or clear the user's last-selected project without duplicating localStorage logic. When called with a non-null string, it writes the id under ACTIVE_PROJECT_KEY; when called with a falsy value (such as null or an empty string), it removes that key. All operations are wrapped in a try/catch that quietly ignores errors to avoid impacting the UI if storage is unavailable. + +## Remarks +Centralizes persistence concerns for the UI's project selection, ensuring a single, consistent storage key is used and isolating storage details from callers. The abstraction makes it easy to swap storage strategies later and guarantees that a failed storage attempt won't crash the UI. + +## Notes +- An empty string will be treated as falsy and cause removal of the key; callers should pass null or a non-empty string to persist a value. +- Silent failures mean persisted state may be missing when localStorage is unavailable or blocked; callers should not rely on persistence for correctness. + +--- + +## selectProject +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +const selectProject = (id: string) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `id` | `string` | — | + + +selectProject updates the active project context when a user selects a project from the ProjectPicker. It persists the chosen id, notifies listeners about the change via onActiveProjectChange, optionally passes the selected project's metadata to onActiveProjectMetaChange, and then closes the picker by clearing the menu. + +## Remarks +By centralizing the sequence of side effects triggered by a project selection, this symbol acts as a coordination point between persistence, event dispatch, and UI state. It relies on the presence of onActiveProjectMetaChange and the projects collection to fetch and relay metadata; if no matching project is found, it passes null to the meta callback to signal the absence. + +## Notes +- If the selected id is not found in the projects array, onActiveProjectMetaChange will be invoked with null. +- The function ends by closing the ProjectPicker with setMenu(null); calling from non-UI contexts may produce unexpected UI behavior. + +--- + +## toggleMenu +> **File:** `src/webapp/src/components/ProjectPicker.tsx` +> **Kind:** function + +```typescript +const toggleMenu = () => +``` + + +toggleMenu toggles the ProjectPicker's dropdown: if the menu is currently visible, it closes it by clearing the menu state; otherwise it computes the trigger button's position via triggerRef.current.getBoundingClientRect and opens the menu anchored beneath the trigger, using the trigger's width to size the menu. This keeps the popup aligned with its trigger and reuses the same function for both opening and closing. + +## Remarks +By centralizing open/close and placement logic, this function keeps the popup behavior cohesive and easy to reuse in the ProjectPicker. It relies on a ref to the trigger DOM element and a menu state to express visibility and geometry, illustrating a straightforward imperatively-driven popup pattern in React. The positioning formula top: rect.bottom + 4, left: rect.left, width: rect.width ties the menu to the trigger's current size and position, preserving alignment when the trigger moves or resizes. + +## Notes +- Guard clause ensures no action if the trigger button isn't mounted. +- No handling for viewport clipping or automatic reflows; if the menu would overflow the viewport, it isn't adjusted here. +- The function assumes the surrounding container allows absolute positioning based on viewport coordinates. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/Sidebar.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/Sidebar.tsx.md new file mode 100644 index 0000000..6e4e292 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/Sidebar.tsx.md @@ -0,0 +1,92 @@ +# Sidebar.tsx + +> **Source:** `src/webapp/src/components/Sidebar.tsx` + +## Contents + +- [MenuState](#menustate) +- [Sidebar](#sidebar) +- [loadSidebarCollapsed](#loadsidebarcollapsed) + +--- + +## MenuState +> **File:** `src/webapp/src/components/Sidebar.tsx` +> **Kind:** interface + +```typescript +interface MenuState +``` + + +MenuState defines the minimal shape of a menu's identity and positioning used inside the Sidebar. It captures an id and positional offsets (top and right) so components can render or reposition a contextual menu deterministically without depending on DOM specifics. + +## Remarks + +MenuState acts as a lightweight value object that decouples layout concerns from business logic. By centralizing id/top/right into a single contract, components that render menus and those that trigger them can share a consistent representation, reducing duplication and mistakes when computing positioning. + +## Example + +```typescript +const currentMenu: MenuState = { id: 'settings', top: 110, right: 20 }; +``` + +## Notes + +- Mutating MenuState instances can cause subtle UI drift; prefer creating new object instances to reflect updates. +- Keep top/right as pixel values in the same coordinate system used by your styling (e.g., px). + +--- + +## Sidebar +> **File:** `src/webapp/src/components/Sidebar.tsx` +> **Kind:** function + +```typescript +export function Sidebar() +``` + + +Renders a collapsible left-hand navigation panel that lists conversations scoped to the active project and exposes actions to switch between conversations, rename items, and access per-conversation controls. It derives the active conversation from the URL, fetches the project-scoped conversations via ConversationsService, and manages local UI state (collapse state, editing mode, quick-action menus) to drive navigation and interaction without leaking UI concerns into parents. + +## Remarks +Encapsulates the UI concerns around listing and selecting conversations for diagnostics. It coordinates data fetching, derived from the active project, with URL-driven navigation to keep the view in sync with the browser state. It also handles global interactions (Escape-to-close, outside-click to dismiss menus) and UI preferences (collapsed state) to deliver a consistent, accessible experience without leaking layout or data retrieval concerns elsewhere. + +## Notes +- The component relies on ConversationsService for its data; changes to the API surface or data shape may require corresponding updates here. +- It persists the collapsed state to localStorage and gracefully handles environments where localStorage is unavailable or disabled (errors are caught and ignored). + +--- + +## loadSidebarCollapsed +> **File:** `src/webapp/src/components/Sidebar.tsx` +> **Kind:** function + +```typescript +function loadSidebarCollapsed(): boolean +``` + +**Returns:** `boolean` + + +Returns a boolean that indicates whether the app's sidebar should start in a collapsed state by reading a saved flag from localStorage (SIDEBAR_STORAGE_KEY). If the key is missing, the value is treated as collapsed by default; a value of '1' maps to true, and any other value maps to false. If accessing storage fails, it gracefully falls back to true to align with the EchoHub-style collapsed default. Use this helper to decide the initial rendering state of the sidebar without duplicating storage logic. + +## Remarks + +This function centralizes the persistence rule for the sidebar's open/closed state, decoupling rendering from storage concerns and ensuring a consistent default of collapsed to match the EchoHub pattern. It guards against runtime storage exceptions via a try/catch, so UI initialization remains stable even when localStorage is unavailable or blocked. Placing this logic here makes it easy to adjust the default or the storage key in one place without touching rendering code. + +## Example + +```typescript +// Example: apply a collapsed state class based on the persisted preference +
+ {/* sidebar contents */} +
+``` + +## Notes + +- This function relies on the browser's localStorage; in non-browser environments (e.g., server-side rendering) or when storage is disabled, it will return true by default. If you need a different persistence mechanism, consider injecting a storage provider or gating this call behind client-only rendering. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/SkinPicker.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/SkinPicker.tsx.md new file mode 100644 index 0000000..d1f42af --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/SkinPicker.tsx.md @@ -0,0 +1,117 @@ +# SkinPicker.tsx + +> **Source:** `src/webapp/src/components/SkinPicker.tsx` + +## Contents + +- [SkinPickerProps](#skinpickerprops) +- [SkinPicker](#skinpicker) +- [handlePalette](#handlepalette) +- [handlePattern](#handlepattern) + +--- + +## SkinPickerProps +> **File:** `src/webapp/src/components/SkinPicker.tsx` +> **Kind:** interface + +```typescript +interface SkinPickerProps +``` + + +SkinPickerProps defines the props contract for the SkinPicker UI component that lets users choose a skin pattern and a palette. Both selections are represented as nullable strings, signaling an explicit 'not selected' state. The interface requires an onChange callback that receives the next combined selection, allowing the consumer to update the app state in one step. An optional onReroll callback, when provided, causes a reroll action to render next to the pickers; note that rerolling does not clear the pins for pattern or palette, it only changes seed-derived dimensions. A disabled flag can disable interaction during operations like saving. + +## Remarks +This interface serves as a clean boundary between presentation and state management for skin selection. It centralizes the idea that a SkinPicker mutates or reports the next selection via onChange while optionally offering a non-destructive reroll action via onReroll. By using nullable fields, it accommodates situations where a user hasn't pinned a particular attribute yet, leaving the rest unchanged. + +## Example +```typescript +// Example usage of SkinPickerProps +const example: SkinPickerProps = { + pattern: null, + palette: null, + onChange: (next) => { + // apply next to your state-tracking skin + }, + onReroll: () => { + // trigger a reroll; pins remain intact + }, + disabled: false +}; +``` + +## Notes +- Null values indicate 'no pin'; consumer code should handle them gracefully and avoid assuming non-null values. +- Provide onReroll only if your UI actually renders a reroll control; otherwise, omit this prop to keep the API surface minimal. +- When disabled is true, ensure all interactive elements are non-editable and focus is managed for accessibility. + +--- + +## SkinPicker +> **File:** `src/webapp/src/components/SkinPicker.tsx` +> **Kind:** function + +```typescript +export function SkinPicker( +``` + + +SkinPicker is a React component that renders a UI for selecting a skin/pattern from a supplied palette. It delegates state management to its parent: when a user chooses a new pattern, it calls onChange with the selection; onReroll requests a new random option; and disabled toggles interactivity. + +## Remarks +SkinPicker serves as a reusable, presentational control that encapsulates the skin-selection UX. It keeps business logic out of the UI by relying on callbacks (onChange, onReroll) and a controlled pattern prop, enabling consistent behavior across pages and making the underlying rendering easy to swap without touching consumer code. + +--- + +## handlePalette +> **File:** `src/webapp/src/components/SkinPicker.tsx` +> **Kind:** function + +```typescript +const handlePalette = (value: string) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `value` | `string` | — | + + +handlePalette updates the skin configuration by calling onChange with the current pattern and the new palette value; if the user clears the selection (value is an empty string), the palette is represented as null. + +## Remarks +Acts as a focused handler for palette changes within the SkinPicker component. It preserves the existing pattern while updating only the palette, delegating state management to the onChange callback. Representing an empty selection as null standardizes the payload, avoiding ambiguous empty strings in downstream logic. + +## Notes +- Relies on the surrounding scope to provide a defined pattern value and a working onChange callback; undefined pattern could lead to unexpected payloads. +- Passing null for no palette assumes downstream logic accepts null; if null is not supported, adjust the shape or validation accordingly. + +--- + +## handlePattern +> **File:** `src/webapp/src/components/SkinPicker.tsx` +> **Kind:** function + +```typescript +const handlePattern = (value: string) => +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `value` | `string` | — | + + +handlePattern is a small change-handler that normalizes the user input for the pattern field and forwards it to the shared onChange callback. It passes along the current palette and sets pattern to null when the input is empty, or to the literal value when non-empty. This is intended for use as the input change handler in SkinPicker.tsx so downstream logic consistently sees a string|null for pattern rather than an empty string. + +## Remarks +handlePattern relies on a closure over onChange and palette from the surrounding component. It centralizes the normalization of the pattern value, ensuring the exposed update shape remains stable across calls and removing the need for callers to handle empty strings themselves. + +## Notes +- The function does not mutate local state itself; it only invokes onChange with a new payload. If onChange is asynchronous, callers should handle potential race conditions at a higher level. +- Empty input becomes null, which is meaningful to downstream logic that distinguishes “no pattern” from an empty string. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/StreamingText.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/StreamingText.tsx.md new file mode 100644 index 0000000..916bdad --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/StreamingText.tsx.md @@ -0,0 +1,91 @@ +# StreamingText.tsx + +> **Source:** `src/webapp/src/components/StreamingText.tsx` + +## Contents + +- [Props](#props) +- [StreamingText](#streamingtext) +- [tick](#tick) + +--- + +## Props +> **File:** `src/webapp/src/components/StreamingText.tsx` +> **Kind:** interface + +```typescript +interface Props +``` + + +Props defines the property contract for StreamingText: the text to render, whether the SSE-driven in-flight reveal should animate, and optional visuals such as a caret indicator and the galactic-cipher reveal. + +## Remarks +Encapsulating these options in an interface gives StreamingText a clean contract and lets its parent control when animation starts and which embellishments are shown. The animate flag is captured at mount and, once the in-flight typewriter begins, toggling animate later does not abort it — it always completes. The optional caret and galactic fields let callers opt into subtle UI modes without expanding the core rendering logic. + +## Example +```typescript +const example: Props = { + text: 'Connecting to server...', + animate: true, + caret: true, + galactic: true +}; +``` + +## Notes +- The animate property is captured at mount and will not be interrupted by changing it mid-flight. +- Optional props default to undefined; treat as disabled if not provided. +- The galactic option is opt-in and adds a leading-edge reveal; callers should ensure the consuming rendering path supports this mode if used. + +--- + +## StreamingText +> **File:** `src/webapp/src/components/StreamingText.tsx` +> **Kind:** function + +```typescript +export function StreamingText( +``` + + +StreamingText is a React component that renders the provided text with optional streaming animation and presentation cues. It accepts a text string and three boolean toggles: animate, caret, and galactic, which control whether the text reveals itself progressively, whether a caret cursor is shown, and whether a galactic styling variant is applied. Use this component when you want to present text in a dynamic, newsfeed-like or chat-like fashion without implementing the animation and cursor UI yourself; it also lets you switch to a static rendering or a themed look by adjusting props. + +## Remarks +StreamingText encapsulates a common UI pattern—progressive text reveal—so callers can opt into animation and decorations in one place instead of scattering this logic throughout the codebase. It serves as a lightweight abstraction over the visual behaviors of text streams, allowing teams to maintain consistent UX by toggling simple props rather than wiring up bespoke animation code in every usage site. + +## Example +```typescript + +``` + + +--- + +## tick +> **File:** `src/webapp/src/components/StreamingText.tsx` +> **Kind:** function + +```typescript +function tick(now: number) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `now` | `number` | — | + + +tick is an internal animation helper used by StreamingText.tsx to progressively reveal a target string over time. It drives a frame-based reveal loop, updating internal counters (gal and en) and scheduling subsequent frames via requestAnimationFrame until the entire text is shown. The reveal cadence is computed from a backlog-aware rate, with a separate path for Galactic mode that can adjust how many characters are exposed per step. This function encapsulates the timing and pacing logic separate from render cycles, ensuring a smooth, frame-rate-independent streaming experience. + +## Remarks +tick centralizes the time-based pacing for the streaming reveal, decoupling character emission from React re-renders and letting the UI remain responsive even under frame drops. It mutates state through refs and only signals progress when something actually advances (via bump), which minimizes unnecessary renders while preserving correct timing. The exact pacing is governed by constants (BASE_RATE, MAX_RATE, SPEEDUP_PER_BACKLOG_CHAR) and Galactic behavior (GALACTIC_LEAD), so tweaking those values directly influences the perceived speed of the streaming text. + +## Notes +- The animation is driven by the browser's requestAnimationFrame; in environments with throttled or suspended tabs, the reveal pace may slow or pause. +- The function mutates shared refs (e.g., c.gal, c.en) and relies on a single RAF loop; introducing concurrent callers or multiple loops can produce unexpected pacing unless carefully coordinated. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/components/ThinkingPulse.tsx.md b/docs/auriondocs/Code/src/webapp/src/components/ThinkingPulse.tsx.md new file mode 100644 index 0000000..c040b75 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/components/ThinkingPulse.tsx.md @@ -0,0 +1,108 @@ +# ThinkingPulse.tsx + +> **Source:** `src/webapp/src/components/ThinkingPulse.tsx` + +## Contents + +- [PulseState](#pulsestate) +- [Bars](#bars) +- [ThinkingPulse](#thinkingpulse) +- [buildState](#buildstate) + +--- + +## PulseState +> **File:** `src/webapp/src/components/ThinkingPulse.tsx` +> **Kind:** interface + +```typescript +interface PulseState +``` + + +PulseState is a compact data contract that groups the color palette, the visual pattern, and a flexible set of runtime parameters into a single object consumed by the pulse rendering logic. It lets ThinkingPulse.tsx render different visual variants by swapping palettes or patterns while keeping the rendering code unchanged. The params field is typed as unknown to permit future extension without altering the interface. + +## Remarks +PulseState isolates styling, behavior, and runtime knobs from the rendering implementation. By pairing a Palette with a Pattern, it enables theming and behavior experimentation without touching rendering internals, supporting reuse of the same rendering path for multiple visual variants. + +## Example +```typescript +// Example usage: create a PulseState by wiring together a Palette, a Pattern, and params. +const palette: Palette = somePaletteInstance; // Palette implements Count and indexer[] +const pattern: Pattern = somePatternInstance; // Pattern interface +const state: PulseState = { + palette, + pattern, + params: { speed: 1.0, density: 0.8 } +}; +``` + +## Notes +- Because params is of type unknown, callers must narrow its type before accessing any properties to avoid runtime errors. + +--- + +## Bars +> **File:** `src/webapp/src/components/ThinkingPulse.tsx` +> **Kind:** function + +```typescript +function Bars( +``` + + +Bars is a React function component in ThinkingPulse.tsx that takes a props object with seed and paletteStops. The provided snippet shows only the function signature, so the exact rendering logic and output aren’t visible here. A developer would reach for Bars when building or adjusting the ThinkingPulse UI portion that relies on a seed value and a set of palette stops to drive its rendering. + +--- + +## ThinkingPulse +> **File:** `src/webapp/src/components/ThinkingPulse.tsx` +> **Kind:** function + +```typescript +export function ThinkingPulse( +``` + + +ThinkingPulse is a React functional component that renders a pulsing visual cue for ongoing work. It accepts a destructured props object with seed and paletteStops, which the implementation uses to determine the pulse’s appearance. The seed enables deterministic variation across renders, while paletteStops configures the color progression of the pulse. Use ThinkingPulse when you want a compact, reusable loading indicator that can be themed and made deterministic without duplicating animation code. + +## Remarks + +ThinkingPulse provides a single abstraction for a common UI pattern: a small, animated indicator that communicates activity without blocking layout. It is designed as a presentational component that can be dropped into any part of the UI and customized via its props rather than by host components. While the exact rendering mechanism (CSS or SVG) is abstracted away, the seed and paletteStops knobs ensure you can produce varied yet reproducible visuals across pages. + +## Notes + +- If you need deterministic visuals across navigations or re-renders, pass a stable seed value; changing the seed will alter the pulse's appearance. +- Ensure the paletteStops argument matches the component's expected shape (colors/color stops) to avoid runtime issues; when in doubt, rely on the project's established color palette. + +--- + +## buildState +> **File:** `src/webapp/src/components/ThinkingPulse.tsx` +> **Kind:** function + +```typescript +function buildState(seed: number, paletteStops?: readonly RGB[]): PulseState +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `seed` | `number` | — | +| `paletteStops` | `readonly RGB[]` | — | + +**Returns:** `PulseState` + + +Generates a deterministic PulseState from a numeric seed and optional palette stops. It constructs an RNG with mulberry32(seed), then deterministically selects a seed palette, a visual pattern, and per-pattern parameters in that order. If paletteStops is provided and non-empty, the returned palette becomes a 'sequence' palette whose stops are a shallow copy of paletteStops, allowing the indicator's colors to track the server-driven Gabriel Sequence palette instead of the local pulse palette. The function returns an object with palette, pattern, and params, ready for rendering by ThinkingPulse or related components. + +## Remarks +Consolidates state generation for ThinkingPulse visuals behind a simple seed-based API. This makes visuals reproducible across runs and aligns client output with server-driven color palettes when paletteStops is provided. It relies on Avatar.tsx's RNG order to preserve the legacy mapping between seed, palette, and pattern. + +## Notes +- The palette is chosen by the RNG, and if paletteStops is provided and non-empty, the palette is replaced with a sequence palette built from those stops; otherwise the seed-derived palette is used. +- paletteStops is shallow-copied into a new array to prevent external mutation from affecting the generated state. +- The function uses BARS and the picked pattern's init to populate params, tying the state to the chosen pattern and its parameter space. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/layouts/AuthLayout.tsx.md b/docs/auriondocs/Code/src/webapp/src/layouts/AuthLayout.tsx.md new file mode 100644 index 0000000..761abb6 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/layouts/AuthLayout.tsx.md @@ -0,0 +1,18 @@ +# AuthLayout + +> **File:** `src/webapp/src/layouts/AuthLayout.tsx` +> **Kind:** function + +```typescript +export function AuthLayout() +``` + + +AuthLayout is a lightweight React layout component used for authentication-related pages. It renders an Outlet for nested routes (e.g., login, signup, forgot-password) and injects a preconfigured ToastContainer so that toast notifications appear consistently in the bottom-right with a dark theme and a 4-second auto-close. Use this layout to ensure all auth screens share the same frame and toast behavior without duplicating configuration on every page. + +## Remarks +AuthLayout centralizes the concerns of route composition and user feedback within the authentication area. By providing a single ToastContainer, it guarantees a uniform notification experience across all auth screens while keeping each page focused on its specific content. This separation of concerns also makes it easy to adjust branding (position, theme, timeout) in one place without touching individual routes. + +## Notes +- The ToastContainer is configured with a fixed position (bottom-right), newestOnTop, and a dark theme. If you need a different look for a sub-section, you may override defaults at the call site or replace the container. +- If AuthLayout is mounted in multiple parts of the app, multiple ToastContainer instances may be created. Ensure this aligns with your UX expectations to avoid duplicate or conflicting toasts. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/layouts/MainLayout.tsx.md b/docs/auriondocs/Code/src/webapp/src/layouts/MainLayout.tsx.md new file mode 100644 index 0000000..04cae95 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/layouts/MainLayout.tsx.md @@ -0,0 +1,18 @@ +# MainLayout + +> **File:** `src/webapp/src/layouts/MainLayout.tsx` +> **Kind:** function + +```typescript +export function MainLayout() +``` + + +MainLayout is the top-level layout component that provides the app's chrome by wrapping a persistent Sidebar, a routed content region via Outlet, and a global ToastContainer for toast notifications. Use this component as the root layout for your routes to ensure a consistent navigation chrome and a centralized toast area across pages. + +## Remarks +MainLayout centralizes layout concerns so page components can focus on content. It wires together three collaborators—Sidebar for navigation, Outlet for embedding nested routes, and ToastContainer for user feedback—so they can evolve separately without duplicating chrome code. + +## Notes +- The ToastContainer is configured with autoClose={4000} and draggable={false}; changing these will affect the user experience for toasts. +- The wrapper uses className="app" and main-col; ensure the corresponding CSS exists to preserve layout and styling across pages. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/lib/notify.ts.md b/docs/auriondocs/Code/src/webapp/src/lib/notify.ts.md new file mode 100644 index 0000000..a565384 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/lib/notify.ts.md @@ -0,0 +1,136 @@ +# notify.ts + +> **Source:** `src/webapp/src/lib/notify.ts` + +## Contents + +- [formatError](#formaterror) +- [notifyError](#notifyerror) +- [notifyInfo](#notifyinfo) +- [notifySuccess](#notifysuccess) + +--- + +## formatError +> **File:** `src/webapp/src/lib/notify.ts` +> **Kind:** function + +```typescript +export function formatError(e: unknown, fallback = 'Something went wrong.'): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `unknown` | — | +| `fallback` | — | `'Something went wrong.'` | + +**Returns:** `string` + + +Converts an unknown error into a user-friendly string by first extracting details from ApiError bodies, then falling back to a plain Error message, and finally using a provided fallback for non-errors. Use this to normalize error messaging in UI layers where errors may originate from API responses or generic exceptions. + +## Remarks +This function centralizes error message formatting across different error shapes (ApiError vs. generic Error), reducing duplication and ensuring consistent user feedback. By preferring body.detail over body.title, it surfaces the most specific API-provided information when available, while still presenting meaningful messages for non-ApiError errors. The approach relies on runtime type checks (instanceof) to select the appropriate path, making the behavior predictable at call sites. + +## Notes +- Potential gotcha: ApiError must be a runtime class; if not, the ApiError branch is skipped. +- It always returns a string, so no crash due to non-string outputs; however, ensure the fallback is a string. + +--- + +## notifyError +> **File:** `src/webapp/src/lib/notify.ts` +> **Kind:** function + +```typescript +export const notifyError = (e: unknown, fallback?: string) => toast.error(formatError(e, fallback)) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `e` | `unknown` | — | +| `fallback` | `string` | — | + + +notifyError takes an unknown error value and an optional fallback string, formats the error with formatError, and surfaces it to the user via toast.error. Use this helper to centralize how errors are presented in the UI, instead of calling toast.error and formatting errors ad-hoc throughout the codebase. + +## Remarks + +By encapsulating error formatting and presentation, this symbol establishes a single, reusable contract for user-facing error notifications. It decouples the UI notification mechanism from error sources, so you can swap the underlying toast library or the formatting strategy in one place without touching call sites. + +## Notes + +- Triggers a UI side-effect (a toast) and may be affected by tests that mock or suppress UI notifications; ensure test setup accounts for UI interactions. + +--- + +## notifyInfo +> **File:** `src/webapp/src/lib/notify.ts` +> **Kind:** function + +```typescript +export const notifyInfo = (msg: string) => toast.info(msg) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `msg` | `string` | — | + + +notifyInfo is a tiny helper that renders an informational toast by delegating to toast.info. It accepts a single string message and surfaces it to the user as an info-styled notification. This wrapper exists to centralize usage of the underlying toast library, making it easier to apply consistent defaults, replace or augment the notification mechanism, and keep call sites readable. + +## Remarks +By naming the operation notifyInfo rather than calling toast.info directly, the codebase gains a semantic abstraction for informational messages. This makes it straightforward to adjust default behavior (such as autoClose duration, position, or styling) in one place, or to swap the notification implementation during testing or UI rewrites without touching call sites. It also simplifies testing by allowing mocks of the high-level notifyInfo API. + +## Example +```typescript +// Common usage +notifyInfo("Data saved successfully"); +``` + +## Notes +- The app must configure the toast container (style and behavior) for the info to display correctly. +- If you need more control over presentation, consider extending this wrapper or configuring toast defaults centrally. + +--- + +## notifySuccess +> **File:** `src/webapp/src/lib/notify.ts` +> **Kind:** function + +```typescript +export const notifySuccess = (msg: string) => toast.success(msg) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `msg` | `string` | — | + + +notifySuccess is a small wrapper around toast.success that displays a success toast with the given message. Developers should reach for this helper when they want a consistent, centralized way to show success notifications, making it easy to swap the underlying toast implementation in the future without changing call sites. + +## Remarks + +By centralizing toast usage, you can swap the notification library or adjust default options in one place. It also makes unit testing easier, as you can mock notifySuccess instead of dealing with the toast library directly. The naming convention communicates intent clearly and pairs well with other notify helpers you might introduce (e.g., notifyError). + +## Example + +```typescript +notifySuccess('Profile updated successfully'); +``` + +## Notes + +- It is a thin alias; if you need per-call customization (duration, position, etc.), extend this wrapper or provide an overload. +- Ensure a toast container (or equivalent) is configured in your app; otherwise, the toast may not render. +- When testing, prefer mocking notifySuccess over the toast library to keep tests deterministic. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/lib/userPrefs.ts.md b/docs/auriondocs/Code/src/webapp/src/lib/userPrefs.ts.md new file mode 100644 index 0000000..26f28c5 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/lib/userPrefs.ts.md @@ -0,0 +1,250 @@ +# userPrefs.ts + +> **Source:** `src/webapp/src/lib/userPrefs.ts` + +## Contents + +- [readBool](#readbool) +- [readLegacyHideReactDetails](#readlegacyhidereactdetails) +- [useBoolPref](#useboolpref) +- [useHideThinking](#usehidethinking) +- [useHideToolCalls](#usehidetoolcalls) +- [useHideToolResults](#usehidetoolresults) +- [writeBool](#writebool) + +--- + +## readBool +> **File:** `src/webapp/src/lib/userPrefs.ts` +> **Kind:** function + +```typescript +function readBool(key: string, fallback: boolean): boolean +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `key` | `string` | — | +| `fallback` | `boolean` | — | + +**Returns:** `boolean` + + +Reads a boolean from localStorage under the given key and returns it as a boolean. If the key is missing or localStorage access fails, it returns the provided fallback. The stored value is true only when the string is exactly "1"; any other value yields false. + +## Remarks +This helper centralizes the common pattern of reading a boolean preference from localStorage while guarding against storage access errors. By encoding a simple convention ("1" means true), callers can store booleans compactly and read them reliably without repeating parsing logic. It also avoids crashing the app if storage is unavailable, thanks to the try/catch. + +## Example +```typescript +const prefersCompact = readBool('compactMode', false); +``` + +## Notes +- Only the string '1' maps to true; '0', 'true', or any other value will be treated as false. +- If localStorage is blocked or unavailable (e.g., in some privacy modes), this function returns the fallback instead of throwing. + +--- + +## readLegacyHideReactDetails +> **File:** `src/webapp/src/lib/userPrefs.ts` +> **Kind:** function + +```typescript +function readLegacyHideReactDetails(): boolean +``` + +**Returns:** `boolean` + + +Reads the LEGACY_HIDE_REACT_DETAILS_KEY flag from localStorage and returns true if its value is '1'; otherwise, it returns false. If localStorage access fails (for example, in environments where storage is unavailable), it safely falls back to false. Use this function to determine whether to hide React-related UI details in the legacy path rather than performing storage checks inline. + +## Remarks +By centralizing the storage access, this function isolates the legacy UI toggle from the rest of the codebase. The try/catch pattern ensures resilience against environments without localStorage, private modes, or blocked storage, providing a deterministic default behavior. It relies on the LEGACY_HIDE_REACT_DETAILS_KEY; any change to the key affects every caller. + +## Example +```typescript +if (readLegacyHideReactDetails()) { + // Hide React-specific details for legacy configurations +} else { + // Render React details as usual +} +``` + +## Notes +- The function uses strict equality to '1' to enable the flag. +- If the key is missing or its value is not '1', the function returns false. +- This function swallows storage errors; do not rely on it to detect storage availability. + +--- + +## useBoolPref +> **File:** `src/webapp/src/lib/userPrefs.ts` +> **Kind:** function + +```typescript +export function useBoolPref(key: string, fallback: boolean): [boolean, (next: boolean) => void] +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `key` | `string` | — | +| `fallback` | `boolean` | — | + + +useBoolPref binds a boolean user preference to React state. It reads the initial value with readBool(key, fallback) and keeps the value in sync when the preference changes, either within the tab (via PREFS_CHANGED_EVENT) or across tabs (via the storage event). It returns a tuple [value, set], where value is the current boolean and set updates the preference with writeBool and applies an optimistic UI update. + +## Remarks +This hook centralizes the boilerplate of reading and observing a boolean preference, decoupling UI components from the underlying storage and event-wiring. It ensures a consistent, reactive view of a named preference across the app and tabs, coordinating with a unified event stream so changes propagate predictably to all listeners. + +## Example +```typescript +function ThemeToggle() { + const [darkMode, setDarkMode] = useBoolPref('darkMode', false); + return ( + + ); +} +``` + +## Notes +- The hook listens for changes in two channels: a custom in-tab event (PREFS_CHANGED_EVENT) and the browser storage event. If you update the same key from elsewhere, the hook will refresh automatically. +- The setter performs an optimistic update: it writes the new value and immediately updates local state, with the expectation that the change event will propagate to keep all listeners in sync. + +--- + +## useHideThinking +> **File:** `src/webapp/src/lib/userPrefs.ts` +> **Kind:** function + +```typescript +export function useHideThinking(): [boolean, (next: boolean) => void] +``` + + +Returns a two-element tuple: the current boolean indicating whether the thinking indicator should be hidden, and a setter to update that preference. It wraps a generic boolean-preference hook via useBoolPref(HIDE_THINKING_KEY, readLegacyHideReactDetails()). Use this hook when you want to read or persist the user's preference for hiding the thinking state in the UI, rather than accessing the preference directly. + +## Remarks +This hook centralizes a specific user preference for the UI's thinking indicator, preventing duplication of the underlying key and default logic across components. It also preserves backward compatibility by using readLegacyHideReactDetails() as the default when the preference has not yet been set, ensuring consistent behavior across both legacy and newer code paths. + +## Notes +- The setter accepts a boolean value; it does not toggle automatically. Pass true to hide or false to show. +- The default value is sourced from readLegacyHideReactDetails(), which provides backward-compatible behavior for older React details state. + +--- + +## useHideToolCalls +> **File:** `src/webapp/src/lib/userPrefs.ts` +> **Kind:** function + +```typescript +export function useHideToolCalls(): [boolean, (next: boolean) => void] +``` + + +useHideToolCalls is a React hook that exposes a boolean preference for hiding tool calls in the UI. It delegates to useBoolPref with the key HIDE_TOOL_CALLS_KEY and a default value sourced from readLegacyHideReactDetails(), returning a tuple [boolean, (next: boolean) => void] that components can read and update to reflect the user's preference. + +## Remarks +This hook centralizes access to the 'hide tool calls' preference, so components don't need to know how the value is stored or persisted. The default value comes from readLegacyHideReactDetails(), ensuring backward compatibility while the app migrates to newer preference mechanisms. It sits at the boundary between user preferences and UI rendering, enabling consistent behavior across the app while keeping storage details out of the UI layer. + +## Example +```typescript +const [hideToolCalls, setHideToolCalls] = useHideToolCalls(); +// Use hideToolCalls to conditionally render tool call details +if (hideToolCalls) { + // suppress tool-call related UI +} +setHideToolCalls(true); // persist the choice to hide tool calls +``` + +## Notes +- The setter accepts a boolean and updates the underlying preference store via useBoolPref. +- The default is read from legacy behavior via readLegacyHideReactDetails(), so existing users see familiar UI choices on first load. + +--- + +## useHideToolResults +> **File:** `src/webapp/src/lib/userPrefs.ts` +> **Kind:** function + +```typescript +export function useHideToolResults(): [boolean, (next: boolean) => void] +``` + + +UseHideToolResults provides a small, reusable React hook for honoring a user preference to hide or show tool results. It returns a tuple [boolean, (next: boolean) => void] that components can read to determine whether to hide the results and to update that preference in response to user actions. The hook relies on the HIDE_TOOL_RESULTS_KEY for storage and uses readLegacyHideReactDetails as the default when no preference has yet been stored. + +## Remarks +By centralizing this preference behind a single hook, you avoid duplicating the storage key logic across several components. It also helps migrate existing UI that followed the legacy behavior by encapsulating the default in readLegacyHideReactDetails, while new code can rely on the shared preference. This hook follows the same pattern as other preference hooks, providing a consistent, composable API for UI decisions tied to user settings. + +## Example +```ts +// Example: toggle the visibility of tool results +import React from 'react'; + +function ToolResultsToggle() { + const [hide, setHide] = useHideToolResults(); + + return ( +
+ + + {!hide && ( +
+ {/* render tool results here */} +
+ )} +
+ ); +} +``` + +## Notes +- The setter signature is (next: boolean) => void; to toggle, compute the desired next value and pass it directly (e.g., setHide(!hide)). There is no updater-function form. +- Initial value depends on readLegacyHideReactDetails; during migration, the default may reflect legacy behavior until the stored preference is updated. +- The hook uses a shared storage key (HIDE_TOOL_RESULTS_KEY); keep that key consistent across all consumers to avoid diverging behavior. + +--- + +## writeBool +> **File:** `src/webapp/src/lib/userPrefs.ts` +> **Kind:** function + +```typescript +function writeBool(key: string, value: boolean): void +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `key` | `string` | — | +| `value` | `boolean` | — | + +**Returns:** `void` + + +The function persists a boolean preference by storing it in localStorage as an encoded string ('1' for true, '0' for false) under the provided key, and then broadcasts a change notification to the rest of the application. If the write succeeds, a CustomEvent named by PREFS_CHANGED_EVENT is dispatched with the key included in the event detail, allowing listeners to react immediately. If localStorage is unavailable (for example in privacy mode or due to quota limits), the operation is swallowed silently, and the app continues to function with ephemeral state. + +## Remarks +This helper abstracts the common pattern of persisting a boolean preference and notifying dependents in one place. By coupling the persistence with an event broadcast, components can stay in sync without requiring direct references to the storage mechanism. The silent fallback ensures robustness in restricted environments, preventing a hard failure if persistence is blocked, while signaling to developers that the change could not be persisted. + +## Example +```typescript +writeBool('darkMode', true); +``` + +## Notes +- If localStorage is disabled or quota-exceeded, no error is surfaced and no event is dispatched. +- Values are stored as '1' or '0'; retrieval logic should interpret these strings back into booleans. +- The function returns void and does not surface success/failure to the caller; callers should rely on the event or storage state if needed. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/main.tsx.md b/docs/auriondocs/Code/src/webapp/src/main.tsx.md new file mode 100644 index 0000000..cfc8226 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/main.tsx.md @@ -0,0 +1,16 @@ +# main + +> **File:** `src/webapp/src/main.tsx` +> **Kind:** file + + +Bootstraps the React application: installs a one-time authentication interceptor for the HTTP client, loads global styles, and renders App into the DOM's root element inside StrictMode. + +## Remarks + +By centralizing initialization here, the app ensures that global concerns (HTTP retry behavior, theming, and math/code styling) are in place before components mount. Wrapping the render in StrictMode in development helps surface potential side effects and lifecycle issues early, while the imported CSS assets guarantee consistent visuals and correct math/code rendering across the UI. This file acts as a clear boundary between setup and application logic, keeping App focused on rendering and state management. + +## Notes + +- The code uses a non-null assertion on the root element (document.getElementById('root')!). Ensure the HTML contains an element with id 'root'; otherwise, the app will crash at startup. +- installAuthInterceptor is described as a one-time global setup; in development environments with hot module replacement, repeated evaluation could re-register interceptors. Ensure this setup remains idempotent or guarded in such environments. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pages/ChatPage.tsx.md b/docs/auriondocs/Code/src/webapp/src/pages/ChatPage.tsx.md new file mode 100644 index 0000000..622b273 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pages/ChatPage.tsx.md @@ -0,0 +1,61 @@ +# ChatPage.tsx + +> **Source:** `src/webapp/src/pages/ChatPage.tsx` + +## Contents + +- [ChatPage](#chatpage) +- [persistActiveConversation](#persistactiveconversation) + +--- + +## ChatPage +> **File:** `src/webapp/src/pages/ChatPage.tsx` +> **Kind:** function + +```typescript +export function ChatPage() +``` + + +ChatPage is the React page component that renders the chat UI for a given conversation, using a server-driven GabrielSequence palette and avatar seed while gracefully falling back to seed-based visuals until the data arrives. It persists the active conversation for quick restoration on return, resets the visual palette when the conversation changes to avoid color bleed, and exposes a reroll avatar action that updates both the seed and the current palette without refetching the chat list. + +## Remarks +ChatPage serves as an orchestration layer between route-derived state, server-provided sequence data, and local UI state. It decides when to rely on a server palette versus a seed-derived fallback, and it coordinates avatar seeds with the active palette to give a coherent visual identity per conversation and project context. By caching and refreshing only the necessary slices (avatar seed, palette, and sequence), it minimizes churn and preserves the sidebar's stability while the main conversation visuals update. + +## Notes +- Palette fallback ensures visual continuity during sequence loading; when conversationId changes, sequenceStops is cleared to prevent bleeding. +- Avatar reroll updates avatarSeed and triggers a palette refresh; it bumps the sequence but does not refetch the conversation list. + +--- + +## persistActiveConversation +> **File:** `src/webapp/src/pages/ChatPage.tsx` +> **Kind:** function + +```typescript +function persistActiveConversation(id: string | null) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `id` | `string | null` | — | + + +Persists the currently active conversation identifier in the browser's storage. When an id is provided, it stores the value under ACTIVE_CONVERSATION_KEY; when null is provided, it removes that key. The operation is wrapped in a try/catch and errors are ignored to avoid impacting the UI, so callers can persist state without risking user-facing failures. + +## Remarks +This function encapsulates the browser storage interaction, keeping ChatPage.tsx focused on UI concerns. By centralizing the key and error handling, it reduces boilerplate and makes restoration of the active conversation state straightforward across page reloads. + +## Example +```typescript +persistActiveConversation('conv-42'); +persistActiveConversation(null); +``` + +## Notes +- Silent catch means storage failures won't throw, but you won't be alerted to persistence issues; consider adding logging or a fallback if persistence is mission-critical. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pages/DiagnosticsPage.tsx.md b/docs/auriondocs/Code/src/webapp/src/pages/DiagnosticsPage.tsx.md new file mode 100644 index 0000000..67b2ac2 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pages/DiagnosticsPage.tsx.md @@ -0,0 +1,145 @@ +# DiagnosticsPage.tsx + +> **Source:** `src/webapp/src/pages/DiagnosticsPage.tsx` + +## Contents + +- [DiagnosticsFrameProps](#diagnosticsframeprops) +- [DiagnosticsPageProps](#diagnosticspageprops) +- [DiagnosticsFrame](#diagnosticsframe) +- [DiagnosticsPage](#diagnosticspage) +- [DiagnosticsPageInner](#diagnosticspageinner) +- [ProjectDiagnosticsPage](#projectdiagnosticspage) +- [onBack](#onback) + +--- + +## DiagnosticsFrameProps +> **File:** `src/webapp/src/pages/DiagnosticsPage.tsx` +> **Kind:** interface + +```typescript +interface DiagnosticsFrameProps +``` + + +DiagnosticsFrameProps specifies the data contract for the DiagnosticsFrame component used on the DiagnosticsPage. It provides three pieces of data: frame, a numeric array representing frame data; palette, a two-dimensional numeric array representing color mappings; and index, a number used to select the active frame or color entry. + +## Remarks +This interface groups related props to reduce prop drilling and to standardize how diagnostic visuals are fed into the UI. It serves as a stable contract between the DiagnosticsPage and its rendering component, making it easier to reuse the same shape across different frames or visualizations. + +## Notes +- The interface does not enforce mutability; avoid mutating the provided arrays after they are passed to a component to prevent unintended re-renders. +- There is no runtime validation here; callers should ensure the shapes and bounds (e.g., index within the valid range) before using the data. + +--- + +## DiagnosticsPageProps +> **File:** `src/webapp/src/pages/DiagnosticsPage.tsx` +> **Kind:** interface + +```typescript +interface DiagnosticsPageProps +``` + + +DiagnosticsPageProps defines the props for DiagnosticsPage: source selects the SequenceSource to inspect; backTo, when provided, overrides the back-navigation target; and scopeLabel, when provided, customizes the heading suffix to reflect the current scope. This simple prop bag allows route wrappers to pass URL-derived values into DiagnosticsPage without embedding routing logic inside the component. + +--- + +## DiagnosticsFrame +> **File:** `src/webapp/src/pages/DiagnosticsPage.tsx` +> **Kind:** function + +```typescript +function DiagnosticsFrame( +``` + + +DiagnosticsFrame is a React function component that renders the visual representation of a single diagnostic frame within DiagnosticsPage. It accepts a frame object that describes the data for that frame, a color palette for styling, and an index that indicates its position in the sequence, enabling DiagnosticsPage to render a consistent, ordered collection of frames. + +## Remarks +This component encapsulates presentation concerns, keeping frame data handling separate from rendering details. By consuming a palette, it ensures consistent colors across frames and supports theming without altering the frame data shape. The index can be used for deterministic ordering or animation sequencing when rendering multiple frames. In the DiagnosticsPage, DiagnosticsFrame is one tile in a broader diagnostic timeline or stack, helping users quickly scan frame-level context. + +## Notes +- Prefer passing immutable frame data and a stable palette; avoid mutating props inside the component to prevent re-renders. + +--- + +## DiagnosticsPage +> **File:** `src/webapp/src/pages/DiagnosticsPage.tsx` +> **Kind:** function + +```typescript +export function DiagnosticsPage() +``` + + +DiagnosticsPage is a small routing adapter that, when the URL provides a conversationId, renders DiagnosticsPageInner configured for that conversation; without a conversationId it renders nothing. It wires the diagnostic source as kind: 'conversation' with the given conversationId, and supplies a backTo link to the encoded conversation route while labeling the scope as conversation. + +## Remarks +DiagnosticsPage serves as a narrow adapter that decouples routing concerns from the actual diagnostics view. It ensures the inner component receives a precise context object and a proper back-navigation target, enabling the diagnostics UI to live behind a stable interface regardless of how the route is composed. + +## Notes +- Renders null when conversationId is missing from the route, resulting in no diagnostics UI until a valid ID is provided. +- The backTo path uses encodeURIComponent to safely embed the conversationId in the URL. +- DiagnosticsPageInner is responsible for data fetching and presentation; DiagnosticsPage does not perform data retrieval by itself. + +--- + +## DiagnosticsPageInner +> **File:** `src/webapp/src/pages/DiagnosticsPage.tsx` +> **Kind:** function + +```typescript +function DiagnosticsPageInner( +``` + + +DiagnosticsPageInner is a React function component that renders the inner content of a diagnostics page for a given source. It receives three props: source — the diagnostic data or content to present; backTo — a navigation target or callback used to return to the previous view; and scopeLabel — a label indicating the current scope or context of the diagnostics. The component is designed to be composed within DiagnosticsPage.tsx to present context-aware diagnostic information and provide an in-page navigation control. + +## Remarks +It acts as a focused presenter that abstracts away the details of how diagnostics are rendered from the page shell. By taking source, backTo, and scopeLabel as props, it remains reusable across different diagnostic flows and contexts, enabling the DiagnosticsPage container to swap data or navigation behavior without changing the inner rendering logic. This separation helps testability and makes the UI composition clearer, as the page-level layout can handle chrome (headers, sidebars) while DiagnosticsPageInner concentrates on the diagnostics view. + +--- + +## ProjectDiagnosticsPage +> **File:** `src/webapp/src/pages/DiagnosticsPage.tsx` +> **Kind:** function + +```typescript +export function ProjectDiagnosticsPage() +``` + + +This small route-level component wires a diagnostics view to a specific project. It reads the projectId from the URL parameters and renders nothing when the ID is missing; when present, it renders DiagnosticsPageInner with a project-scoped source and a back-to root behavior. + +## Remarks +Isolates routing concerns from DiagnosticsPageInner: this wrapper ensures a consistent project context and navigation label without requiring DiagnosticsPageInner to know about routing. It also guards against rendering the diagnostics UI without a valid projectId, which could lead to an incomplete or broken page. + +## Notes +- A missing projectId causes the component to render nothing; if this is not desirable in your routing, add a guard or redirect at the route level. +- DiagnosticsPageInner relies on a source object with kind: 'project' and a projectId; ensure the downstream page handles this shape correctly. + + +--- + +## onBack +> **File:** `src/webapp/src/pages/DiagnosticsPage.tsx` +> **Kind:** function + +```typescript +const onBack = () => +``` + + +onBack is a small navigation helper used in DiagnosticsPage to return the user to the previous screen. It navigates to backTo when that value is truthy; otherwise it falls back to navigating back one step in history (navigate(-1)). This encapsulates a common back-navigation pattern and keeps event handlers focused on UI concerns rather than routing details. + +## Remarks +onBack centralizes the back-navigation policy for the DiagnosticsPage. When a predefined back target is available, it uses that destination; otherwise it relies on the router’s history to go back. This abstraction reduces duplication and makes it easier to adjust back-navigation behavior in one place. + +## Notes +- Ensure navigate is the routing function in scope (e.g., from useNavigate) and that the code runs within a router context. If navigate is unavailable, calling it will throw. +- backTo should be a value that navigate can interpret as a destination (string path) when truthy; otherwise, the fallback navigate(-1) is used. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pages/IndexPage.tsx.md b/docs/auriondocs/Code/src/webapp/src/pages/IndexPage.tsx.md new file mode 100644 index 0000000..e772b05 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pages/IndexPage.tsx.md @@ -0,0 +1,54 @@ +# IndexPage.tsx + +> **Source:** `src/webapp/src/pages/IndexPage.tsx` + +## Contents + +- [IndexPage](#indexpage) +- [loadLastConversation](#loadlastconversation) + +--- + +## IndexPage +> **File:** `src/webapp/src/pages/IndexPage.tsx` +> **Kind:** function + +```typescript +export function IndexPage() +``` + + +IndexPage bootstraps the chat experience by routing the user to the most relevant conversation on mount: if a previously loaded last conversation exists, it navigates there; otherwise it creates a new conversation (title: null so the backend assigns a GUID) and navigates to that chat. While this startup sequence runs, it renders a simple loading indicator. + +## Remarks +IndexPage centralizes the initial navigation flow for the chat UI. It determines whether to resume an existing conversation or start a fresh one, leveraging loadLastConversation and ConversationsService.postApiConversations to establish the destination and using the router to navigate without a full page reload. The one-time bootstrap is guarded by an internal ranBootRef to prevent duplicate navigation in environments that invoke effects multiple times (e.g., React StrictMode during development). The backend-driven ID generation (title: null) is intentional and can be renamed later via PATCH. + +## Notes +- The component renders only a loading state during bootstrap; there is no additional UI until navigation completes, and errors are surfaced via notifyError without an explicit retry UI. +- If the startup flow fails to create or locate a conversation, the catch handler calls notifyError, but the user may remain on the loading screen unless a higher-level error handler intervenes. +- The backend generates the initial conversation identifier when title is null; renaming the conversation is handled by PATCH as noted in the code comments. + +--- + +## loadLastConversation +> **File:** `src/webapp/src/pages/IndexPage.tsx` +> **Kind:** function + +```typescript +function loadLastConversation(): string | null +``` + +**Returns:** `string | null` + + +Safely reads the last conversation from the browser's localStorage using ACTIVE_CONVERSATION_KEY. It returns the stored string value when available, or null if the key is missing or an access error occurs. This non-throwing accessor is intended for restoring UI state (e.g., pre-filling a chat) on load without risking a crash if storage access is blocked or unavailable. + +## Remarks +loadLastConversation centralizes a small, potentially brittle interaction with the Web Storage API behind a clean, predictable surface. It ensures the page can render even when localStorage is blocked or throws, at the cost of losing visibility into storage failures since the catch is silent. Callers should treat a non-null return as an opportunity to restore previous discourse, and remember to parse the returned string if the stored value is JSON. + +## Notes +- It cannot distinguish between a missing value and a read error because both yield null (the function returns null for any failure path). +- The value is a string; if you store JSON, parse it before use. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pages/LoginPage.tsx.md b/docs/auriondocs/Code/src/webapp/src/pages/LoginPage.tsx.md new file mode 100644 index 0000000..f77ad82 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pages/LoginPage.tsx.md @@ -0,0 +1,63 @@ +# LoginPage.tsx + +> **Source:** `src/webapp/src/pages/LoginPage.tsx` + +## Contents + +- [LocationState](#locationstate) +- [LoginPage](#loginpage) + +--- + +## LocationState +> **File:** `src/webapp/src/pages/LoginPage.tsx` +> **Kind:** interface + +```typescript +interface LocationState +``` + + +LocationState is a tiny, typed carrier for navigation state used during routing flows. It carries an optional from object that may include a pathname, allowing a caller to remember where the user navigated from (for example, to return after login). + +## Remarks +By isolating this into a dedicated interface, the codebase can pass around a minimal, well-typed hint about the origin without coupling to full location objects. It helps separate concerns between page components and the router, enabling simpler redirects to authentication or gated routes while preserving a sensible back-navigation target. + +## Example +```typescript +const example: LocationState = { from: { pathname: '/dashboard' } }; + +function determineReturnPath(state?: LocationState): string { + return state?.from?.pathname ?? '/'; +} + +console.log(determineReturnPath(example)); // '/dashboard' +``` + +## Notes +- The from and its pathname are both optional; check for undefined before reading. +- This interface models only a small slice of routing state; do not assume more fields exist. + +--- + +## LoginPage +> **File:** `src/webapp/src/pages/LoginPage.tsx` +> **Kind:** function + +```typescript +export function LoginPage() +``` + + +LoginPage is the sign-in screen used when accessing protected routes. It renders a login form, submits credentials through the authentication context, and returns the user to the original destination (or '/' as a fallback) upon success; it also features a visual avatar seed that refreshes on each visit for a lively touch. + +## Remarks +LoginPage orchestrates the authentication flow by reading the intended destination from location.state and by invoking login(email, password) from the authentication hook. After a successful login, it navigates back to the originally requested URL, preserving the user experience across protected routes. The avatar seed is strictly visual and does not persist across sessions; it is re-seeded on mount and can be re-rerolled via the UI to keep the login screen feeling dynamic. A busy state gates the UI to prevent duplicate submissions and to surface errors clearly. + +## Notes +- The redirect target is derived from location.state.from.pathname with a fallback to '/'; if ProtectedRoute doesn't stash a target, login lands at the root. +- The avatar seed is ephemeral and re-rolls through the provided button; it does not influence authentication or persistence. +- The UI disables inputs and the submit button while a login attempt is in flight to prevent concurrent submissions, and displays an inline error message on failure. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pages/ProjectSettingsPage.tsx.md b/docs/auriondocs/Code/src/webapp/src/pages/ProjectSettingsPage.tsx.md new file mode 100644 index 0000000..1afb297 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pages/ProjectSettingsPage.tsx.md @@ -0,0 +1,73 @@ +# ProjectSettingsPage.tsx + +> **Source:** `src/webapp/src/pages/ProjectSettingsPage.tsx` + +## Contents + +- [ProjectSettingsPage](#projectsettingspage) +- [formatBytes](#formatbytes) + +--- + +## ProjectSettingsPage +> **File:** `src/webapp/src/pages/ProjectSettingsPage.tsx` +> **Kind:** function + +```typescript +export function ProjectSettingsPage() +``` + + +ProjectSettingsPage is a React functional component that renders a settings editor for a single project identified by the route's projectId. It fetches the project's data, mirrors key fields (name, description, systemPrompt) in local state for dirty-checking, and provides save, file upload, and file deletion capabilities with abortable requests and user feedback. + +## Remarks +This component acts as the UI and orchestration layer for project configuration, coordinating data loading, optimistic updates, and server mutations via the ProjectsService and ProjectFilesService. It demonstrates a common pattern in admin-style pages: loading a domain object, presenting a controlled form, and performing partial updates (PATCH) that only send changed fields to minimize unintended overwrites. The component also integrates file management and a lightweight skin-update flow, illustrating how cross-cutting concerns like loading state, error handling, and cancellation are wired into a single page. + +## Notes +- The Save operation patches only the fields that actually changed, reducing the risk of clobbering unrelated fields. +- Data loading is cancelable via AbortController/AbortSignal to avoid state updates after unmounting or navigation. +- Deleting a file uses a browser confirm dialog to prevent accidental removals; file uploads reset the input value to allow re-uploading the same file. +- A skin update pathway exists and is guarded to avoid race conditions (skinSaving flag); this enables instant visual feedback when the user selects a new skin configuration, without requiring a separate explicit action. + + +--- + +## formatBytes +> **File:** `src/webapp/src/pages/ProjectSettingsPage.tsx` +> **Kind:** function + +```typescript +function formatBytes(bytes: number): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `bytes` | `number` | — | + +**Returns:** `string` + + +Formats a numeric byte count into a human-friendly string using binary-size units (B, KB, MB, GB). This small, pure helper is useful whenever you need to display storage sizes in the UI rather than raw byte counts. + +## Remarks + +This function centralizes the formatting rules for byte counts, ensuring consistent display across the application. It relies on binary thresholds (1024, 1024^2, 1024^3) and formats KB and MB with one decimal place, while GB uses two decimal places. Note that negative inputs are not guarded against; they will be formatted as a negative number of bytes in B (e.g., -500 B). If negative values are possible in your data, consider clamping beforehand or extending the function with input validation. + +## Example + +```typescript +formatBytes(500); // "500 B" +formatBytes(2048); // "2.0 KB" +formatBytes(5 * 1024 * 1024); // "5.0 MB" +formatBytes(3 * 1024 * 1024 * 1024); // "3.00 GB" +``` + +## Notes + +- Negative inputs are not guarded against; callers may want to clamp to non-negative values. +- Uses binary prefixes (KB/MB/GB) with 1 decimal for KB/MB and 2 decimals for GB. +- This is a pure formatter; it does not localize units or numbers. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pages/RegisterPage.tsx.md b/docs/auriondocs/Code/src/webapp/src/pages/RegisterPage.tsx.md new file mode 100644 index 0000000..db44eee --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pages/RegisterPage.tsx.md @@ -0,0 +1,20 @@ +# RegisterPage + +> **File:** `src/webapp/src/pages/RegisterPage.tsx` +> **Kind:** function + +```typescript +export function RegisterPage() +``` + + +RegisterPage is a React functional component that renders a complete user registration screen and wires it into the app's authentication flow. It manages local UI state for email, password, password visibility, and loading status, and on submission it delegates to the authentication hook's register(email, password) method, then navigates to the home page on success; if registration fails, it surfaces an error message to the user. The UI includes a visual avatar seeded by a numeric seed, which is rerolled on mount and on demand to keep the screen feeling alive until the server assigns the real avatar during registration. + +## Remarks +RegisterPage encapsulates the presentation layer of the signup flow, coordinating form state, validation cues, and routing while keeping the actual registration logic inside the useAuth hook. This separation supports swapping authentication strategies or reusing the same signup UI in different contexts without touching business logic. The seed-based avatar is a purely visual flourish used to lend liveliness to the screen, with the server finalizing the avatar on account creation. + +## Notes +- Busy state disables inputs and the avatar reroll button to prevent duplicate submissions. +- Errors are surfaced in a dedicated alert area with an icon to ensure visibility. +- The password field enforces a minimum length and includes a toggle to show/hide the password for usability. +- The submit button indicates progress with a spinner and a localized label while creation is in progress. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pages/UserSettingsPage.tsx.md b/docs/auriondocs/Code/src/webapp/src/pages/UserSettingsPage.tsx.md new file mode 100644 index 0000000..a827378 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pages/UserSettingsPage.tsx.md @@ -0,0 +1,21 @@ +# UserSettingsPage + +> **File:** `src/webapp/src/pages/UserSettingsPage.tsx` +> **Kind:** function + +```typescript +export function UserSettingsPage() +``` + + +UserSettingsPage renders the Settings UI for the current user, coordinating authentication state, navigation, and a collection of preference panels. It conditionally shows account information (loading while user data is being fetched, the email and user ID when signed in, or a not-signed-in message) and provides controls for selecting a model, listing memory items scoped to the user, and toggling visibility of the thinking steps, tool calls, and tool results in the chat view. A back button and a sign-out action complete the page. + +## Remarks + +By composing ModelSelector, MemoryList, and the trio of visibility toggles, this symbol serves as a cohesive user-facing surface that centralizes session-scoped preferences. It isolates authentication/navigation concerns from the content of the panels and delegates data retrieval to child components, while presenting a consistent settings experience. + +## Notes + +- Back navigation relies on browser/history state; in contexts with no history, the action may be a no-op. +- Logout is invoked without awaiting; if a post-logout redirect or loading state is required, handle the promise accordingly. +- The three toggles source their state from dedicated hooks (useHideThinking, useHideToolCalls, useHideToolResults); ensure these hooks implement persistence if you expect preferences to survive reloads. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pulse/palettes.ts.md b/docs/auriondocs/Code/src/webapp/src/pulse/palettes.ts.md new file mode 100644 index 0000000..2cd4dc9 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pulse/palettes.ts.md @@ -0,0 +1,337 @@ +# palettes.ts + +> **Source:** `src/webapp/src/pulse/palettes.ts` + +## Contents + +- [Palette](#palette) +- [RGB](#rgb) +- [brightestStop](#brighteststop) +- [gradientCssFromStops](#gradientcssfromstops) +- [paletteAccent](#paletteaccent) +- [paletteForSeed](#paletteforseed) +- [paletteGradientCss](#palettegradientcss) +- [paletteVarsFromStops](#palettevarsfromstops) +- [pickPalette](#pickpalette) +- [rgbToCss](#rgbtocss) +- [sampleGradient](#samplegradient) + +--- + +## Palette +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** interface + +```typescript +export interface Palette +``` + + +Palette defines a named sequence of color stops as an ordered array of RGB colors. It acts as a compact data contract you can pass around when you need a color ramp as a single unit—useful for theming, charts, or UI components that render gradients. + +## Remarks +Palette is a simple data shape that decouples color ramp data from presentation. By packaging a name with an ordered stops array, it enables reuse across theming and rendering layers, makes serialization/transport straightforward, and makes the gradient definition explicit and self-describing. The order of stops matters for gradients, and each element is an RGB color that defines a point on the ramp. + +## Notes +- The stops array and the RGB objects it contains are mutable; avoid sharing mutable Palette instances across boundaries or clone before mutation to prevent unintended side effects. +- The interface does not enforce a non-empty stops collection; if your rendering or validation requires at least one color, ensure such checks at runtime. + +--- + +## RGB +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** type alias + +```typescript +export type RGB = readonly [number, number, number] +``` + + +RGB is a type alias for a read-only triple of numbers, representing the red, green, and blue components of a color. Use it to pass around a color as a single, immutable value rather than as separate numeric components, ensuring the length is exactly three and the components cannot be mutated in-place. + +## Remarks +Choosing RGB as a distinct type communicates intent and documents that a value is a color triplet. The readonly tuple enforces both the three-component structure and immutability, which helps prevent accidental mutations and keeps color values consistent across APIs. Note that the numeric range for each channel is not enforced by the type; you may need runtime checks or a helper constructor if you require 0–255 bounds. + +## Notes +- Individual elements are immutable; attempts to mutate channels (e.g., rgb[0] = 123) are a type error. +- The type does not enforce a per-channel range; validate or clamp values if you rely on 0–255 bounds. + +--- + +## brightestStop +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function brightestStop(stops: readonly RGB[]): RGB +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `stops` | `readonly RGB[]` | — | + +**Returns:** `RGB` + + +BrightestStop returns the color stop with the highest sum of its RGB channels, using that sum as a cheap proxy for luminance. This fast heuristic works well for the small palettes shipped with the client and for larger palettes the server may emit; in pulse palettes the last stop is already the brightest, so this function preserves that behavior. + +## Remarks +This abstraction provides a simple, dependency-light way to pick a standout color from a list of stops without requiring perceptual color computations. It yields deterministic results by comparing a single numeric score and keeps the implementation small and easy to test. The function assumes a non-empty input and non-negative channel values; callers should guard against empty arrays. + +## Example +```typescript +// Example usage: pick the brightest stop from a palette +const stops: readonly RGB[] = [ + [255, 0, 0], + [0, 255, 0], + [10, 10, 250] +]; +const best = brightestStop(stops); +``` + +## Notes +- Assumes the input array is non-empty; calling with an empty array would access stops[0] and may throw. + +--- + +## gradientCssFromStops +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function gradientCssFromStops(stops: readonly RGB[]): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `stops` | `readonly RGB[]` | — | + +**Returns:** `string` + + +Converts an array of RGB colors into a CSS linear-gradient string. It handles edge cases where there are no stops (returns 'transparent') or a single stop (returns the color as-is). For palettes with multiple stops, it sorts by a simple brightness metric (the sum of the red, green, and blue channels) and derives a representative five-stop subset when needed to produce a clear dim-to-bright gradient. It then builds a 90-degree linear gradient from these stops using per-stop colors generated by rgbToCss, returning the CSS gradient string. The function relies on rgbToCss to render each color as an rgb(...) formatted color value. + +When there are more than five stops, the function selects five representative stops by first sorting the input by brightness and then sampling at 0, 0.25, 0.5, 0.75, and 1 of the sorted sequence. Each selected stop is assigned a position from 0% to 100%, and the resulting stops are concatenated into a linear-gradient(90deg, ...) string. + +In short: gradientCssFromStops provides a compact, ready-to-use CSS gradient from an arbitrary set of RGB colors, normalizing output for consistent UI visuals. + +--- + +## paletteAccent +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function paletteAccent(palette: Palette): RGB +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `palette` | `Palette` | — | + +**Returns:** `RGB` + + +paletteAccent returns the RGB color corresponding to the palette’s brightest stop. It does so by calling brightestStop on palette.stops, effectively choosing the most vivid stop defined in the palette as the accent color. Use this helper when you want a consistent UI accent derived from a named palette without selecting a stop yourself. + +## Remarks + +paletteAccent encodes a design rule: the accent color should come from the palette’s brightest stop. Centralizing this logic ensures the same rule is applied across components and makes it easy to adjust the rule by changing brightestStop in one place. Because the function simply delegates to brightestStop, any changes to how brightness is determined will propagate to all consumers of paletteAccent. + +## Notes + +- Requires at least one stop in the palette; behavior for an empty stops collection depends on brightestStop. +- The function is a non-mutating mapper from Palette to RGB; given the same Palette, it will produce the same RGB as long as brightestStop is deterministic. + +--- + +## paletteForSeed +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function paletteForSeed(seed: number, name?: string): Palette +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `seed` | `number` | — | +| `name` | `string` | — | + +**Returns:** `Palette` + + +Documentation submitted for symbol paletteForSeed (function, TypeScript, file: src/webapp/src/pulse/palettes.ts, ID f14b8cb3-2f97-487a-890e-753467baee8a). + +--- + +## paletteGradientCss +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function paletteGradientCss(palette: Palette): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `palette` | `Palette` | — | + +**Returns:** `string` + + +paletteGradientCss converts a Palette into a CSS gradient string by delegating to gradientCssFromStops with the palette's stops. It is useful when you already have a Palette object and need a ready CSS gradient value for styling, abstracting away the details of turning stops into CSS. + +## Remarks +paletteGradientCss serves as a small adapter between the Palette data model and the UI styling layer. By funneling through a single export, it ensures consistent gradient formatting across components and isolates the rest of the code from the exact CSS generation logic, which lives in gradientCssFromStops. + +--- + +## paletteVarsFromStops +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function paletteVarsFromStops(stops: readonly RGB[]): Record +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `stops` | `readonly RGB[]` | — | + +**Returns:** `Record` + + +Translates a color stops palette into a ready-to-use set of CSS custom properties for theming. It selects the brightest stop as the accent color and exposes three tokens: --palette-accent for the solid color, --palette-accent-soft for a translucent variant, and --palette-gradient which represents a gradient built from all stops. Use this function when you want to derive theme tokens from a simple stops representation rather than hard-coding CSS values. + +## Remarks +PaletteVarsFromStops serves as a focused adapter between design-time color stops and runtime CSS tokens. It centralizes the logic for picking the accent (brightest stop) and for formatting colors (rgbToCss) and gradients (gradientCssFromStops) so UI code can rely on a single source of truth. Returning a plain object of CSS variable mappings makes the function usable from CSS-in-JS, style objects, or inline styles while keeping theming concerns isolated from component code. + +## Example +```ts +const stops: readonly RGB[] = [ + { r: 255, g: 64, b: 0 }, + { r: 0, g: 128, b: 255 }, + { r: 34, g: 177, b: 76 }, +]; +const vars = paletteVarsFromStops(stops); +console.log(vars); +``` + +## Notes +- Requires at least one stop; an empty array may cause an error when brightestStop is invoked. +- Returns a new object mapping CSS variable names to color strings; input stops array is not mutated. + +--- + +## pickPalette +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function pickPalette(rng: Rand, name?: string): Palette +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `rng` | `Rand` | — | +| `name` | `string` | — | + +**Returns:** `Palette` + + +PickPalette selects a Palette from the central catalog. If a name is supplied and a matching Palette exists, that Palette is returned; otherwise, a random Palette is chosen from PALETTES using the provided RNG function. This enables both deterministic selection by name and reproducible randomization by injecting a seedable Rand function. + +## Remarks +This function centralizes access to the Palette catalog, abstracting away the details of PALETTES and the Palette type. By relying on an injected RNG, it supports deterministic testing and reproducible rendering scenarios, while keeping the caller agnostic to how Palettes are stored or iterated. + +## Notes +- Be sure PALETTES is non-empty; if it is empty, the random access path may yield undefined. Consider validating the catalog before use. +- If a name is provided but no matching Palette exists, the function silently falls back to a random selection rather than throwing. +- The function relies on the RNG returning a value in [0, 1); ensure the Rand implementation adheres to this contract for predictable behavior. + +--- + +## rgbToCss +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function rgbToCss([r, g, b]: RGB, alpha = 1): string +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `[r, g, b]` | `RGB` | — | +| `alpha` | — | `1` | + +**Returns:** `string` + + +Converts an RGB color represented as a 3-tuple into a CSS color string. Given an RGB value [r, g, b] and an optional alpha (default 1), it returns a CSS color string using the opaque form when alpha is 1 or greater, or the translucent form using the CSS Color Module syntax rgb(r g b / a) when alpha is less than 1. This utility is handy whenever you build inline styles or color strings from raw color components without manually composing the CSS syntax at every call site. + +## Remarks + rgbToCss centralizes the formatting of CSS color strings derived from a simple RGB triplet, keeping the color-construction logic consistent across the codebase. By supporting the alpha-slash syntax, it cleanly expresses transparency in environments that expect modern CSS color syntax, reducing boilerplate and potential inconsistencies in styling code. + +## Example +```typescript +rgbToCss([255, 0, 0]); // "rgb(255 0 0)" +rgbToCss([255, 0, 0], 0.5); // "rgb(255 0 0 / 0.5)" +``` + +## Notes +- No bounds validation is performed on r, g, b, or alpha. Callers should ensure RGB components are in 0–255 and alpha is in 0–1 to avoid producing invalid CSS strings. + + +--- + +## sampleGradient +> **File:** `src/webapp/src/pulse/palettes.ts` +> **Kind:** function + +```typescript +export function sampleGradient(stops: readonly RGB[], t: number): RGB +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `stops` | `readonly RGB[]` | — | +| `t` | `number` | — | + +**Returns:** `RGB` + + +Computes an RGB color by linearly interpolating along a gradient defined by the provided stops. The input t represents a position within the gradient from 0 to 1; t is clamped to this range, the appropriate adjacent stops are found, and each color channel is interpolated with rounding. If there is only a single stop, that color is returned. Use this when you need a color corresponding to a scalar progress value along a custom gradient defined by RGB stops. + +## Remarks +This function encapsulates the common gradient-mapping pattern, so callers don’t have to implement per-channel interpolation or boundary handling themselves. It deterministically maps any t in [0,1] to a color between the nearest stops, using linear interpolation with integer rounding for RGB channels. It assumes the stops array has at least one color; edge cases with an empty array are not defined by the function and should be prevented by callers upstream. + +## Example +```typescript +const stops: readonly RGB[] = [[0, 0, 0], [255, 0, 0], [255, 255, 0]]; +const color = sampleGradient(stops, 0.5); +``` + +## Notes +- Do not pass an empty stops array; the function does not guard against this and behavior is undefined. Ensure stops.length >= 1 before calling. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pulse/patterns.ts.md b/docs/auriondocs/Code/src/webapp/src/pulse/patterns.ts.md new file mode 100644 index 0000000..692012b --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pulse/patterns.ts.md @@ -0,0 +1,413 @@ +# patterns.ts + +> **Source:** `src/webapp/src/pulse/patterns.ts` + +## Contents + +- [FlowParams](#flowparams) +- [NoiseParams](#noiseparams) +- [Pattern](#pattern) +- [PlasmaParams](#plasmaparams) +- [PulseParams](#pulseparams) +- [ShimmerCell](#shimmercell) +- [ShimmerParams](#shimmerparams) +- [SpiralParams](#spiralparams) +- [WavesParams](#wavesparams) +- [PatternName](#patternname) +- [fbm](#fbm) +- [hash2](#hash2) +- [pickPattern](#pickpattern) +- [range](#range) +- [smooth](#smooth) +- [valueNoise](#valuenoise) + +--- + +## FlowParams +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface FlowParams +``` + + +FlowParams is a lightweight data contract that describes the configuration for a flow-style decorative pattern used by the pulse rendering system. It groups numeric parameters such as the direction (angle), band characteristics (bandWidth and bands), the animation cadence (period), movement speed (speed), and the origin for placement (cx, cy). By aggregating these fields into a single FlowParams object, code can pass pattern configuration between functions cleanly and reuse the same parameter set across different parts of the rendering pipeline. + +## Remarks +FlowParams exists to encapsulate related configuration for a flow-like visual pattern, separating concerns between shape definition and rendering logic. It helps keep interfaces stable when pattern computations evolve, and supports reuse across components that consume these parameters. The interface acts as a pure data carrier; it does not implement behavior or enforce validation, so callers should apply any necessary checks at the boundaries of the system. + +## Notes +- The interface does not enforce value ranges or units; consumers should validate values as needed for their rendering context. +- FlowParams is a plain object; if you share it across asynchronous boundaries or multiple renderers, consider immutability (e.g., freezing the object or copying before mutation) to avoid subtle bugs. +- No default values are provided; ensure all required fields are supplied by the caller before use. + +--- + +## NoiseParams +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface NoiseParams +``` + + +NoiseParams is a compact data contract that groups four numeric parameters used to configure a noise-based pattern generation routine. It provides a single, strongly-typed object containing scale, loopR, seed, and octaves so callers can pass consistent noise configuration to the pattern logic without scattering individual numbers. + +## Remarks +NoiseParams acts as a shared configuration object that decouples the noise-generation algorithm from its callers, enabling reuse and predictable variation by adjusting a single seed. Centralizing scale and octaves promotes consistent texture across patterns, while loopR is a numeric parameter whose exact interpretation depends on the consuming implementation. Seed ensures reproducible results across renders. + +--- + +## Pattern +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +export interface Pattern

+``` + + +Pattern defines a pluggable contract for generating numeric samples over a temporal-spatial domain. Implementations provide an init method that derives a parameter object P from a given size and a RNG, and a sample method that computes a numeric value using the coordinates (t, x, y), the stored parameters, and a time value. Consumers reach for Pattern when they want interchangeable, parameterized sampling strategies—precomputing parameters once via init and then evaluating samples efficiently via sample across frames or tiles. + +## Remarks +Pattern serves as a strategy boundary for procedural patterns in the system. By separating parameter initialization from per-sample evaluation, it allows the same consumer code to switch between different pattern algorithms without changes to the calling site. The generic P enables implementations to embed whatever state is necessary (e.g., seed, frequency, modulation settings) while keeping the interface stable. + +--- + +## PlasmaParams +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface PlasmaParams +``` + + +PlasmaParams is a TypeScript interface that defines the shape of a plain object carrying ten numeric values used to configure plasma-pattern calculations in the web application's pulse pattern module. It serves as a single, strongly-typed container for related parameters so functions that generate or manipulate plasma visuals can accept one argument rather than ten separate numbers. + +## Remarks +By grouping these fields (a, b, c, d; sa, sb, sc, sd; cx, cy) into a single parameter object, the interface reduces boilerplate, clarifies intent, and makes parameter sets reusable across different plasma computations. The exact meaning of each field is algorithm-dependent, but together they express primary coefficients, secondary modifiers, and the pattern's center coordinates, enabling flexible, testable configurations. + +## Example +```typescript +// Example: construct a plasma parameter set +const params: PlasmaParams = { + a: 1.0, b: 0.5, c: -0.25, d: 2.0, + sa: 0.8, sb: 0.2, sc: 1.1, sd: -0.5, + cx: 320, cy: 240 +}; +``` + +## Notes +- All ten fields are required; the interface does not permit optional properties. Ensure all values are provided at compile time. +- This interface represents a data contract; introducing new fields would require updates to call sites and dependent code. + +--- + +## PulseParams +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface PulseParams +``` + + +PulseParams encapsulates the configuration for a ripple/pulse pattern, specifying the center coordinates (cx, cy), the visual width of the wave (waveWidth), the number of ripples to render (ripples), and the maximum radius the pattern may reach (maxRadius). It is intended to be passed as a single object to rendering logic that draws or animates pulses, rather than forwarding five separate numeric arguments. + +## Remarks +This interface serves as a compact data contract between the pulse-rendering component and the UI layer. By grouping related numeric parameters into one PulseParams object, developers gain reusable presets and cleaner call sites, and can extend the configuration with additional properties without touching multiple call sites. + +## Notes +- All fields are numeric values representing rendering-space quantities; validate non-negativity as appropriate for the consumer. +- This interface defines data shape only and does not implement behavior or rendering logic. + +--- + +## ShimmerCell +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface ShimmerCell +``` + + +ShimmerCell describes a single segment of a shimmer animation, carrying numeric bounds and timing information. It defines four values: from, to, startTime, and duration, which together model the position range and when the segment should begin and how long it lasts. A developer would create or pass around ShimmerCell objects when constructing or configuring the shimmer pattern in the Pulse UI, such as assembling multiple cells to form a complete shimmer effect or transferring timing data between pattern logic and rendering code. + +## Remarks +ShimmerCell acts as a lightweight data contract used by the pattern engine to represent a shimmer segment. By keeping timing (startTime, duration) separate from presentation, it supports composing complex shimmer animations from multiple cells without embedding rendering details. + +--- + +## ShimmerParams +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface ShimmerParams +``` + + +ShimmerParams is a configuration object that describes how a shimmering effect should be rendered on a grid of pixels. It bundles together a 2D array of ShimmerCell instances (pixels), a non-negative duration range for shimmer transitions (minDur and maxDur), and a random-number generator (rng) to drive per-cell variation. Use this interface when you need to pass a single, cohesive shimmer configuration to a generator or renderer instead of supplying multiple scattered parameters. + +## Remarks +ShimmerParams isolates data shape from rendering logic, enabling reusable shimmer generators to operate with a single payload. It combines the per-cell state container (ShimmerCell[][]) with the timing and randomness controls, so changes to the grid or the timing model don't require changes to the consumer code. It relies on ShimmerCell to express per-cell visual state and on Rand to provide deterministic randomness when seeded. + +## Notes +- Validate duration bounds (minDur <= maxDur and minDur >= 0) before use; the interface does not enforce these constraints. +- The rng is a shared randomness source; reuse a single Rand instance for reproducible shimmer visuals; swapping it mid-flight may produce inconsistent results. + +--- + +## SpiralParams +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface SpiralParams +``` + + +SpiralParams is a compact configuration object that describes a multi-arm spiral's geometry and animation, including how many arms it has, how tight the spiral is, how fast it animates, the perceived sharpness of the arms, and the center coordinates. Developers reach for this interface when parameterizing a spiral pattern in the Pulse pattern renderer, allowing a single, typed payload to drive rendering logic rather than passing multiple discrete numbers around. + +## Remarks +SpiralParams serves as a data contract between the UI-driven configuration and the rendering pipeline. By consolidating these numeric controls into one object, it makes it easy to swap different spiral configurations at runtime, experiment with visual styles, or animate changes smoothly. It also decouples the drawing routine from hard-coded constants, improving testability and reusability across patterns. + +## Notes +- The interface carries no validation; callers should enforce sensible ranges (e.g., arms > 0) to avoid unexpected rendering. +- Be mindful of object mutability if SpiralParams is shared across animation frames; prefer creating new instances when tweaking values to avoid unintended drift. + +--- + +## WavesParams +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** interface + +```typescript +interface WavesParams +``` + + +WavesParams is a compact interface that groups six numeric parameters used to configure a wave-based pattern in the web app. It exposes angle (the wave direction in radians), freq (spatial frequency), speed (propagation speed), sharpness (waveform contour), and cx/cy (the pattern's center coordinates). This interface is intended to be passed to rendering routines that draw or animate waves, giving them a single cohesive parameter object rather than a scattered set of values. + +## Remarks +WavesParams acts as a parameter bag that decouples the rendering engine from calling-site internals, enabling reuse across different patterns and components. Centralizing these related settings makes it easier to swap in new configurations, test variations, and serialize parameters for storage or transmission. + +## Notes +- No runtime validation is included; ensure values are finite numbers and within expected ranges before feeding them to rendering code. +- It's a plain data container (no methods). If you need behavior, wrap it or create a helper function to create parameter objects. + +--- + +## PatternName +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** type alias + +```typescript +export type PatternName = keyof typeof PATTERNS +``` + + +PatternName is a TypeScript type that represents the set of valid pattern identifiers by taking the keys of the PATTERNS registry. It lets APIs accept a pattern name in a type-safe way, preventing typos and mismatches by constraining values to known PATTERNS keys rather than using a plain string. + +## Remarks +PatternName serves as a semantic alias over the runtime PATTERNS object. By deriving its values from the actual keys of PATTERNS, it stays in sync with the available patterns and reduces duplication. This promotes safer, self-documenting code when a function or component accepts a pattern name. + +## Notes +- This type is a string-literal union derived from PATTERNS; adding or removing keys updates PatternName automatically. +- It does not create new runtime values; it's only a compile-time type that constrains values to known keys. + +--- + +## fbm +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** function + +```typescript +function fbm(x: number, y: number, seed: number, octaves: number): number +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `x` | `number` | — | +| `y` | `number` | — | +| `seed` | `number` | — | +| `octaves` | `number` | — | + +**Returns:** `number` + + +Fractal Brownian motion (fbm) noise is produced by layering multiple octaves of valueNoise at increasing frequency and decreasing amplitude, then normalizing by the sum of amplitudes. For each octave i (0-based), the function samples valueNoise at coordinates (x * freq, y * freq) with seed offset by i * 17, scales the sample by the current amplitude, and accumulates the result. After all octaves, it divides the total by the sum of amplitudes to produce a single value that reflects detail across scales. This yields smoother, more natural textures or terrain-like variations compared to a single octave of noise. Use fbm when you want richer patterns and you need deterministic results for the same inputs by fixing seed; adjust octaves to trade detail for performance. + +## Remarks +fbm is a convenient abstraction that encapsulates the standard multi-octave sampling pattern over valueNoise. It hides the per-octave loop and normalization, making it easy to reuse stable fractal noise behavior across different parts of the codebase. The implementation decorrelates octaves by using distinct seeds per octave via seed + i * 17, which helps reduce repetitive artifacts. Be mindful that more octaves increase CPU cost; choose octaves to balance detail with performance. The output range depends on valueNoise and the chosen seed; the normalization keeps the result in a consistent range assuming valueNoise is reasonably bounded. + +## Example +```typescript +// Example: 2D fractal noise at position (12.34, 56.78) with 6 octaves and a fixed seed +const n = fbm(12.34, 56.78, 12345, 6); +``` + +## Notes +- Octaves must be >= 1; passing 0 octaves yields NaN due to division by zero. +- More octaves increase computation; use the minimum needed to achieve the desired roughness. +- The pattern is deterministic for a given (x, y, seed, octaves); to animate noise, vary x/y over time or change the seed per frame. + +--- + +## hash2 +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** function + +```typescript +function hash2(x: number, y: number, seed: number): number +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `x` | `number` | — | +| `y` | `number` | — | +| `seed` | `number` | — | + +**Returns:** `number` + + +Computes a deterministic, non-cryptographic value from three integers (x, y, seed). The return value is a pseudo-random-looking number in the range [0, 1), intended for per-coordinate variation in procedural patterns without pulling in a full RNG. + +## Remarks +Provides a lightweight, repeatable seed per coordinate to drive pattern variation without external dependencies. It relies on 32-bit integer arithmetic (Math.imul) to keep results stable across JavaScript engines. Given the same inputs, the output is always the same; changing any input changes the result. This is not suitable for cryptographic purposes or security-critical randomness. + +## Notes +- Not cryptographically secure; suitable only for non-security-critical randomness in visuals. +- The value is in the inclusive range [0, 1]; edge cases may yield 0 or, rarely, 1 depending on intermediate results. + + +--- + +## pickPattern +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** function + +```typescript +export function pickPattern(rng: Rand, name?: PatternName): +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `rng` | `Rand` | — | +| `name` | `PatternName` | — | + + +pickPattern selects a Pattern for the pulse pattern system. It accepts a RNG (Rand) and an optional PatternName, and returns an object containing the chosen name and its corresponding Pattern definition. If you supply a name, the function resolves and returns that specific pattern; if you omit the name, it uses the provided RNG to pick a random available pattern and returns both the name and its definition. + +## Remarks +By centralizing the mapping from PatternName to Pattern, pickPattern isolates pattern selection from callers and other generation logic. It enables deterministic, testable generation when seeded RNGs are provided, and serves as the primary hook for obtaining a concrete Pattern given either a requested name or a random choice. It relies on the Rand instance, the PatternName type, and the Pattern interface, coordinating them to deliver a ready-to-use pattern description and metadata in a single return value. + +--- + +## range +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** function + +```typescript +const range = (rng: Rand, min: number, max: number) => min + rng() * (max - min) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `rng` | `Rand` | — | +| `min` | `number` | — | +| `max` | `number` | — | + + +Produces a random value uniformly distributed within [min, max) by applying a linear interpolation scaled by the provided Rand function. Pass a Rand implementation to range when you want randomness that is pluggable and testable, rather than using a global RNG directly. + +## Remarks +This function is a tiny utility around linear interpolation: it decouples the randomness source (the Rand function) from the numerical interval you want to sample. By injecting RNGs, you can achieve reproducible results for patterns or tests, and swap in different distributions if needed without changing the caller. + +## Example +```ts +// Example: deterministic RNG that always returns 0.5 +const half: Rand = () => 0.5; +const value = range(half, 0, 10); // 0 + 0.5 * (10 - 0) = 5 +``` + +## Notes +- Be aware that the result is scaled by rng() and thus depends on the RNG’s typical output range. With a standard Rand that yields values in [0, 1), the result lies in [min, max]. If rng() can return 1, max is reachable as well. +- If your Rand implementation ever returns values outside [0, 1], the produced value may fall outside the [min, max] interval. + + +--- + +## smooth +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** function + +```typescript +const smooth = (t: number) => t * t * (3 - 2 * t) +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `t` | `number` | — | + + +Implements a classic smoothstep easing function. Given a normalized input t in [0,1], it returns t^2*(3-2t), yielding a smooth 0→1 progression that provides a gentle start and end for interpolations. + +## Remarks +This function offers a simple, monotone ease-in-out profile with zero slope at both ends, making it ideal for smoothly interpolating values without abrupt changes. It is self-contained and deterministic, requiring only the input t to produce a predictable eased value. + +## Example +```typescript +const t = 0.25; +const value = smooth(t); // 0.15625 +``` + +## Notes +- Input values outside [0,1] are not constrained; clamp t to [0,1] if you require a strict 0→1 mapping. + +--- + +## valueNoise +> **File:** `src/webapp/src/pulse/patterns.ts` +> **Kind:** function + +```typescript +function valueNoise(x: number, y: number, seed: number): number +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `x` | `number` | — | +| `y` | `number` | — | +| `seed` | `number` | — | + +**Returns:** `number` + + +valueNoise computes a deterministic 2D noise value at coordinates (x, y) by sampling the four lattice corners of the unit cell that contains (x, y) using a seed-based hash function, then smoothly interpolating those corner values with a smoothing function applied to the fractional parts of x and y. The resulting scalar is suitable for generating procedural textures or patterns where a gently varying field is required, and the seed parameter allows varying the pattern while preserving determinism for identical inputs. + +## Remarks +Used as the fundamental 2D noise primitive in procedural textures and pattern systems, valueNoise provides a simple, deterministic surface from which more complex textures can be built by layering octaves or combining with other pattern generators. It encapsulates the standard approach of hashing cell corners and interpolating to produce a smooth value across space, keeping the implementation local to the coordinates and seed. Consumers can swap the hash or interpolation function to change the noise characteristics without altering the call sites. + +## Notes +- Not a cryptographic random source; suitable for visuals but not security. +- The result depends on the concrete implementations of hash2 and smooth used by the surrounding code base; changes to those affect outputs. +- For richer textures, layer multiple octaves or adjust input scale; this function provides the basic primitive that higher-level noise combines. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/pulse/rng.ts.md b/docs/auriondocs/Code/src/webapp/src/pulse/rng.ts.md new file mode 100644 index 0000000..cb6af01 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/pulse/rng.ts.md @@ -0,0 +1,106 @@ +# rng.ts + +> **Source:** `src/webapp/src/pulse/rng.ts` + +## Contents + +- [Rand](#rand) +- [mulberry32](#mulberry32) +- [randomSeed](#randomseed) + +--- + +## Rand +> **File:** `src/webapp/src/pulse/rng.ts` +> **Kind:** type alias + +```typescript +export type Rand = () => number +``` + + +Rand is a type alias for a no-argument function that returns a number. It represents a randomness source that can be passed around to code that needs random values without calling Math.random directly, enabling easy substitution of RNG implementations for testing or different environments. + +## Remarks +Rand provides a contract for RNG providers used by the surrounding code. By abstracting randomness behind Rand, consumers stay decoupled from a concrete global RNG, enabling deterministic tests and flexible RNG strategies. + +## Example +```typescript +const exampleRand: Rand = () => Math.random(); +``` + +## Notes +- Rand is a type alias, not a value. A value of type Rand must be a function with signature () => number. +- Functions assigned to Rand must not require parameters; extra parameters will fail type-checking. +- Using a deterministic Rand in tests (e.g., returning fixed values or a preset sequence) helps verify behavior independent of true randomness. + +--- + +## mulberry32 +> **File:** `src/webapp/src/pulse/rng.ts` +> **Kind:** function + +```typescript +export function mulberry32(seed: number): Rand +``` + +**Parameters:** + +| Parameter | Type | Default | +|-----------|------|---------| +| `seed` | `number` | — | + +**Returns:** `Rand` + + +mulberry32(seed) returns a small, fast, deterministic pseudo-random number generator (Rand) seeded by a 32-bit unsigned integer. The returned function, when called, advances its internal state and produces a uniform value in [0, 1). + +## Remarks +This lightweight abstraction provides a reproducible source of randomness without a full RNG library. It uses a closure to keep internal state private and updates it with each invocation. It is not suitable for cryptographic needs; for security-sensitive randomness, use a cryptographically secure API (e.g., Web Crypto). If you need multiple streams, create separate generators by supplying different seeds. + +## Example +```typescript +// Create a deterministic RNG from a numeric seed +const rng = mulberry32(42); +console.log(rng()); // 0 <= result < 1 +console.log(rng()); // subsequent samples +``` + +## Notes +- Seed normalization: The seed is coerced to an unsigned 32-bit integer via seed >>> 0; non-integer or negative seeds wrap accordingly. +- The PRNG is implemented as a closure over a mutable 32-bit state; subsequent calls advance the state in a deterministic way. +- Not cryptographically secure; use for non-cryptographic tasks only. + +--- + +## randomSeed +> **File:** `src/webapp/src/pulse/rng.ts` +> **Kind:** function + +```typescript +export function randomSeed(): number +``` + +**Returns:** `number` + + +Generates a fresh 32-bit unsigned seed by sampling Math.random. It returns a non-negative integer in the range 0 through 4294967295, intended for initializing a non-cryptographic RNG. + +## Remarks + +Encapsulating the seed generation provides a single, consistent API surface for obtaining seeds without exposing bitwise manipulation to call sites. It relies on non-cryptographic randomness (Math.random); for security-sensitive randomness, use a cryptographic source such as the Web Crypto API. + +## Example +```typescript +// Example usage +const seed = randomSeed(); +console.log('seed:', seed); +``` + +## Notes +- Not suitable for cryptographic purposes: Math.random() is not a cryptographically secure source. +- The produced value is a number in the inclusive range [0, 4294967295], representing a 32-bit unsigned seed. +- Sequences produced by repeated calls are not guaranteed to be unique; seeds may repeat over time depending on the underlying PRNG. + + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/router.tsx.md b/docs/auriondocs/Code/src/webapp/src/router.tsx.md new file mode 100644 index 0000000..cfb400b --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/router.tsx.md @@ -0,0 +1,148 @@ +# router.tsx + +> **Source:** `src/webapp/src/router.tsx` + +## Contents + +- [ErrorBoundaryProps](#errorboundaryprops) +- [ErrorBoundaryState](#errorboundarystate) +- [ErrorBoundary](#errorboundary) +- [ChunkErrorFallback](#chunkerrorfallback) +- [LazyPage](#lazypage) +- [PageLoader](#pageloader) + +--- + +## ErrorBoundaryProps +> **File:** `src/webapp/src/router.tsx` +> **Kind:** interface + +```typescript +interface ErrorBoundaryProps +``` + + +ErrorBoundaryProps defines the props contract for an error boundary component in this codebase. It requires two renderable values: children, the content to render under normal operation, and fallback, the UI to render when an error occurs within the subtree. This interface exemplifies the standard React error-boundary pattern by separating normal rendering from error UI and enabling strong typing for boundary usage. + +## Remarks +This interface isolates error-handling concerns from rendering logic by providing a clear, strongly-typed boundary contract. Requiring a concrete fallback ensures a predictable UX when an error is caught, rather than letting the error bubble up. The ReactNode type broadens what can be rendered for both children and fallback, supporting simple text, complex elements, or even null. + +## Notes +- Both props are required by the type; consumers cannot omit them at compile time. +- ReactNode covers a wide range of renderable values, so design the fallback and children to be resilient to different shapes (elements, strings, or null). + +--- + +## ErrorBoundaryState +> **File:** `src/webapp/src/router.tsx` +> **Kind:** interface + +```typescript +interface ErrorBoundaryState +``` + + +ErrorBoundaryState defines the minimal, compile-time contract for the state of an error boundary component: a single hasError flag that signals when an error has been caught so the UI can render a fallback. Use this interface to type the boundary’s state rather than relying on ad-hoc object shapes, ensuring consistent error-path rendering. + +## Remarks + +By modeling the error flag behind an interface, the boundary's implementation can focus on the semantics of error handling rather than the particulars of how state is stored. It pairs with error-handling hooks in the boundary to drive the conditional render path. Remember: TypeScript interfaces exist only at compile time and have no runtime footprint; the actual state object must be created and conform to ErrorBoundaryState at runtime according to your framework's conventions. + +## Notes + +- This interface has no runtime footprint; a concrete object that conforms to ErrorBoundaryState must be created to represent the boundary's state at runtime. +- If you extend this shape with additional fields, keep mutation and update patterns in sync so the boundary's render path remains predictable. + + +--- + +## ErrorBoundary +> **File:** `src/webapp/src/router.tsx` +> **Kind:** class + +```typescript +class ErrorBoundary extends Component +``` + + +ErrorBoundary is a React class component that acts as a protective wrapper around its children, catching rendering errors within its subtree and rendering a fallback UI instead of crashing the entire application. It uses a static getDerivedStateFromError to flip hasError to true, and logs errors via componentDidCatch, with a special-case for chunk-loading failures to surface clearer logs when a new version may be available. When an error is detected, render returns the provided fallback UI; otherwise, it renders its children. + +## Remarks +Error boundaries isolate rendering errors to a subtree, preventing a failure in one part of the UI from crashing the whole page. This implementation includes a focused log path for stale chunk scenarios by checking the 'Failed to fetch dynamically imported module' message, helping diagnose deployment-related issues. It relies on ErrorBoundaryProps and ErrorBoundaryState to drive whether to render the fallback or the children. + +## Notes +- Catches errors during rendering, lifecycle methods, and constructors of the subtree; does not automatically catch errors in event handlers. +- The static getDerivedStateFromError updates the boundary state, while componentDidCatch handles logging and diagnostics. +- The chunk-loading error path is a heuristic to surface deployment-related issues and may vary across environments. + +--- + +## ChunkErrorFallback +> **File:** `src/webapp/src/router.tsx` +> **Kind:** function + +```typescript +function ChunkErrorFallback() +``` + + +ChunkErrorFallback is a tiny React function component that renders a browser-styled error UI when a lazily-loaded route chunk fails to load. It presents a brief message and a Reload button that performs a full page refresh to fetch a fresh version of the application. + +## Remarks +The component encapsulates a straightforward recovery UX so callers don't need to implement their own retry logic for failed dynamic imports. By offering a single, deterministic action—reload—it avoids partial or inconsistent state that can occur after a failed chunk load. The UI is self-contained and reusable across different error boundaries or routes. + +## Example +```typescript +// Simple usage + +``` + +## Notes +- This component relies on browser APIs (window). It is safe for client-side rendering, but rendering on the server will not trigger the onClick handler and may require guards in SSR setups. +- Triggering a full reload clears in-memory state; consider a more nuanced retry strategy if preserving user data is important. + +--- + +## LazyPage +> **File:** `src/webapp/src/router.tsx` +> **Kind:** function + +```typescript +function LazyPage( +``` + + +LazyPage is a React function component that accepts a props object containing a children property. Based on the visible signature, it appears to function as a lightweight wrapper around its children, intended for use within the router configuration in src/webapp/src/router.tsx. The snippet does not reveal how it renders its content or any side effects, so its exact behavior cannot be determined from this fragment alone. + +## Remarks +Wrapper-style components like LazyPage are often used to isolate layout concerns from individual routes. Naming a wrapper around routed content communicates intent and makes it easier to apply consistent wrappers, suspense boundaries, or lazy-loading behavior across multiple pages without duplicating boilerplate. + +## Notes +- To preserve type safety in TypeScript, define children as React.ReactNode (instead of any) and consider making it optional if not every route renders children. + +--- + +## PageLoader +> **File:** `src/webapp/src/router.tsx` +> **Kind:** function + +```typescript +function PageLoader() +``` + + +PageLoader is a small, presentation-only React function component that renders a loading indicator: a div with the className auth-loading and the text Loading…. It provides a reusable UI fragment you can drop in during authentication flows or page transitions to signal that content is loading, avoiding inline markup duplication. + +## Remarks +This is deliberately presentation-focused and has no state or props. It centralizes the loading UI behind a single component and a CSS hook (auth-loading), making it easy to swap in a different loader or apply consistent styling across the app without touching call sites. + +## Example +```typescript + +``` + +## Notes +- The component is stateless and always renders the same markup; for dynamic messaging or progress, parameterize or replace with a different loader. +- Ensure the auth-loading CSS exists in your project to style the indicator; without styling, the loader may be unstyled or invisible. + +--- \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/routes/ProtectedRoute.tsx.md b/docs/auriondocs/Code/src/webapp/src/routes/ProtectedRoute.tsx.md new file mode 100644 index 0000000..559b750 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/routes/ProtectedRoute.tsx.md @@ -0,0 +1,18 @@ +# ProtectedRoute + +> **File:** `src/webapp/src/routes/ProtectedRoute.tsx` +> **Kind:** function + +```typescript +export function ProtectedRoute() +``` + + +ProtectedRoute is a React component that guards its nested routes by enforcing an authenticated user. It reads authentication state from the app's auth context via useAuth and preserves navigation intent with useLocation so users return to their original destination after logging in. If the auth state is still loading (user is undefined), it renders a lightweight loading placeholder; if there is no authenticated user (user === null), it redirects to /login and passes the current location in state to support post-login redirection; when a user is present, it renders the child routes via Outlet. + +## Remarks +ProtectedRoute centralizes the common authentication-guard pattern for React Router, reducing duplication and aligning login-flow behavior across protected sections. It relies on Navigate for redirection and Outlet to render nested routes, keeping the routing logic declarative and easy to audit. + +## Notes +- Hard-coded login path '/login' is a coupling; adapt if your app uses a different login route or a configurable redirect. +- The loading state (undefined user) is client-side and may briefly show the loading placeholder even if a session exists; ensure the auth provider properly initialises the user state to minimise flicker. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/routes/PublicRoute.tsx.md b/docs/auriondocs/Code/src/webapp/src/routes/PublicRoute.tsx.md new file mode 100644 index 0000000..37e6019 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/routes/PublicRoute.tsx.md @@ -0,0 +1,18 @@ +# PublicRoute + +> **File:** `src/webapp/src/routes/PublicRoute.tsx` +> **Kind:** function + +```typescript +export function PublicRoute() +``` + + +PublicRoute is a small React component that acts as a gate for routes intended for unauthenticated users. It reads the current user from the authentication context via useAuth and renders one of three outcomes: a loading indicator while the auth state is unresolved (user === undefined), a redirect to the home page ("/") when a user is already authenticated (user is truthy), or an Outlet to render nested routes when there is no authenticated user. This pattern centralizes the logic for public pages (e.g., login or signup) so authenticated users are consistently redirected away and unauthenticated users can access the public content. + +## Remarks +PublicRoute serves as the centralized gate for routes meant for guests. It decouples the decision logic from each page by consulting the auth state and either redirecting authenticated users away or exposing the nested routes to unauthenticated visitors. It sits as the element for a route in React Router, with child routes rendered via Outlet, so you can group all public pages under this wrapper. + +## Notes +- The loading state occurs while useAuth has not yet resolved; ensure your auth provider eventually sets a defined user value. +- The redirect uses replace to prevent back-navigation to the auth page after authentication; adjust the target path if your app uses a different home route. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/src/vite-env.d.ts.md b/docs/auriondocs/Code/src/webapp/src/vite-env.d.ts.md new file mode 100644 index 0000000..5844c37 --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/src/vite-env.d.ts.md @@ -0,0 +1,15 @@ +# vite-env.d + +> **File:** `src/webapp/src/vite-env.d.ts` +> **Kind:** file + + +vite-env.d.ts is a minimal ambient declaration that pulls in Vite's client typings for the TypeScript project. By referencing vite/client, it makes Vite's runtime globals and environment typings available to the compiler and IDE without emitting any runtime code. + +## Remarks +These typings are centralized to ensure consistent type availability across the project without introducing runtime dependencies. It ensures that TypeScript and IDEs understand Vite-specific globals (such as import.meta.env) and related runtime behavior at development time, reducing false type errors. + +## Notes +- This file is pure type information; no emitted JavaScript is produced. +- If your tsconfig.json uses a restricted 'types' array, you may need to add 'vite/client' to that list or rely on this ambient file to supply the types. +- Renaming or deleting this file can cause TypeScript to lose awareness of Vite-specific globals. \ No newline at end of file diff --git a/docs/auriondocs/Code/src/webapp/vite.config.ts.md b/docs/auriondocs/Code/src/webapp/vite.config.ts.md new file mode 100644 index 0000000..6d0208d --- /dev/null +++ b/docs/auriondocs/Code/src/webapp/vite.config.ts.md @@ -0,0 +1,23 @@ +# vite.config + +> **File:** `src/webapp/vite.config.ts` +> **Kind:** file + + +Proposes a Vite configuration that wires React support and a development-time proxy. It exports a config with the React plugin enabled and a development server configured to listen on port 6080. All requests to /api are proxied to http://localhost:6040, so frontend code can call backend endpoints using a single origin during development without CORS headaches. + +## Remarks +This abstraction centralizes the development-time wiring of the frontend and backend. By keeping the API path under /api, the frontend remains decoupled from the exact backend host while still preserving a realistic request path. The React plugin integration ensures fast refresh and seamless JSX/TSX handling during development. + +## Example +```typescript +// Example: a typical frontend call to the backend API during development +fetch('/api/users') + .then((r) => r.json()) + .then((data) => console.log(data)) + .catch((e) => console.error(e)); +``` + +## Notes +- This proxy is active only in development; production builds should use the backend host directly or a separate reverse-proxy. +- If the backend port or path changes, update the proxy target accordingly. \ No newline at end of file diff --git a/docs/auriondocs/README.md b/docs/auriondocs/README.md new file mode 100644 index 0000000..38c0e86 --- /dev/null +++ b/docs/auriondocs/README.md @@ -0,0 +1,47 @@ +# HueByte/Gabriel — Documentation + +> Continuously generated, source-verified documentation for **HueByte/Gabriel**, built from branch `main` at commit `0343e2f8`. Every page is derived from the code itself and cross-checked against the source before it ships. + +Gabriel provides an agent runtime and HTTP-facing API that together expose programmable "tools" to drive conversations and project/file operations end-to-end. The engine (src/api/Gabriel.Engine) hosts a catalog of ITool implementations (e.g. file, web, text, memory and codec tools) that the API and agent logic call into, while conversational and agent state is persisted via the infrastructure persistence configuration (e.g. ConversationConfiguration) and lightweight memory entries managed by memory tools. Interaction is primarily request/response over the API surface with the engine invoking tools and builders to construct system prompts and perform work. + +## Start here + +1. **[Architecture](Synthesis/Architecture.md)** — the system overview: what the components are, how they collaborate, and where to find each one. +2. **[Onboarding](Synthesis/Onboarding.md)** — a curated reading path through the codebase. Start here if you're new to the project. +3. **[Code reference](Code/index.md)** — per-file API documentation mirroring the source tree, one page per documented file. + +## Deep dives + +Cross-file guides on the themes that shape this codebase: + +- [Authentication and Authorization](Synthesis/authentication.md) — Exposes authentication endpoints for login and token refresh. +- [Data persistence and repositories](Synthesis/data-persistence.md) — Implements data access for conversations. +- [Dependency injection and service bootstrap](Synthesis/dependency-injection.md) — This topic documents the centralized dependency-injection wiring that composes the application's core domain, engine runtime, and infrastructure providers so the app can resolve services predictably at runtime. +- [HTTP API surface and contracts](Synthesis/http-api-surface.md) — This guide describes the HTTP API surface: the controllers that expose REST endpoints and the DTO contracts they send and receive. +- [Web search and fetch tooling](Synthesis/web-search-and-fetch.md) — This topic covers the small set of web-search provider implementations and the interface they implement. + +## Recurring workflows + +Step-by-step templates for the patterns this codebase repeats, each grounded in real examples from the source: + +- [Adding a new agent tool](Synthesis/Workflows/agent-tool.md) +- [Adding a new controller](Synthesis/Workflows/controller.md) +- [Adding a new repository](Synthesis/Workflows/repository.md) +- [Adding a new service](Synthesis/Workflows/service.md) + +## By the numbers + +- **250** documentation pages covering **250** source files +- **532** symbols documented and validated +- **22,515** lines of code analyzed +- Languages: C# (200), TypeScript (React) (31), TypeScript (14), JavaScript (5) + +## How these docs are organized + +- **`Code/`** mirrors the repository's source tree one-to-one — `src/Foo.cs` is documented at `Code/src/Foo.cs.md`. +- **`Synthesis/`** holds knowledge derived *across* files: the architecture overview, onboarding path, topic deep-dives, and workflow templates. +- The set is regenerated as the code changes; drift between code and docs is detected and repaired automatically. + +--- + +*Generated by [AurionDocs](https://auriondocs.com) from `main` at commit `0343e2f8` on 2026-07-08 05:48 UTC.* diff --git a/docs/auriondocs/Synthesis/Architecture.md b/docs/auriondocs/Synthesis/Architecture.md new file mode 100644 index 0000000..66271e4 --- /dev/null +++ b/docs/auriondocs/Synthesis/Architecture.md @@ -0,0 +1,125 @@ +# Architecture — HueByte/Gabriel + +> *Auto-synthesized from 532 documented symbols across 250 files on `main`.* + +## Topic Guides + +Deep-dives into cross-cutting concerns synthesized from the per-symbol corpus. + +- [Authentication and Authorization](authentication.md) — How the API authenticates users, issues tokens, and manages identity across the API and infrastructure. +- [Dependency injection and service bootstrap](dependency-injection.md) — Centralized DI wiring across core, engine, and infrastructure layers to compose the app. +- [HTTP API surface and contracts](http-api-surface.md) — Controllers expose REST endpoints and the associated request/response contracts across modules. +- [Data persistence and repositories](data-persistence.md) — Repositories, entities, and UoW coordinating persistence for conversations and memory. +- [Web search and fetch tooling](web-search-and-fetch.md) — Web search providers and fetch utilities used by the engine tooling. + +## Architecture Diagram + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +flowchart TB + n0["src/api/Gabriel.Engine · AgentService (20 files)"] + n1["src/api/Gabriel.Engine · DependencyInjection (27 files)"] + n2["src/webapp/src/api (5 files)"] + n3["src/api/Gabriel.Engine · Project (13 files)"] + n4["src/webapp/src/components · ContractMappings (6 files)"] + n5["src/api/Gabriel.Infrastructure · DependencyInjection (10 files)"] + n6["src/webapp/src/pulse (2 files)"] + n7["src/api/Gabriel.Core · MemoryEntry (8 files)"] + n8["src/api/Gabriel.Engine · DomainException (8 files)"] + n9["src/webapp/src/pulse · Patterns (3 files)"] + n0 -->|14| n1 + n0 -->|7| n3 + n0 -->|2| n7 + n1 -->|6| n0 + n1 -->|9| n3 + n1 -->|2| n5 + n1 -->|6| n7 + n1 -->|7| n8 + n3 -->|2| n0 + n3 -->|4| n1 + n3 -->|6| n2 + n3 -->|3| n4 + n3 -->|8| n9 + n4 -->|3| n0 + n4 -->|3| n2 + n4 -->|2| n3 + n5 -->|9| n0 + n5 -->|6| n1 + n5 -->|3| n3 + n5 -->|2| n7 + n6 -->|3| n4 + n7 -->|3| n3 + n8 -->|10| n1 + n8 -->|6| n3 +``` + +## System Overview +Gabriel provides an agent runtime and HTTP-facing API that together expose programmable "tools" to drive conversations and project/file operations end-to-end. The engine (src/api/Gabriel.Engine) hosts a catalog of ITool implementations (e.g. file, web, text, memory and codec tools) that the API and agent logic call into, while conversational and agent state is persisted via the infrastructure persistence configuration (e.g. ConversationConfiguration) and lightweight memory entries managed by memory tools. Interaction is primarily request/response over the API surface with the engine invoking tools and builders to construct system prompts and perform work. + +## Key Components +**Agent Tools** — Provide discrete capabilities the agent runtime can call (file ops, web/search, text transforms, codecs, memory, project I/O, etc.). Implemented by [`Base64Tool`](../Code/src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md), [`BaseConvertTool`](../Code/src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md), [`CalculateTool`](../Code/src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md), [`ColorConvertTool`](../Code/src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md), [`DocsListTool`](../Code/src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md), [`DocsReadTool`](../Code/src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md), [`FileInfoTool`](../Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md), [`FindTool`](../Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md), [`GetCurrentTimeTool`](../Code/src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md), [`GrepTool`](../Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md), [`HashTool`](../Code/src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md), [`JsonFormatTool`](../Code/src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs.md), [`ListDirTool`](../Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md), [`ListProjectFilesTool`](../Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md), [`MemoryListTool`](../Code/src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md), [`MemoryRemoveTool`](../Code/src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md), [`MemorySaveTool`](../Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md), [`ReadProjectFileTool`](../Code/src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md), [`TextStatsTool`](../Code/src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md), [`TextTransformTool`](../Code/src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md), [`WebFetchTool`](../Code/src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md), [`WebSearchTool`](../Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md), and the core interface [`ITool`](../Code/src/api/Gabriel.Engine/Tools/ITool.cs.md). + +**Prompt Builders** — Construct the system prompt and personality for agents used during conversation handling. Implemented by [`GabrielSystemPromptBuilder`](../Code/src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md) and the interface [`ISystemPromptBuilder`](../Code/src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md). + +**Configuration** — Holds typed configuration sections that drive agent behavior, tools, authentication and search integration. Implemented by [`AgentOptions`](../Code/src/api/Gabriel.Core/Configuration/AgentOptions.cs.md), [`AgentToolsOptions`](../Code/src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md), [`AuthOptions`](../Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md), and [`BraveSearchOptions`](../Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md). + +**Persistence / Repositories** — Defines how conversation state is mapped and stored in the persistence layer (entity configuration). Implemented by [`ConversationConfiguration`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md). + +## Component Map + +*Subsystems below are structural clusters detected from the dependency graph — groups of symbols more densely wired to each other than to the rest of the codebase.* + +- **src/api/Gabriel.Engine · DependencyInjection** — 27 documented files +- **src/api/Gabriel.Engine · AgentService** — 20 documented files +- **src/api/Gabriel.Engine · Project** — 13 documented files +- **src/webapp/src/components** — 13 documented files +- **src/api/Gabriel.Infrastructure · DependencyInjection** — 10 documented files +- **src/api/Gabriel.Core · ICurrentUser** — 9 documented files +- **src/api/Gabriel.Core · MemoryEntry** — 8 documented files +- **src/api/Gabriel.Engine · DomainException** — 8 documented files +- **src/api/Gabriel.Engine · ConversationState** — 7 documented files +- **src/api/Gabriel.Engine · GabrielSystemPromptBuilder** — 7 documented files +- **src/api/Gabriel.API · AuthController** — 6 documented files +- **src/api/Gabriel.API · Program** — 6 documented files +- *…and 46 more subsystem folders* + +### Components by Role + +**Agent Tools** +- `Base64Tool` — `src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs` +- `BaseConvertTool` — `src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs` +- `CalculateTool` — `src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs` +- `ColorConvertTool` — `src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs` +- `DocsListTool` — `src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs` +- `DocsReadTool` — `src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs` +- `FileInfoTool` — `src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs` +- `FindTool` — `src/api/Gabriel.Engine/Tools/Files/FindTool.cs` +- `GetCurrentTimeTool` — `src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs` +- `GrepTool` — `src/api/Gabriel.Engine/Tools/Files/GrepTool.cs` +- `HashTool` — `src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs` +- `ITool` — `src/api/Gabriel.Engine/Tools/ITool.cs` +- `JsonFormatTool` — `src/api/Gabriel.Engine/Tools/Data/JsonFormatTool.cs` +- `ListDirTool` — `src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs` +- `ListProjectFilesTool` — `src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs` +- `MemoryListTool` — `src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs` +- `MemoryRemoveTool` — `src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs` +- `MemorySaveTool` — `src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs` +- `ReadProjectFileTool` — `src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs` +- `TextStatsTool` — `src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs` +- `TextTransformTool` — `src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs` +- `WebFetchTool` — `src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs` +- `WebSearchTool` — `src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs` + +**Builders** +- `GabrielSystemPromptBuilder` — `src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs` +- `ISystemPromptBuilder` — `src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs` + +**Configuration** +- `AgentOptions` — `src/api/Gabriel.Core/Configuration/AgentOptions.cs` +- `AgentToolsOptions` — `src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs` +- `AuthOptions` — `src/api/Gabriel.Core/Configuration/AuthOptions.cs` +- `BraveSearchOptions` — `src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs` +- `ConversationConfiguration` — `src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs` + +--- +*Generated by Aurion on 2026-07-08 05:48:37 UTC* diff --git a/docs/auriondocs/Synthesis/DriftReport.md b/docs/auriondocs/Synthesis/DriftReport.md new file mode 100644 index 0000000..305b430 --- /dev/null +++ b/docs/auriondocs/Synthesis/DriftReport.md @@ -0,0 +1,306 @@ +# Drift Report — HueByte/Gabriel + +> 6 stale, 15 ambiguous, 241 ok since commit `03c8997`. + +**Drift score:** ![clean (2.3%)](https://img.shields.io/badge/drift-2.3%2525%20clean-brightgreen) + +| Status | Count | +| --- | ---: | +| 🔴 Broken | 0 | +| 🟡 Stale | 6 | +| 🟠 Ambiguous | 15 | +| 🟢 OK | 241 | +| **Total** | **262** | + +## Broken (0) + +Docs that reference symbols the merge deleted. These must be regenerated or removed before publish. + +*None.* + +## Stale (6) + +Docs whose underlying symbols changed in this merge. Targeted regeneration is in flight. + +- [`src/api/Gabriel.Engine/DependencyInjection.cs`](../Code/src/api/Gabriel.Engine/DependencyInjection.cs.md) — References 1 modified symbol(s) +- [`src/api/Gabriel.Infrastructure/DependencyInjection.cs`](../Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md) — References 1 modified symbol(s) +- [`src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md) — References 1 modified symbol(s) +- [`src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs`](../Code/src/api/Gabriel.Infrastructure/Providers/GrokChatProvider.cs.md) — References 1 modified symbol(s) +- [`src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md) — References 1 modified symbol(s) +- [`src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Colors/ColorConvertTool.cs.md) — New file — needs initial documentation + +## Ambiguous (15) + +Docs that reference symbols the baseline cache doesn't know about — likely a stale cache rather than real drift. + +- [`prototype/palettes.js`](../Code/prototype/palettes.js.md) — References an unknown symbol — cache may be stale +- [`prototype/patterns.js`](../Code/prototype/patterns.js.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/router.tsx`](../Code/src/webapp/src/router.tsx.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/pulse/rng.ts`](../Code/src/webapp/src/pulse/rng.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/api/models.ts`](../Code/src/webapp/src/api/models.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/lib/notify.ts`](../Code/src/webapp/src/lib/notify.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/api/memories.ts`](../Code/src/webapp/src/api/memories.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/api/sequence.ts`](../Code/src/webapp/src/api/sequence.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/lib/userPrefs.ts`](../Code/src/webapp/src/lib/userPrefs.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/pulse/palettes.ts`](../Code/src/webapp/src/pulse/palettes.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/pulse/patterns.ts`](../Code/src/webapp/src/pulse/patterns.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/api/authRefresh.ts`](../Code/src/webapp/src/api/authRefresh.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/auth/AuthContext.tsx`](../Code/src/webapp/src/auth/AuthContext.tsx.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/api/conversationMode.ts`](../Code/src/webapp/src/api/conversationMode.ts.md) — References an unknown symbol — cache may be stale +- [`src/webapp/src/components/Markdown.tsx`](../Code/src/webapp/src/components/Markdown.tsx.md) — References an unknown symbol — cache may be stale + +## OK (241) + +Docs the merge did not impact. No action required. + +

+Show 241 unaffected doc(s) + +- [`index.md`](../Code/index.md.md) +- [`README.md`](../Code/README.md.md) +- [`Onboarding.md`](../Code/Onboarding.md.md) +- [`Architecture.md`](../Code/Architecture.md.md) +- [`prototype/run.js`](../Code/prototype/run.js.md) +- [`authentication.md`](../Code/authentication.md.md) +- [`prototype/play.js`](../Code/prototype/play.js.md) +- [`ai-tools-engine.md`](../Code/ai-tools-engine.md.md) +- [`configuration-di.md`](../Code/configuration-di.md.md) +- [`Workflows/service.md`](../Code/Workflows/service.md.md) +- [`prototype/generate.js`](../Code/prototype/generate.js.md) +- [`sequence-rendering.md`](../Code/sequence-rendering.md.md) +- [`src/webapp/src/App.tsx`](../Code/src/webapp/src/App.tsx.md) +- [`Workflows/agent-tool.md`](../Code/Workflows/agent-tool.md.md) +- [`Workflows/controller.md`](../Code/Workflows/controller.md.md) +- [`Workflows/repository.md`](../Code/Workflows/repository.md.md) +- [`src/webapp/src/main.tsx`](../Code/src/webapp/src/main.tsx.md) +- [`src/webapp/vite.config.ts`](../Code/src/webapp/vite.config.ts.md) +- [`data-access-persistence.md`](../Code/data-access-persistence.md.md) +- [`src/webapp/src/vite-env.d.ts`](../Code/src/webapp/src/vite-env.d.ts.md) +- [`src/api/Gabriel.API/Program.cs`](../Code/src/api/Gabriel.API/Program.cs.md) +- [`src/webapp/src/api/streamChat.ts`](../Code/src/webapp/src/api/streamChat.ts.md) +- [`src/webapp/src/pages/ChatPage.tsx`](../Code/src/webapp/src/pages/ChatPage.tsx.md) +- [`src/webapp/src/components/Chat.tsx`](../Code/src/webapp/src/components/Chat.tsx.md) +- [`src/webapp/src/pages/IndexPage.tsx`](../Code/src/webapp/src/pages/IndexPage.tsx.md) +- [`src/webapp/src/pages/LoginPage.tsx`](../Code/src/webapp/src/pages/LoginPage.tsx.md) +- [`src/webapp/src/components/Avatar.tsx`](../Code/src/webapp/src/components/Avatar.tsx.md) +- [`src/api/Gabriel.Engine/Tools/ITool.cs`](../Code/src/api/Gabriel.Engine/Tools/ITool.cs.md) +- [`src/webapp/src/api/authInterceptor.ts`](../Code/src/webapp/src/api/authInterceptor.ts.md) +- [`src/webapp/src/components/Mermaid.tsx`](../Code/src/webapp/src/components/Mermaid.tsx.md) +- [`src/webapp/src/components/Sidebar.tsx`](../Code/src/webapp/src/components/Sidebar.tsx.md) +- [`src/webapp/src/layouts/AuthLayout.tsx`](../Code/src/webapp/src/layouts/AuthLayout.tsx.md) +- [`src/webapp/src/layouts/MainLayout.tsx`](../Code/src/webapp/src/layouts/MainLayout.tsx.md) +- [`src/webapp/src/pages/RegisterPage.tsx`](../Code/src/webapp/src/pages/RegisterPage.tsx.md) +- [`src/webapp/src/routes/PublicRoute.tsx`](../Code/src/webapp/src/routes/PublicRoute.tsx.md) +- [`src/api/Gabriel.Core/Entities/Message.cs`](../Code/src/api/Gabriel.Core/Entities/Message.cs.md) +- [`src/api/Gabriel.Core/Entities/Project.cs`](../Code/src/api/Gabriel.Core/Entities/Project.cs.md) +- [`src/api/Gabriel.Core/Personality/Mood.cs`](../Code/src/api/Gabriel.Core/Personality/Mood.cs.md) +- [`src/api/Gabriel.Engine/Sequence/Frame.cs`](../Code/src/api/Gabriel.Engine/Sequence/Frame.cs.md) +- [`src/api/Gabriel.Engine/Sequence/Noise.cs`](../Code/src/api/Gabriel.Engine/Sequence/Noise.cs.md) +- [`src/webapp/src/components/MemoryList.tsx`](../Code/src/webapp/src/components/MemoryList.tsx.md) +- [`src/webapp/src/components/SkinPicker.tsx`](../Code/src/webapp/src/components/SkinPicker.tsx.md) +- [`src/webapp/src/pages/DiagnosticsPage.tsx`](../Code/src/webapp/src/pages/DiagnosticsPage.tsx.md) +- [`src/webapp/src/routes/ProtectedRoute.tsx`](../Code/src/webapp/src/routes/ProtectedRoute.tsx.md) +- [`src/webapp/src/pages/UserSettingsPage.tsx`](../Code/src/webapp/src/pages/UserSettingsPage.tsx.md) +- [`src/api/Gabriel.Engine/Sequence/Palette.cs`](../Code/src/api/Gabriel.Engine/Sequence/Palette.cs.md) +- [`src/webapp/src/components/ContextStats.tsx`](../Code/src/webapp/src/components/ContextStats.tsx.md) +- [`src/webapp/src/components/ModeSelector.tsx`](../Code/src/webapp/src/components/ModeSelector.tsx.md) +- [`src/api/Gabriel.API/Identity/AuthCookies.cs`](../Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md) +- [`src/api/Gabriel.Core/DependencyInjection.cs`](../Code/src/api/Gabriel.Core/DependencyInjection.cs.md) +- [`src/api/Gabriel.Engine/Sequence/Patterns.cs`](../Code/src/api/Gabriel.Engine/Sequence/Patterns.cs.md) +- [`src/api/Gabriel.Engine/Sequence/RgbColor.cs`](../Code/src/api/Gabriel.Engine/Sequence/RgbColor.cs.md) +- [`src/webapp/src/components/ModelSelector.tsx`](../Code/src/webapp/src/components/ModelSelector.tsx.md) +- [`src/webapp/src/components/ProjectPicker.tsx`](../Code/src/webapp/src/components/ProjectPicker.tsx.md) +- [`src/webapp/src/components/StreamingText.tsx`](../Code/src/webapp/src/components/StreamingText.tsx.md) +- [`src/webapp/src/components/ThinkingPulse.tsx`](../Code/src/webapp/src/components/ThinkingPulse.tsx.md) +- [`src/api/Gabriel.Core/Entities/GabrielMode.cs`](../Code/src/api/Gabriel.Core/Entities/GabrielMode.cs.md) +- [`src/api/Gabriel.Core/Entities/MemoryEntry.cs`](../Code/src/api/Gabriel.Core/Entities/MemoryEntry.cs.md) +- [`src/api/Gabriel.Core/Entities/MessageRole.cs`](../Code/src/api/Gabriel.Core/Entities/MessageRole.cs.md) +- [`src/api/Gabriel.Core/Entities/MetricEntry.cs`](../Code/src/api/Gabriel.Core/Entities/MetricEntry.cs.md) +- [`src/api/Gabriel.Core/Entities/ProjectFile.cs`](../Code/src/api/Gabriel.Core/Entities/ProjectFile.cs.md) +- [`src/api/Gabriel.Core/Services/ChatService.cs`](../Code/src/api/Gabriel.Core/Services/ChatService.cs.md) +- [`src/api/Gabriel.Engine/Tools/ToolRegistry.cs`](../Code/src/api/Gabriel.Engine/Tools/ToolRegistry.cs.md) +- [`src/webapp/src/pages/ProjectSettingsPage.tsx`](../Code/src/webapp/src/pages/ProjectSettingsPage.tsx.md) +- [`src/api/Gabriel.Core/Entities/Conversation.cs`](../Code/src/api/Gabriel.Core/Entities/Conversation.cs.md) +- [`src/api/Gabriel.Core/Identity/ICurrentUser.cs`](../Code/src/api/Gabriel.Core/Identity/ICurrentUser.cs.md) +- [`src/api/Gabriel.Core/Identity/RefreshToken.cs`](../Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md) +- [`src/api/Gabriel.Core/Services/IChatService.cs`](../Code/src/api/Gabriel.Core/Services/IChatService.cs.md) +- [`src/api/Gabriel.Engine/Sequence/FrameLayer.cs`](../Code/src/api/Gabriel.Engine/Sequence/FrameLayer.cs.md) +- [`src/api/Gabriel.Engine/Services/AgentEvent.cs`](../Code/src/api/Gabriel.Engine/Services/AgentEvent.cs.md) +- [`src/api/Gabriel.Engine/Tools/IToolRegistry.cs`](../Code/src/api/Gabriel.Engine/Tools/IToolRegistry.cs.md) +- [`src/webapp/src/components/MemoryQuickSave.tsx`](../Code/src/webapp/src/components/MemoryQuickSave.tsx.md) +- [`src/api/Gabriel.API/Identity/IdentitySeeder.cs`](../Code/src/api/Gabriel.API/Identity/IdentitySeeder.cs.md) +- [`src/api/Gabriel.Core/Configuration/LLMModel.cs`](../Code/src/api/Gabriel.Core/Configuration/LLMModel.cs.md) +- [`src/api/Gabriel.Core/Configuration/ToolMode.cs`](../Code/src/api/Gabriel.Core/Configuration/ToolMode.cs.md) +- [`src/api/Gabriel.Core/Services/MemoryService.cs`](../Code/src/api/Gabriel.Core/Services/MemoryService.cs.md) +- [`src/api/Gabriel.Engine/Sequence/PatternKind.cs`](../Code/src/api/Gabriel.Engine/Sequence/PatternKind.cs.md) +- [`src/api/Gabriel.Engine/Tools/Files/FindTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Files/GrepTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs`](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) +- [`src/api/Gabriel.API/Mapping/ContractMappings.cs`](../Code/src/api/Gabriel.API/Mapping/ContractMappings.cs.md) +- [`src/api/Gabriel.Core/Services/IMemoryService.cs`](../Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md) +- [`src/api/Gabriel.Core/Services/ProjectService.cs`](../Code/src/api/Gabriel.Core/Services/ProjectService.cs.md) +- [`src/api/Gabriel.Engine/Services/AgentContext.cs`](../Code/src/api/Gabriel.Engine/Services/AgentContext.cs.md) +- [`src/api/Gabriel.Engine/Services/AgentService.cs`](../Code/src/api/Gabriel.Engine/Services/AgentService.cs.md) +- [`src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Codecs/HashTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs`](../Code/src/api/Gabriel.Engine/Tools/Web/IUrlFetcher.cs.md) +- [`src/webapp/src/components/CompactingOverlay.tsx`](../Code/src/webapp/src/components/CompactingOverlay.tsx.md) +- [`src/api/Gabriel.API/Contracts/Auth/MeResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md) +- [`src/api/Gabriel.API/Contracts/Models/ModelDto.cs`](../Code/src/api/Gabriel.API/Contracts/Models/ModelDto.cs.md) +- [`src/api/Gabriel.Core/Configuration/JwtOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/JwtOptions.cs.md) +- [`src/api/Gabriel.Core/Entities/MemoryEntryType.cs`](../Code/src/api/Gabriel.Core/Entities/MemoryEntryType.cs.md) +- [`src/api/Gabriel.Core/Repositories/IUnitOfWork.cs`](../Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md) +- [`src/api/Gabriel.Core/Services/IProjectService.cs`](../Code/src/api/Gabriel.Core/Services/IProjectService.cs.md) +- [`src/api/Gabriel.Engine/Providers/ModelCatalog.cs`](../Code/src/api/Gabriel.Engine/Providers/ModelCatalog.cs.md) +- [`src/api/Gabriel.Engine/Services/IAgentService.cs`](../Code/src/api/Gabriel.Engine/Services/IAgentService.cs.md) +- [`src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs`](../Code/src/api/Gabriel.Engine/Tools/Docs/IDocsLookup.cs.md) +- [`src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Web/WebFetchTool.cs.md) +- [`src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md) +- [`src/api/Gabriel.API/Controllers/AuthController.cs`](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) +- [`src/api/Gabriel.Core/Configuration/AuthOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md) +- [`src/api/Gabriel.Core/Configuration/GrokOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/GrokOptions.cs.md) +- [`src/api/Gabriel.Core/Identity/IJwtTokenService.cs`](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) +- [`src/api/Gabriel.Core/Identity/IUserPreferences.cs`](../Code/src/api/Gabriel.Core/Identity/IUserPreferences.cs.md) +- [`src/api/Gabriel.Engine/Providers/IChatProvider.cs`](../Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md) +- [`src/api/Gabriel.Engine/Providers/IModelCatalog.cs`](../Code/src/api/Gabriel.Engine/Providers/IModelCatalog.cs.md) +- [`src/api/Gabriel.Engine/Services/ContextMetrics.cs`](../Code/src/api/Gabriel.Engine/Services/ContextMetrics.cs.md) +- [`src/api/Gabriel.Engine/Services/MetricRecorder.cs`](../Code/src/api/Gabriel.Engine/Services/MetricRecorder.cs.md) +- [`src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs`](../Code/src/api/Gabriel.Engine/Tools/Codecs/Base64Tool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Docs/DocsListTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Docs/DocsReadTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Web/WebSearchTool.cs.md) +- [`src/webapp/src/components/GabrielSequenceView.tsx`](../Code/src/webapp/src/components/GabrielSequenceView.tsx.md) +- [`src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs`](../Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md) +- [`src/api/Gabriel.Core/Configuration/AgentOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/AgentOptions.cs.md) +- [`src/api/Gabriel.Core/Exceptions/DomainException.cs`](../Code/src/api/Gabriel.Core/Exceptions/DomainException.cs.md) +- [`src/api/Gabriel.Engine/Providers/AvailableModel.cs`](../Code/src/api/Gabriel.Engine/Providers/AvailableModel.cs.md) +- [`src/api/Gabriel.Engine/Providers/ToolDescriptor.cs`](../Code/src/api/Gabriel.Engine/Providers/ToolDescriptor.cs.md) +- [`src/api/Gabriel.Engine/Sequence/GabrielSequence.cs`](../Code/src/api/Gabriel.Engine/Sequence/GabrielSequence.cs.md) +- [`src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs`](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) +- [`src/api/Gabriel.Engine/Services/IMetricRecorder.cs`](../Code/src/api/Gabriel.Engine/Services/IMetricRecorder.cs.md) +- [`src/api/Gabriel.Engine/Services/ITokenEstimator.cs`](../Code/src/api/Gabriel.Engine/Services/ITokenEstimator.cs.md) +- [`src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Calc/CalculateTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs`](../Code/src/api/Gabriel.Engine/Tools/GetCurrentTimeTool.cs.md) +- [`src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs`](../Code/src/api/Gabriel.API/Contracts/Memories/MemoryDto.cs.md) +- [`src/api/Gabriel.API/Controllers/ModelsController.cs`](../Code/src/api/Gabriel.API/Controllers/ModelsController.cs.md) +- [`src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs`](../Code/src/api/Gabriel.Core/Identity/IRefreshTokenStore.cs.md) +- [`src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs`](../Code/src/api/Gabriel.Engine/Sequence/PaletteTemplates.cs.md) +- [`src/api/Gabriel.API/Configuration/LogDateEnricher.cs`](../Code/src/api/Gabriel.API/Configuration/LogDateEnricher.cs.md) +- [`src/api/Gabriel.Core/Configuration/IConfigSection.cs`](../Code/src/api/Gabriel.Core/Configuration/IConfigSection.cs.md) +- [`src/api/Gabriel.Core/Configuration/ModelSelection.cs`](../Code/src/api/Gabriel.Core/Configuration/ModelSelection.cs.md) +- [`src/api/Gabriel.Core/Exceptions/NotFoundException.cs`](../Code/src/api/Gabriel.Core/Exceptions/NotFoundException.cs.md) +- [`src/api/Gabriel.Core/Services/IProjectFileService.cs`](../Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md) +- [`src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs`](../Code/src/api/Gabriel.API/Contracts/Auth/RegisterRequest.cs.md) +- [`src/api/Gabriel.API/Controllers/MemoriesController.cs`](../Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md) +- [`src/api/Gabriel.API/Controllers/ProjectsController.cs`](../Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md) +- [`src/api/Gabriel.API/Controllers/SequenceController.cs`](../Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md) +- [`src/api/Gabriel.Core/Personality/ConversationState.cs`](../Code/src/api/Gabriel.Core/Personality/ConversationState.cs.md) +- [`src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs`](../Code/src/api/Gabriel.Engine/Providers/ChatProviderEvent.cs.md) +- [`src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs`](../Code/src/api/Gabriel.Engine/Tools/IToolExecutionContext.cs.md) +- [`src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Memory/MemoryListTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Memory/MemorySaveTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Strings/TextStatsTool.cs.md) +- [`src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs`](../Code/src/api/Gabriel.API/Identity/HttpContextCurrentUser.cs.md) +- [`src/api/Gabriel.Core/Configuration/InfisicalOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md) +- [`src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/LocalDocsOptions.cs.md) +- [`src/api/Gabriel.Core/Repositories/IMemoryRepository.cs`](../Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md) +- [`src/api/Gabriel.Core/Repositories/IMetricRepository.cs`](../Code/src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md) +- [`src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs`](../Code/src/api/Gabriel.Engine/Services/NaiveTokenEstimator.cs.md) +- [`src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/AgentToolsOptions.cs.md) +- [`src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/GitHubDocsOptions.cs.md) +- [`src/api/Gabriel.Core/Repositories/IProjectRepository.cs`](../Code/src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/PromptKey.cs.md) +- [`src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs`](../Code/src/api/Gabriel.Engine/Providers/ChatProviderMessage.cs.md) +- [`src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs`](../Code/src/api/Gabriel.Engine/Tools/Files/AgentPathResolver.cs.md) +- [`src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Memory/MemoryRemoveTool.cs.md) +- [`src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Numbers/BaseConvertTool.cs.md) +- [`src/api/Gabriel.API/Configuration/InfisicalExtensions.cs`](../Code/src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md) +- [`src/api/Gabriel.API/Controllers/DiagnosticsController.cs`](../Code/src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md) +- [`src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs`](../Code/src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md) +- [`src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/BraveSearchOptions.cs.md) +- [`src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/LLMProviderOptions.cs.md) +- [`src/api/Gabriel.Core/Configuration/PersonalityOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/PersonalityOptions.cs.md) +- [`src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs`](../Code/src/api/Gabriel.Engine/Providers/ChatProviderToolCall.cs.md) +- [`src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs`](../Code/src/api/Gabriel.Engine/Tools/Files/IAgentPathResolver.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md) +- [`src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs`](../Code/src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md) +- [`src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Messages/MessageResponse.cs.md) +- [`src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md) +- [`src/api/Gabriel.API/Controllers/ProjectFilesController.cs`](../Code/src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md) +- [`src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/ProjectFilesOptions.cs.md) +- [`src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs`](../Code/src/api/Gabriel.Core/Configuration/TavilySearchOptions.cs.md) +- [`src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs`](../Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md) +- [`src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs`](../Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs.md) +- [`src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Strings/TextTransformTool.cs.md) +- [`src/api/Gabriel.API/Controllers/ConversationsController.cs`](../Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md) +- [`src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs`](../Code/src/api/Gabriel.Engine/Personality/ISystemPromptBuilder.cs.md) +- [`src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs`](../Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceService.cs.md) +- [`src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs`](../Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md) +- [`src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs`](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md) +- [`src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/HttpUrlFetcher.cs.md) +- [`src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs`](../Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md) +- [`src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs`](../Code/src/api/Gabriel.Engine/Personality/ResponsePostProcessor.cs.md) +- [`src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs`](../Code/src/api/Gabriel.Engine/Sequence/GabrielSequenceGenerator.cs.md) +- [`src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs`](../Code/src/api/Gabriel.Infrastructure/Providers/GrokAuthHandler.cs.md) +- [`src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/TavilyWebSearch.cs.md) +- [`src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs`](../Code/src/api/Gabriel.API/Contracts/Messages/SendMessageRequest.cs.md) +- [`src/api/Gabriel.Core/Repositories/IConversationRepository.cs`](../Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) +- [`src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs`](../Code/src/api/Gabriel.Engine/Personality/IResponsePostProcessor.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/PromptRegistry.cs.md) +- [`src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs`](../Code/src/api/Gabriel.Engine/Sequence/IGabrielSequenceGenerator.cs.md) +- [`src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Projects/ReadProjectFileTool.cs.md) +- [`src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs`](../Code/src/api/Gabriel.Infrastructure/Providers/MockChatProvider.cs.md) +- [`src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Docs/LocalDocsLookup.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Modes.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/IPromptRegistry.cs.md) +- [`src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs`](../Code/src/api/Gabriel.Engine/Tools/Projects/ListProjectFilesTool.cs.md) +- [`src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Docs/GitHubDocsLookup.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Memory.cs.md) +- [`src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/CompositeWebSearch.cs.md) +- [`src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs`](../Code/src/api/Gabriel.Engine/Personality/IConversationStateUpdater.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.FewShot.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Persona.cs.md) +- [`src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs`](../Code/src/api/Gabriel.API/Configuration/GlobalRoutePrefixConvention.cs.md) +- [`src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs`](../Code/src/api/Gabriel.Engine/Personality/GabrielSystemPromptBuilder.cs.md) +- [`src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs`](../Code/src/api/Gabriel.Engine/Providers/ToolBridge/GabrielToolBridge.cs.md) +- [`src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Docs/CompositeDocsLookup.cs.md) +- [`src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs`](../Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md) +- [`src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Sequence/GabrielSequenceResponse.cs.md) +- [`src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Sequence/SequenceCatalogResponse.cs.md) +- [`src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs`](../Code/src/api/Gabriel.Infrastructure/Identity/UserPreferencesService.cs.md) +- [`src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs`](../Code/src/api/Gabriel.Infrastructure/Projects/DiskProjectFileService.cs.md) +- [`src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs`](../Code/src/api/Gabriel.Infrastructure/Tools/Web/InstrumentedWebSearch.cs.md) +- [`src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs`](../Code/src/api/Gabriel.Engine/Personality/Prompts/Fragments.Formatting.cs.md) +- [`src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs`](../Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallBlockParser.cs.md) +- [`src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs`](../Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md) +- [`src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Conversations/ConversationResponse.cs.md) +- [`src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Conversations/ContextMetricsResponse.cs.md) +- [`src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs`](../Code/src/api/Gabriel.Engine/Providers/ToolBridge/ToolCallStreamSplitter.cs.md) +- [`src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs`](../Code/src/api/Gabriel.Engine/Personality/HeuristicConversationStateUpdater.cs.md) +- [`src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs`](../Code/src/api/Gabriel.API/Contracts/Conversations/CreateConversationRequest.cs.md) +- [`src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs`](../Code/src/api/Gabriel.API/Contracts/Conversations/UpdateConversationRequest.cs.md) +- [`src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs`](../Code/src/api/Gabriel.API/Contracts/Conversations/SetConversationModeRequest.cs.md) +- [`src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs`](../Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md) +- [`src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs`](../Code/src/api/Gabriel.API/Middleware/RequireNonNullablePropertiesSchemaFilter.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MetricRepository.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ProjectRepository.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/RefreshTokenStore.cs.md) +- [`src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs`](../Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MessageConfiguration.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectConfiguration.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MemoryEntryConfiguration.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/MetricEntryConfiguration.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ProjectFileConfiguration.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md) +- [`src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs`](../Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/RefreshTokenConfiguration.cs.md) + +
+ +--- + +*Generated by Aurion on 2026-06-27 23:12:28 UTC* +*Commit range: `03c8997` → `ddfc907`* diff --git a/docs/auriondocs/Synthesis/Onboarding.md b/docs/auriondocs/Synthesis/Onboarding.md new file mode 100644 index 0000000..fd51fed --- /dev/null +++ b/docs/auriondocs/Synthesis/Onboarding.md @@ -0,0 +1,29 @@ +# Onboarding — HueByte/Gabriel + +> *A curated reading path through this codebase for new contributors. Work through the stops in order.* + +This reading path gets a new contributor from zero to being able to make small, safe changes in Gabriel. Read it in order: the early stops show the system shape and where execution begins, the middle stops trace a request and the business logic it touches, and the later stops show the data contracts and the conventional places to add new code. + +## What this project is +Read the auto-generated architecture overview at [Architecture.md](Architecture.md) to pick up the system’s collaboration pattern, the main components (API, Engine, Infrastructure, and webapp), and where state is owned. This document highlights the boundaries and the primary data flows so you know which subsystems to look into next. + +## Where execution starts +Open [Program.cs](../Code/src/api/Gabriel.API/Program.cs.md) to see how the ASP.NET Core host is bootstrapped: logging, configuration (including Infisical-sourced secrets), service registration, hosted services, and middleware wiring are centralized there. For the front end, inspect [App.tsx](../Code/src/webapp/src/App.tsx.md) and [main.tsx](../Code/src/webapp/src/main.tsx.md) to see how the React application is mounted, where the one-time authentication HTTP interceptor is installed, global styles are loaded, and App is rendered inside StrictMode. + +## Where requests come in +Trace inbound requests through the API surface beginning with the [AuthController.cs](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) controller, which exposes the authentication endpoints clients hit (login, refresh, me, etc.). Also read the [GlobalExceptionHandler.cs](../Code/src/api/Gabriel.API/Middleware/GlobalExceptionHandler.cs.md) middleware to understand how uncaught exceptions are translated into API responses and how errors are centralized for logging and client-friendly messages. + +## Where the business logic lives +Look at the core and engine abstractions and implementations: the [IJwtTokenService](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) interface defines token operations and is implemented by [JwtTokenService](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md) in Infrastructure; chat behavior is exposed by the [IChatProvider](../Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md) contract and agent orchestration is implemented by [AgentService.cs](../Code/src/api/Gabriel.Engine/Services/AgentService.cs.md) (the concrete class implementing IAgentService). On the frontend, inspect [AuthContext.tsx](../Code/src/webapp/src/auth/AuthContext.tsx.md) to see the in-browser AuthState and how UI code consumes authentication state. + +## Where state lives +Examine the API contracts that cross the HTTP boundary to understand persisted and transmitted shapes: [JwtResponse.cs](../Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md) defines the token response, [LoginRequest.cs](../Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md) the login payload, [MeResponse.cs](../Code/src/api/Gabriel.API/Contracts/Auth/MeResponse.cs.md) the authenticated user shape, and [RefreshTokenRequest.cs](../Code/src/api/Gabriel.API/Contracts/Auth/RefreshTokenRequest.cs.md) the refresh-token payload. These records show what the API stores or issues and which boundary (API contract layer) owns those shapes. + +## Where to put new code +Follow the project’s conventions when adding features: API endpoints belong in controllers such as [AuthController.cs](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md), domain services live in Core (for example [ChatService.cs](../Code/src/api/Gabriel.Core/Services/ChatService.cs.md) implements chat use cases), repository interfaces like [IConversationRepository](../Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) declare persistence contracts, and Engine tools such as [FileInfoTool.cs](../Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md) implement tool interfaces for agent workflows. Use these files as canonical examples for placement and naming when you add controllers, services, repositories, or engine tools. + +## Next steps +Start by reading [Architecture.md](Architecture.md) to confirm the high-level boundaries, then open [Program.cs](../Code/src/api/Gabriel.API/Program.cs.md) and the frontend [main.tsx](../Code/src/webapp/src/main.tsx.md) to run and observe the dev servers so you can hit the [AuthController.cs](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) endpoints and follow a request through the stack. + +--- +*Synthesised by Aurion on 2026-07-08 05:46:46 UTC* diff --git a/docs/auriondocs/Synthesis/Workflows/agent-tool.md b/docs/auriondocs/Synthesis/Workflows/agent-tool.md new file mode 100644 index 0000000..9064d4c --- /dev/null +++ b/docs/auriondocs/Synthesis/Workflows/agent-tool.md @@ -0,0 +1,29 @@ +# Adding a new agent tool + +> *Workflow template auto-derived from 4 existing exemplar(s).* + +Adding a new agent tool + +When you need to add a new capability the engine can invoke (an agent tool), follow the existing file-tool examples so the new tool is discoverable and registered alongside other tools. A new tool is typically a small, self-contained type that implements the same interface as the existing tools; model its surface and placement on an existing implementation and then add it to the composition/registration site used by the engine. + +## Reference implementation + +Model a new file-related tool on the [FindTool](../../Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md) implementation. The FindTool source in src/api/Gabriel.Engine/Tools/Files/FindTool.cs is the concrete example this repository uses for a file-oriented agent tool; inspect that file to mirror structure, inputs, outputs, and how it implements the tool contract. + +## Where it lives + +Place the new type in the src/api/Gabriel.Engine/Tools/Files folder. The existing examples in that folder are named with the Tool suffix (for example FileInfoTool, FindTool, GrepTool, ListDirTool) and are public sealed classes that implement ITool; use those files as naming and placement examples when creating your new tool type. + +## Wiring + +The detected registration/composition site for tools is src/api/Gabriel.Engine/DependencyInjection.cs. Add your new tool to the same registration area used by the existing file tools in that file so the engine composes the new instance at startup; inspect src/api/Gabriel.Engine/DependencyInjection.cs to see how the repository wires the current tools and follow that pattern when registering your type. + +## Existing examples + +- [`FileInfoTool`](../../Code/src/api/Gabriel.Engine/Tools/Files/FileInfoTool.cs.md) +- [`FindTool`](../../Code/src/api/Gabriel.Engine/Tools/Files/FindTool.cs.md) +- [`GrepTool`](../../Code/src/api/Gabriel.Engine/Tools/Files/GrepTool.cs.md) +- [`ListDirTool`](../../Code/src/api/Gabriel.Engine/Tools/Files/ListDirTool.cs.md) + +--- +*Synthesised by Aurion on 2026-07-08 05:48:02 UTC* diff --git a/docs/auriondocs/Synthesis/Workflows/controller.md b/docs/auriondocs/Synthesis/Workflows/controller.md new file mode 100644 index 0000000..32901c3 --- /dev/null +++ b/docs/auriondocs/Synthesis/Workflows/controller.md @@ -0,0 +1,51 @@ +# Adding a new controller + +> *Workflow template auto-derived from 8 existing exemplar(s).* + +Adding a new controller is the pattern to use when you need to expose a new set of HTTP endpoints in the Gabriel API surface. New controllers in this area are placed alongside existing controllers under src/api/Gabriel.API/Controllers and follow the established file and type naming seen in the examples; model your controller on the Reference implementation below. + +## Reference implementation + +The following is real code from src/api/Gabriel.API/Controllers/SequenceController.cs that a new controller instance can be modeled on: + +```csharp +// Gabriel-Sequence-scoped endpoints that aren't anchored to a specific +// conversation or project. Today: the skin-picker catalog. The per-conversation +// / per-project sequence endpoints stay on their respective controllers +// (ConversationsController, ProjectsController) so they sit alongside the rest +// of those entities' surface. +[ApiController] +[Authorize] +[Route("sequence")] +public class SequenceController : ControllerBase +{ + // Returns the catalog of pattern + palette identifiers a client can pin + // on a project / conversation as a "skin" override. The lists are static + // and cheap - clients can fetch once per session. + [HttpGet("catalog")] + public ActionResult GetCatalog() + => Ok(new SequenceCatalogResponse(SequenceCatalog.Patterns, SequenceCatalog.Palettes)); +} +``` + +## Where it lives + +Place new controllers in the src/api/Gabriel.API/Controllers folder. Name the file with a trailing Controller suffix (for example, Controller.cs) and declare a corresponding public class named Controller. The provided exemplars (for example AuthController, ConversationsController, ProjectsController, etc.) follow this same placement and naming. + +## Wiring + +A registration or composition site for controllers was not detected in the provided wiring sites. To understand how controllers are organized and to copy consistent patterns, inspect the existing controllers in src/api/Gabriel.API/Controllers listed below. + +## Existing examples + +- [`AuthController`](../../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) +- [`ConversationsController`](../../Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md) +- [`DiagnosticsController`](../../Code/src/api/Gabriel.API/Controllers/DiagnosticsController.cs.md) +- [`MemoriesController`](../../Code/src/api/Gabriel.API/Controllers/MemoriesController.cs.md) +- [`ModelsController`](../../Code/src/api/Gabriel.API/Controllers/ModelsController.cs.md) +- [`ProjectFilesController`](../../Code/src/api/Gabriel.API/Controllers/ProjectFilesController.cs.md) +- [`ProjectsController`](../../Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md) +- [`SequenceController`](../../Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md) + +--- +*Synthesised by Aurion on 2026-07-08 05:47:04 UTC* diff --git a/docs/auriondocs/Synthesis/Workflows/repository.md b/docs/auriondocs/Synthesis/Workflows/repository.md new file mode 100644 index 0000000..48c232f --- /dev/null +++ b/docs/auriondocs/Synthesis/Workflows/repository.md @@ -0,0 +1,51 @@ +# Adding a new repository + +> *Workflow template auto-derived from 4 existing exemplar(s).* + +Adding a new repository in this codebase means adding a repository contract that expresses the data-access surface for a domain concept and then providing an implementation and registering it where implementations are composed. Reach for this pattern when you need a typed place to encapsulate CRUD and query operations for a domain entity so services can depend on an abstraction rather than concrete data-access code. + +## Reference implementation + +Real code from `src/api/Gabriel.Core/Repositories/IProjectRepository.cs` that a new instance can be modelled on: + +```csharp +public interface IProjectRepository +{ + Task GetByIdAsync(Guid id, Guid ownerUserId, CancellationToken ct = default); + Task GetByIdWithFilesAsync(Guid id, Guid ownerUserId, CancellationToken ct = default); + Task> ListAsync(Guid ownerUserId, CancellationToken ct = default); + Task GetFirstByNameAsync(Guid ownerUserId, string name, CancellationToken ct = default); + + Task AddAsync(Project project, CancellationToken ct = default); + void Update(Project project); + void Remove(Project project); + + // Bulk-assign every project-less conversation of a user to the given project. + // Used by the Default-project lazy backfill on first project interaction. + Task AssignOrphanConversationsAsync(Guid ownerUserId, Guid projectId, CancellationToken ct = default); +} +``` + +## Where it lives + +Repository interfaces and their implementations are colocated under the repository area shown by the exemplars: the folder path is `src/api/Gabriel.Core/Repositories`. The existing interfaces use an `I{Name}Repository` naming style such as `IConversationRepository`, `IMemoryRepository`, `IMetricRepository`, and `IProjectRepository`, so add the new repository interface into that folder and follow the same interface name pattern. + +## Wiring + +Registration and composition points detected in this codebase where repository implementations are referenced or composed are: + +- `src/api/Gabriel.Infrastructure/DependencyInjection.cs` +- `src/api/Gabriel.Core/Services/ChatService.cs` +- `src/api/Gabriel.Engine/Sequence/GabrielSequenceService.cs` + +Use these wiring sites as the places to add or inspect registrations and where services consume repositories; the exemplars show how repository abstractions are referenced across the codebase. + +## Existing examples + +- [`IConversationRepository`](../../Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) +- [`IMemoryRepository`](../../Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md) +- [`IMetricRepository`](../../Code/src/api/Gabriel.Core/Repositories/IMetricRepository.cs.md) +- [`IProjectRepository`](../../Code/src/api/Gabriel.Core/Repositories/IProjectRepository.cs.md) + +--- +*Synthesised by Aurion on 2026-07-08 05:47:38 UTC* diff --git a/docs/auriondocs/Synthesis/Workflows/service.md b/docs/auriondocs/Synthesis/Workflows/service.md new file mode 100644 index 0000000..f4fdb32 --- /dev/null +++ b/docs/auriondocs/Synthesis/Workflows/service.md @@ -0,0 +1,62 @@ +# Adding a new service + +> *Workflow template auto-derived from 7 existing exemplar(s).* + +Adding a new service in this codebase means creating a pair of types — an interface and its implementation — and registering/injecting them where other services live. Reach for this pattern when you need a transactional, testable boundary for business logic (something like the existing ChatService or MemoryService). The exemplars and wiring sites below show the concrete files to mimic and the places to register and consume your new service. + +## Reference implementation + +```csharp +// Service layer over IMemoryRepository. Pulls UserId from ICurrentUser so +// controllers and tools don't pass it everywhere; enforces user-scoping at +// this boundary so a tool can't accidentally cross-read another user's +// memory by manipulating its arguments. +public interface IMemoryService +{ + // All memories the calling user has in the given scope. Pass projectId=null + // for user-scope only. + Task> ListAsync(Guid? projectId, CancellationToken ct = default); + + // What the agent should see for a given conversation: user-scope memories + // plus (if applicable) the conversation's project-scope memories. Returned + // sorted in display order (Type, then Name). + Task> ListForConversationAsync(Guid? projectId, CancellationToken ct = default); + + Task GetByIdAsync(Guid id, CancellationToken ct = default); + + // Upsert: creates a new entry if (UserId, ProjectId, Name) is free, or + // updates the existing one in place. Returns the saved entity either way. + // Idempotent — calling twice with the same spec is a no-op apart from + // bumping UpdatedAt. + Task SaveAsync(MemoryEntrySpec spec, CancellationToken ct = default); + + // Returns false if no entry matched (vs. true on actual delete) so the + // memory_remove tool can give the model a clear "wasn't there" response. + Task RemoveAsync(Guid id, CancellationToken ct = default); + Task RemoveByNameAsync(Guid? projectId, string name, CancellationToken ct = default); +} +``` + +## Where it lives + +Look in src/api/Gabriel.Core/Services for service interfaces and implementations. The exemplars place interface types such as IChatService and IMemoryService alongside concrete classes ChatService and MemoryService in that Services folder; follow the same placement and naming pattern when adding a new service (an interface named I{Name}Service and a class named {Name}Service that implements it). + +## Wiring + +The repository analysis detected these wiring sites; check them to see how services are registered and consumed and to add your new service where appropriate: + +- src/api/Gabriel.Core/DependencyInjection.cs +- src/api/Gabriel.API/Controllers/ConversationsController.cs + +## Existing examples + +- [`ChatService`](../../Code/src/api/Gabriel.Core/Services/ChatService.cs.md) +- [`IChatService`](../../Code/src/api/Gabriel.Core/Services/IChatService.cs.md) +- [`IMemoryService`](../../Code/src/api/Gabriel.Core/Services/IMemoryService.cs.md) +- [`IProjectFileService`](../../Code/src/api/Gabriel.Core/Services/IProjectFileService.cs.md) +- [`IProjectService`](../../Code/src/api/Gabriel.Core/Services/IProjectService.cs.md) +- [`MemoryService`](../../Code/src/api/Gabriel.Core/Services/MemoryService.cs.md) +- [`ProjectService`](../../Code/src/api/Gabriel.Core/Services/ProjectService.cs.md) + +--- +*Synthesised by Aurion on 2026-07-08 05:47:19 UTC* diff --git a/docs/auriondocs/Synthesis/authentication-tokens.md b/docs/auriondocs/Synthesis/authentication-tokens.md new file mode 100644 index 0000000..90f4775 --- /dev/null +++ b/docs/auriondocs/Synthesis/authentication-tokens.md @@ -0,0 +1,37 @@ +# Authentication, tokens, and user sessions + +> How the system authenticates users, issues and rotates tokens, and manages session cookies across API layers. This topic covers the surface API, token providers, and identity persistence. + +How the system performs authentication, issues and rotates tokens, and persists session identity across API layers. It describes the HTTP surface for login/refresh/logout, the cookie helper used to read and write tokens in responses, the JWT service that mints and rotates tokens, the interface abstraction used by the controller, and the persisted application user record that stores identity and refresh state. + +## AuthController.cs +Exposes login/registration HTTP endpoints and token refresh surface. + +This controller is the HTTP entry point for authentication actions (register, login, refresh, logout, revoke, revoke-all, and me). It orchestrates requests from clients and delegates token-related work to the token service abstraction ([IJwtTokenService.cs](Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md)), while using the cookie helper ([AuthCookies.cs](Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md)) to set or clear access and refresh tokens in HTTP responses. Think of this file as the routing and policy layer that translates HTTP verbs and JSON payloads into calls into the token lifecycle and identity persistence components. + +## AuthCookies.cs +Manages issuing and reading authentication cookies used by the API. + +This helper encapsulates cookie semantics for the API (setting, clearing, and reading access and refresh tokens) so controllers like [AuthController.cs](Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) don't need to reimplement cookie handling. When the token service issues or rotates tokens ([JwtTokenService.cs](Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md)), the controller uses this helper to persist those tokens to the client as cookies or to remove them on logout/revoke. + +## JwtTokenService.cs +Implements token minting and rotation logic for access/refresh tokens. + +This implementation encapsulates the core JWT lifecycle: minting short-lived access tokens, creating and persisting server-backed refresh tokens, rotating refresh tokens on use, and detecting reuse/theft. It is the concrete behind the [IJwtTokenService.cs](Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) abstraction used by the API surface. The service also interacts with persisted user data to store refresh state (see [ApplicationUser.cs](Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md)) so refresh tokens can be revoked or rotated safely. + +## IJwtTokenService.cs +Defines the interface for token generation and validation used by the auth surface. + +This interface expresses the operations the HTTP surface needs (issue access tokens, create/validate/rotate refresh tokens, revoke, and detect token reuse). [AuthController.cs](Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) depends on this abstraction so the controller remains implementation-agnostic; the concrete behavior is provided by [JwtTokenService.cs](Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md). + +## ApplicationUser.cs +Represents the application user persisted by ASP.NET Identity. + +This type models the persisted user record (Guid primary key) and carries any per-user state the token service needs to track (for example, refresh token metadata and user preferences). The token implementation ([JwtTokenService.cs](Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md)) reads and updates this record to persist refresh tokens, detect reuse, and perform revocation so that tokens remain rotatable and revocable across sessions. + +How the pieces fit + +The runtime flow is: the HTTP client calls endpoints on [AuthController.cs](Code/src/api/Gabriel.API/Controllers/AuthController.cs.md), which relies on [IJwtTokenService.cs](Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) (implemented by [JwtTokenService.cs](Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md)) to mint or rotate tokens; the controller then uses [AuthCookies.cs](Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md) to write the access and refresh tokens into cookies for the client. The token service persists refresh-state to the user record type defined in [ApplicationUser.cs](Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md), enabling server-side revocation and detection of token reuse. + +--- +*Synthesised by Aurion on 2026-06-08 22:34:22 UTC* diff --git a/docs/auriondocs/Synthesis/authentication.md b/docs/auriondocs/Synthesis/authentication.md new file mode 100644 index 0000000..f656f2f --- /dev/null +++ b/docs/auriondocs/Synthesis/authentication.md @@ -0,0 +1,81 @@ +# Authentication and Authorization + +> How the API authenticates users, issues tokens, and manages identity across the API and infrastructure. + +*Figure: How Authentication and Authorization works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +sequenceDiagram +IdentityServiceCollectionExtensions.cs->>JwtTokenService: Register JwtTokenService as IJwtTokenService +IdentityServiceCollectionExtensions.cs->>AuthOptions: Bind AuthOptions +IdentityServiceCollectionExtensions.cs->>GabrielIdentityExtensions: Configure identity extensions +Client->>AuthController: POST /auth/login (credentials) +AuthController->>ApplicationUser: Find user & validate credentials +ApplicationUser-->>AuthController: Return ApplicationUser +AuthController->>IJwtTokenService: Request token pair for user +IJwtTokenService->>JwtTokenService: Generate access token + refresh +JwtTokenService->>RefreshToken: Create RefreshToken +RefreshToken-->>JwtTokenService: RefreshToken instance +JwtTokenService-->>IJwtTokenService: Return tokens +IJwtTokenService-->>AuthController: Tokens (access + refresh) +AuthController->>AuthOptions: Read token/cookie settings +AuthOptions-->>AuthController: AuthOptions values +AuthController->>AuthCookies: Set auth cookies (access + refresh) +AuthCookies->>IJwtTokenService: Use IJwtTokenService to sign/validate +AuthCookies->>GabrielIdentityExtensions: Apply claims / identity hooks +GabrielIdentityExtensions-->>AuthCookies: Identity augmented +AuthCookies-->>AuthController: Cookies set +AuthController-->>Client: 200 OK + tokens / cookies +``` + +> This guide explains the authentication and authorization surface: how the app wires Identity at startup, issues and rotates JWT access/refresh pairs, and exposes a single controller surface that serves both browser clients (via HttpOnly cookies) and external clients (via JSON token bodies). Read this to understand which types own cookie policy, refresh-token state, and token issuance rules so you can safely modify or extend auth behavior without breaking the rotation/revocation invariants. + +## AuthController.cs +Exposes authentication endpoints for login and token refresh. + +The [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) is the canonical HTTP surface for registration, login, refresh, logout, token revocation, and retrieving the current user. It delegates credential checks and user persistence to ASP.NET Identity (UserManager/SignInManager) and uses the [IJwtTokenService](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) implementation to mint and rotate token pairs (access + refresh). To support browser flows it calls the [AuthCookies](../Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md) helpers to set, clear, and read HttpOnly cookies while also returning the [TokenPair](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) in the response body for external clients. The controller consults [AuthOptions](../Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md) (via IOptionsMonitor) to gate registration at runtime and is registered into the pipeline by [GabrielIdentityExtensions](../Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md). + +## AuthCookies.cs +Manages authentication cookies for web sessions. + +The internal [AuthCookies](../Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md) helper centralizes cookie lifecycle: it exposes Set to write both access and refresh cookies, Clear to remove them, and ReadRefresh to read the refresh token from inbound requests. It enforces the cookie policy (HttpOnly, Secure, SameSite, explicit paths) and scopes the refresh cookie to /api/auth so browser-presented cookies map correctly to the identity pipeline. In practice the [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) calls these helpers after receiving a token pair from the [IJwtTokenService](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) implementation; the cookie names and paths align with wiring in [GabrielIdentityExtensions](../Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md). + +## JwtTokenService.cs +Implements issuing and validating JWT tokens. + +The [JwtTokenService](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md) is the concrete implementation of [IJwtTokenService](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) that issues initial token pairs (IssueAsync), rotates/validates refresh tokens (RefreshAsync), and revokes refresh tokens or entire families when compromise is detected. It centralizes signing, hashing/persisting refresh tokens, and detection of suspicious reuse: the service delegates durable storage to an IRefreshTokenStore, looks up user details with `UserManager`, and uses an IUnitOfWork to commit changes so rotation and revocation are atomic. Callers (for example the [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) or refresh endpoints) should handle UnauthorizedAccessException from RefreshAsync and persist the issued refresh plaintext into the cookie via [AuthCookies](../Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md). + +## ApplicationUser.cs +Represents the user entity persisted by the identity system. + +[ApplicationUser](../Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md) derives from `IdentityUser` and stores per-user preferences used by higher-level services: `PreferredProvider` and `PreferredModel` (nullable strings) which record the user's chosen chat provider and model identifier. The type lives in Infrastructure so persistence details stay out of Core; callers such as [JwtTokenService](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md) and [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) work with users through this shape and must handle nulls by falling back to configuration defaults. + +## IJwtTokenService.cs +Defines contract for token service operations (signing/refresh). + +[IJwtTokenService](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) is the abstraction for issuing and managing JWT-based token pairs. Its semantic responsibilities include producing an initial [TokenPair](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) for authenticated users, rotating refresh tokens with theft detection on refresh, and revoking single or all refresh tokens for a user (signing out a device or everywhere). The interface separates stateless access tokens from stateful refresh tokens so callers like [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) and [AuthCookies](../Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md) can remain agnostic of storage and rotation mechanics implemented in [JwtTokenService](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md). + +## RefreshToken.cs +`RefreshToken` collaborates directly with `AuthController` and other members of this topic (5 dependency links). + +The [RefreshToken](../Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md) class models the server-side refresh-token record and intentionally stores only a SHA‑256 hash of the token; the plaintext is emitted to the client only at issuance. Use the Create(...) factory to build a token with an expiry; the instance exposes behaviors to evaluate IsActive/IsExpired, to Revoke (idempotent), and to MarkReplacedBy (linking a replaced token and setting RevokedAt). Higher-level services such as [JwtTokenService](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md) perform rotation by creating a new [RefreshToken](../Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md), marking the previous one as replaced, and persisting those changes via the configured store. + +## IdentityServiceCollectionExtensions.cs +`GabrielIdentityExtensions` collaborates directly with `AuthController` and other members of this topic (5 dependency links). + +[GabrielIdentityExtensions](../Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md) provides AddIdentityAndAuth, the single startup entry point that wires IdentityCore with the EF user store, binds JWT options, registers the JwtBearer authentication scheme, and registers token-related services (including the concrete [JwtTokenService](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md) and an IRefreshTokenStore). It validates critical config (for example the signing key length unless SKIP_DB_INIT=true) and sets the JwtBearer parameters used later to validate access tokens that [IJwtTokenService](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) issues. This bootstrapper also exposes the cookie names and paths used by [AuthCookies](../Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md) so cookie handling and authentication wiring stay consistent. + +## AuthOptions.cs +`AuthOptions` collaborates directly with `AuthController` and other members of this topic (2 dependency links). + +[AuthOptions](../Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md) is the configuration surface under the Auth section. It exposes `RegistrationEnabled` to toggle the public registration endpoint at runtime and `Seed` (a [SeedUserOptions](../Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md)) to opt into creating a bootstrapped identity user at startup. The [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) checks `RegistrationEnabled` via IOptionsMonitor so flipping the flag takes effect immediately; the seeding behavior is intended to be used during application initialization and is idempotent (skipped if the user already exists). + +How the pieces fit + +At startup, [GabrielIdentityExtensions](../Code/src/api/Gabriel.Infrastructure/Identity/IdentityServiceCollectionExtensions.cs.md) wires Identity, binds JWT options, and registers the [IJwtTokenService](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md) implementation ([JwtTokenService](../Code/src/api/Gabriel.Infrastructure/Identity/JwtTokenService.cs.md)). Runtime auth flows go through the [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md), which uses Identity to validate credentials, asks the token service for a [TokenPair](../Code/src/api/Gabriel.Core/Identity/IJwtTokenService.cs.md), and then writes cookies via [AuthCookies](../Code/src/api/Gabriel.API/Identity/AuthCookies.cs.md) while also returning the token pair in the response body. Persistent refresh state lives as hashed [RefreshToken](../Code/src/api/Gabriel.Core/Identity/RefreshToken.cs.md) records and user preferences are stored on [ApplicationUser](../Code/src/api/Gabriel.Infrastructure/Identity/ApplicationUser.cs.md); [AuthOptions](../Code/src/api/Gabriel.Core/Configuration/AuthOptions.cs.md) controls registration and startup seeding so operator decisions flow into the runtime endpoints without code changes. + +--- +*Covers 8 of 8 source files identified for this topic.* + +*Synthesised by Aurion on 2026-07-08 05:44:03 UTC* diff --git a/docs/auriondocs/Synthesis/chat-providers.md b/docs/auriondocs/Synthesis/chat-providers.md new file mode 100644 index 0000000..8eeb528 --- /dev/null +++ b/docs/auriondocs/Synthesis/chat-providers.md @@ -0,0 +1,22 @@ +# Chat providers integration and registry + +> Abstractions and registry for chat providers used by the agent runtime; how providers are discovered, registered, and resolved. + +This guide explains the abstractions and registry used to integrate chat-capable model providers into the agent runtime. It highlights how providers present a streaming interface for incremental outputs and how a registry maps human-friendly names to concrete provider instances so callers can select and resolve them at runtime. Readers should come away understanding the two core pieces you need to implement or call when adding or using a chat provider. + +## IChatProvider.cs + +Abstracts chat-capable provider behavior for streaming model outputs. + +[IChatProvider.cs](Code/src/api/Gabriel.Engine/Providers/IChatProvider.cs.md) defines the core abstraction a provider must implement to be used as a chat service by the agent. The interface models a streaming conversation API: implementations produce a sequence of incremental events (text deltas, tool-call completions, finish signals) rather than returning a single final result, allowing callers to react as output is produced. When you implement or call a provider, this is the contract that shapes how you subscribe to outputs, handle partial text, detect tool-invocation completions, and finalize the exchange. + +## IChatProviderRegistry.cs + +Defines a registry to map provider names to concrete IChatProvider implementations. + +[IChatProviderRegistry.cs](Code/src/api/Gabriel.Engine/Providers/IChatProviderRegistry.cs.md) describes the lookup layer that ties provider names to their concrete IChatProvider instances and exposes the set of known provider names. Use this interface when code needs to select a chat provider dynamically by name (for example, based on configuration or an agent directive). The registry centralizes discovery and resolution so callers ask for a provider by name and receive an implementation conforming to the streaming contract defined by IChatProvider. + +Together these two pieces form a simple discovery-and-use pattern: concrete chat providers implement the streaming-focused IChatProvider interface, and an IChatProviderRegistry implementation registers those providers under names and exposes the known set. At runtime, code (for example, the agent runtime) resolves a named provider from the registry and then interacts with it via the streaming API so outputs can be consumed incrementally and reactively. + +--- +*Synthesised by Aurion on 2026-06-08 22:35:31 UTC* diff --git a/docs/auriondocs/Synthesis/data-persistence.md b/docs/auriondocs/Synthesis/data-persistence.md new file mode 100644 index 0000000..f9f82be --- /dev/null +++ b/docs/auriondocs/Synthesis/data-persistence.md @@ -0,0 +1,54 @@ +# Data persistence and repositories + +> Repositories, entities, and UoW coordinating persistence for conversations and memory. + +> This topic covers the repository and unit-of-work layer that persist conversation and memory domain objects. Readers will learn which concrete repositories exist, what domain types they operate on, how query semantics (user- and project-scoped) are expressed, and where commits actually occur. The patterns here keep EF Core details inside thin adapters ([AppDbContext](../Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md)) while services depend on small interfaces for testability. + +## ConversationRepository.cs +Implements data access for conversations. + +The [ConversationRepository](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md) is a thin EF Core adapter that implements the [IConversationRepository](../Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) contract. It centralizes the common query shapes and mutation helpers for the [Conversation](../Code/src/api/Gabriel.Core/Entities/Conversation.cs.md) aggregate and its Message collection: callers use it for user-scoped reads (by userId), to include messages (the repository offers a GetByIdWithMessagesAsync that applies an Include with an OrderBy for messages), and to add/update/remove conversations and messages. The class mutates the injected [AppDbContext](../Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md) but intentionally does not call SaveChanges/SaveChangesAsync; callers (for example higher-level services or a unit-of-work) are responsible for persisting the changes. The repository depends on the domain Conversation type and its interface [IConversationRepository] for shape and usage expectations. + +## MemoryRepository.cs +Implements in-memory persistence for memory entries. + +[MemoryRepository](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md) is a lightweight EF Core-backed repository implementing [IMemoryRepository](../Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md). It exposes common query patterns for MemoryEntry entities scoped to a user and optionally to a project: ListAsync for explicit project or global lists, FindByNameAsync which matches Name and ProjectId exactly within a UserId, and ListForAgentAsync which encodes the agent-facing view by returning the user's global entries plus a project’s entries (when a projectId is provided) in a single query with ordering that places global entries first then project-scoped entries. Like the conversation repository, its AddAsync/Update/Remove methods only change the DbContext state and do not commit; callers must persist with an ambient unit-of-work or direct AppDbContext save. + +## UnitOfWork.cs +Coordinates transactional changes across repositories. + +[UnitOfWork](../Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md) is the concrete implementation of [IUnitOfWork](../Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md) in this codebase. It encapsulates an [AppDbContext](../Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md) and exposes a single asynchronous SaveChangesAsync operation that forwards the provided CancellationToken to the context’s SaveChangesAsync, returning the number of state entries written. It is intended to be the coordination point so multiple repositories that share the same context can persist their accumulated changes together; it does not create its own cross-context transaction scope, so transactional semantics are those provided by the underlying context/provider. + +## Conversation.cs +Represents a conversation domain entity. + +[Conversation](../Code/src/api/Gabriel.Core/Entities/Conversation.cs.md) is the aggregate root for a chat thread owned by a user. It holds metadata (Title, timestamps, AvatarSeed, optional PatternOverride/PaletteOverride), a collection of Message entities (exposed as an IReadOnlyList), optional ProjectId, a rolling summary with summarized-through pointer, and a serialized StateJson for evolving state that is not modeled as discrete columns. The class is designed for EF Core materialization (private parameterless constructor) and exposes a factory method Create that enforces non-empty userId and projectId for new conversations and initializes stable defaults such as Id and AvatarSeed; repository code is expected to filter by Conversation.UserId to enforce per-user isolation. + +## IConversationRepository.cs +Defines repository interface for conversations. + +[IConversationRepository](../Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) defines the tenant-aware contract for working with Conversation aggregates and their Message entities. The interface requires reads to include a userId to guarantee user-scoped access, exposes methods to fetch a conversation (with or without its messages), list a user’s conversations (optionally filtered by project), and perform explicit create/update/delete operations on conversations and messages. The design intentionally separates AddMessage and RemoveMessages to make message lifecycle explicit and to ensure EF Core’s change tracker records deletions reliably rather than relying on implicit orphan removal. + +## IMemoryRepository.cs +`IMemoryRepository` is the interface/implementation counterpart of `MemoryRepository`, which this topic covers. + +[IMemoryRepository](../Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md) is the abstraction for storing and querying MemoryEntry objects with user- and project-scoped visibility. Its surface includes listing the entries visible to an agent (ListForAgentAsync), listing or filtering by project scope, finding a memory by name within a specific (userId, projectId) scope (FindByNameAsync), and creating/updating/removing entries. Implementations are expected to rely on an ambient unit-of-work or change-tracking context for persisting Update/Remove operations; AddAsync is asynchronous while Update/Remove are synchronous in the interface. + +## IUnitOfWork.cs +`IUnitOfWork` is the interface/implementation counterpart of `UnitOfWork`, which this topic covers. + +[IUnitOfWork](../Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md) defines the contract that coordinates persistence across repositories by exposing SaveChangesAsync(CancellationToken) which returns the number of state entries written. Services perform repository updates and then invoke this single commit point to persist the combined changes. The abstraction hides EF Core specifics from callers and improves testability by enabling mock or fake implementations in unit tests. + +## AppDbContext.cs +`AppDbContext` collaborates directly with `Conversation` and other members of this topic (4 dependency links). + +[AppDbContext](../Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md) is the EF Core DbContext used across the persistence layer. It inherits from IdentityDbContext, Guid> so Identity tables are wired into the model and it exposes DbSet properties for Conversations, Messages, MemoryEntries, RefreshTokens, Projects, ProjectFiles, and MetricEntries. In OnModelCreating it applies identity configuration via the base and then loads entity configurations from the assembly with ApplyConfigurationsFromAssembly; ConfigureConventions applies a DateTimeOffsetToBinaryConverter to persist DateTimeOffset values safely on providers (like SQLite) that lack native support. Repositories and the UnitOfWork operate against this context; it is intended to be registered in DI with an appropriate scope so repositories share the same context instance for a coordinated commit. + +How the pieces fit + +Repositories ([ConversationRepository](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md) and [MemoryRepository](../Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/MemoryRepository.cs.md)) are thin adapters over [AppDbContext](../Code/src/api/Gabriel.Infrastructure/Persistence/AppDbContext.cs.md) that encapsulate common query shapes and ownership filtering (userId and optional projectId). They mutate the shared DbContext state but purposely do not persist changes; the single commit point is [UnitOfWork](../Code/src/api/Gabriel.Infrastructure/Persistence/UnitOfWork.cs.md) which implements [IUnitOfWork](../Code/src/api/Gabriel.Core/Repositories/IUnitOfWork.cs.md) and forwards SaveChangesAsync to AppDbContext. The [IConversationRepository](../Code/src/api/Gabriel.Core/Repositories/IConversationRepository.cs.md) and [IMemoryRepository](../Code/src/api/Gabriel.Core/Repositories/IMemoryRepository.cs.md) interfaces decouple services from EF Core and encourage reuse and testability while the [Conversation](../Code/src/api/Gabriel.Core/Entities/Conversation.cs.md) entity models the aggregate root and message ownership that the repositories enforce. + +--- +*Covers 8 of 8 source files identified for this topic.* + +*Synthesised by Aurion on 2026-07-08 05:45:56 UTC* diff --git a/docs/auriondocs/Synthesis/data-repositories.md b/docs/auriondocs/Synthesis/data-repositories.md new file mode 100644 index 0000000..6d75b12 --- /dev/null +++ b/docs/auriondocs/Synthesis/data-repositories.md @@ -0,0 +1,34 @@ +# Persistence layer and repositories + +> EF Core entity mappings and repository patterns for persisting conversations, memories, and related data; includes a unit of work for coordinated saves. + +This guide describes the persistence layer and repository patterns used to store conversations, memory entries, and related data with Entity Framework Core. It highlights the EF Core entity mapping for conversations, the concrete repository implementations for conversations and memory entries, and the Unit of Work that provides a single commit point for coordinated saves. Use this guide to understand where data access logic lives and how repositories and configuration classes interact with an AppDbContext to persist domain aggregates. + +## ConversationRepository.cs + +Provides data access for Conversation aggregates using EF Core. + +ConversationRepository is the concrete EF Core repository that performs queries and updates against Conversation aggregates (including their messages) using the shared AppDbContext. When you need to load conversation graphs, apply filters, add or remove messages, or update conversation metadata, this class centralizes those operations and translates domain intent into EF Core queries. It is the primary entry point for higher-level services that operate on conversations and relies on the entity mapping defined in [ConversationConfiguration.cs](Code/src/api/Gabriel.Infrastructure/Persistence/Configurations/ConversationConfiguration.cs.md) and the scoped DbContext that the UnitOfWork coordinates. + +## MemoryRepository.cs + +Provides an EF Core-backed repository for MemoryEntry records. + +MemoryRepository offers a thin data-access abstraction for persisting and querying MemoryEntry records, typically scoped by user and optionally by project. It encapsulates common query patterns and persistence concerns for memory entries so service code does not use the DbContext directly. Use this repository when your application code needs to store, update, or enumerate memory entries; like the conversation repository it depends on the same AppDbContext and participates in coordinated commits handled by the UnitOfWork. + +## UnitOfWork.cs + +Coordinates persistence across repositories and defines a single commit point. + +UnitOfWork is a small adapter that implements the application IUnitOfWork abstraction by delegating SaveChangesAsync to an injected AppDbContext. It provides a single commit boundary for multiple repository operations so callers can perform several changes across repositories and then call one commit method to persist them atomically (as supported by EF Core and the underlying transaction behavior). Prefer depending on this abstraction in services and handlers to decouple business logic from the concrete DbContext and to make coordinated saves explicit. + +## ConversationConfiguration.cs + +Configures EF Core mappings for the Conversation entity. + +ConversationConfiguration declares the EF Core mapping rules for the Conversation entity: table name, primary key, property constraints (required and max-length settings), and indexes used by common queries, as well as mapping for optional fields like rolling summaries. This configuration ensures the database schema and EF Core model align with how the repositories query and update conversations; the settings here (indexes, nullability, lengths) directly influence query performance and correctness when [ConversationRepository.cs](Code/src/api/Gabriel.Infrastructure/Persistence/Repositories/ConversationRepository.cs.md) and other data-access classes materialize conversation data. + +These pieces collaborate in a straightforward request-flow: repositories (ConversationRepository and MemoryRepository) encapsulate EF Core queries and updates and depend on the AppDbContext mapping established by ConversationConfiguration, while UnitOfWork provides a single commit point that application code calls after performing repository operations. Together they enforce a clear separation between domain persistence logic (repositories), the EF Core model (configurations), and transaction/commit concerns (unit of work), enabling coordinated, testable data access across the application. + +--- +*Synthesised by Aurion on 2026-06-08 22:35:19 UTC* diff --git a/docs/auriondocs/Synthesis/dependency-injection.md b/docs/auriondocs/Synthesis/dependency-injection.md new file mode 100644 index 0000000..efdc007 --- /dev/null +++ b/docs/auriondocs/Synthesis/dependency-injection.md @@ -0,0 +1,33 @@ +# Dependency injection and service bootstrap + +> Centralized DI wiring across core, engine, and infrastructure layers to compose the app. + +# Dependency injection and service bootstrap + +This topic documents the centralized dependency-injection wiring that composes the application's core domain, engine runtime, and infrastructure providers so the app can resolve services predictably at runtime. The three DependencyInjection extensions divide responsibilities: core domain implementations and small-scope services live in the Core wiring, engine-level runtime behaviors and per-request contexts live in the Engine wiring, and external provider configuration and HTTP/web plumbing live in the Infrastructure wiring. Together they let startup compose a layered DI graph with clear lifetime boundaries and a single place to change or swap implementations. + +## DependencyInjection.cs +Provides DI wiring for core services. +The [DependencyInjection](../Code/src/api/Gabriel.Core/DependencyInjection.cs.md) class exposes an extension (AddCoreServices) that registers the core Gabriel.Core domain services into the application's IServiceCollection. Concretely, the extension wires concrete implementations behind domain interfaces — for example ChatService, ProjectService, and MemoryService are registered to resolve for IChatService, IProjectService, and IMemoryService respectively — and all are registered with a scoped lifetime. The method returns IServiceCollection to allow fluent composition during startup and intentionally keeps domain registrations separate from engine-level wiring so the domain layer can evolve independently and be reused in tests or different host compositions. + +This core wiring is consumed by application startup and is orthogonal to the engine’s runtime wiring: it does not register engine tools or providers itself but is meant to be composed alongside engine and infrastructure registrations (see the engine and infrastructure DependencyInjection files). + +## DependencyInjection.cs +Provides DI wiring for engine services. +The [DependencyInjection](../Code/src/api/Gabriel.Engine/DependencyInjection.cs.md) class configures the engine’s runtime DI graph by binding configuration sections (AgentOptions, PersonalityOptions, AgentToolsOptions) and registering engine-level collaborators so controllers and higher-level services can request them via constructor injection. It registers engine services such as IAgentService, tool-related types (ITool, IToolRegistry), per-request contexts like ToolExecutionContext, and cross-cutting singletons like MetricRecorder and PromptRegistry, with an emphasis on scoping most tools to Scoped so they align with per-turn processing. The extension also registers every built-in ITool implementation that the engine provides, but it deliberately defers concrete provider bindings (e.g., chat or search providers) to the infrastructure layer. + +Because it relies on provider implementations and other infrastructure bindings, the engine wiring expects [Gabriel.Infrastructure's DependencyInjection](../Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md) to be present in the host composition; it centralizes engine concerns while keeping provider wiring and HTTP client/resilience concerns out of the engine assembly. + +## DependencyInjection.cs +Provides DI wiring for infrastructure services. +The [DependencyInjection](../Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md) class supplies an AddInfrastructure extension that registers the application's infrastructure services and helpers. It wires disk-backed project file storage configured from Projects:Files (persisting under a {Root}/{ProjectId:N} path), configures a single HttpClient-based web page fetcher with sensible defaults and UA handling, and outlines a composable web-search pipeline that can combine multiple providers with instrumentation and fallback behavior. The file also contains helper registration methods referenced by the extension (AddChatProvider, AddDocsLookup, AddWebFetch, AddWebSearch) plus helper configurers like ConfigureDdgHttpClient and ConfigureGrokResilience. + +One specific provider wiring example is [AddChatProvider](../Code/src/api/Gabriel.Infrastructure/DependencyInjection.cs.md) which always registers a MockChatProvider as a safe default and conditionally wires Grok support when a Providers:Grok configuration section with active models is present; it binds Grok options via Options, reads secrets via the PROVIDERS__GROK__APIKEY environment key, and only registers Grok when configured, leaving MockChatProvider as fallback to avoid crashes in development. Overall, this file centralizes external provider and HTTP/resilience concerns so those implementations are available for the engine to consume. + +How the pieces fit +Startup composes these three layers in a predictable order: the infrastructure wiring (AddInfrastructure) attaches external providers, HTTP clients, and persistence details; the core wiring (AddCoreServices) registers domain implementations with scoped lifetimes; the engine wiring (AddEngineServices) binds engine runtime options, tool registrations, and per-request contexts and consumes provider bindings supplied by the infrastructure layer. Lifetimes are deliberate: most tools and domain services are Scoped to align with request/turn processing, while telemetry and registries may be singletons. This separation keeps domain, engine, and infrastructure concerns decoupled while allowing a single startup composition to produce a complete DI graph. + +--- +*Covers 3 of 3 source files identified for this topic.* + +*Synthesised by Aurion on 2026-07-08 05:44:36 UTC* diff --git a/docs/auriondocs/Synthesis/http-api-surface.md b/docs/auriondocs/Synthesis/http-api-surface.md new file mode 100644 index 0000000..0001195 --- /dev/null +++ b/docs/auriondocs/Synthesis/http-api-surface.md @@ -0,0 +1,91 @@ +# HTTP API surface and contracts + +> Controllers expose REST endpoints and the associated request/response contracts across modules. + +*Figure: How HTTP API surface and contracts works.* + +```mermaid +%%{init: {'theme':'base','themeVariables':{'background':'#faf7ef','primaryColor':'#f0e2c2','primaryTextColor':'#1f2840','primaryBorderColor':'#8a7548','secondaryColor':'#d9efec','secondaryBorderColor':'#1d8a80','secondaryTextColor':'#1f2840','tertiaryColor':'#f2ebd8','tertiaryBorderColor':'#8a7548','tertiaryTextColor':'#1f2840','lineColor':'#1d8a80','titleColor':'#1f2840','fontSize':'14px','edgeLabelBackground':'#faf7ef','clusterBkg':'#f2ebd8','clusterBorder':'#8a7548','actorBkg':'#f0e2c2','actorBorder':'#8a7548','actorTextColor':'#1f2840','actorLineColor':'#8a7548','signalColor':'#1d8a80','signalTextColor':'#1f2840','activationBkgColor':'#d9efec','activationBorderColor':'#1d8a80','noteBkgColor':'#f2ebd8','noteBorderColor':'#8a7548','noteTextColor':'#1f2840','labelBoxBkgColor':'#f0e2c2','labelBoxBorderColor':'#8a7548','labelTextColor':'#1f2840','transitionColor':'#1d8a80','transitionLabelColor':'#1f2840','stateLabelColor':'#1f2840','altBackground':'#f2ebd8'}}}%% +sequenceDiagram +Client->>AuthController: POST /auth/login (LoginRequest) +AuthController->>LoginRequest: bind & validate +AuthController->>JwtResponse: create token +AuthController-->>Client: 200 JwtResponse + +Client->>ProjectsController: POST /projects (ProjectResponse) +ProjectsController->>ProjectResponse: bind & validate +ProjectsController->>SequenceCatalog: lookup templates +ProjectsController->>ConversationsController: create initial conversation +ConversationsController-->>ProjectsController: conversation created +ProjectsController-->>Client: 201 ProjectResponse + +Client->>ConversationsController: GET /projects/{id}/conversations +ConversationsController->>SequenceCatalog: resolve sequences +ConversationsController->>ProjectResponse: map project data +ConversationsController-->>Client: 200 [ProjectResponse] + +Client->>SequenceController: GET /projects/{id}/sequences +SequenceController->>ConversationsController: fetch conversations +SequenceController->>ProjectsController: fetch project metadata +SequenceController->>SequenceCatalog: assemble sequences +SequenceController-->>Client: 200 [SequenceCatalog entries] + +Client->>DiagnosticsController: GET /diagnostics/websearch +DiagnosticsController->>MetricEntryDto: collect metric entries +DiagnosticsController->>WebSearchDiagnosticsResponse: format response +DiagnosticsController-->>Client: 200 WebSearchDiagnosticsResponse +``` + +This guide describes the HTTP API surface: the controllers that expose REST endpoints and the DTO contracts they send and receive. Each controller is a thin, authenticated HTTP façade that delegates business work to service-layer components and returns stable DTOs for clients. The contracts (login payload, JWT response, project and diagnostics DTOs, and a centralized sequence catalog) standardize communication so clients — browser or API consumers — get consistent behavior across endpoints. + +## AuthController.cs +Exposes authentication endpoints for login and token refresh. +AuthController implements the canonical authentication surface and centralizes JWT issuance, refresh rotation, cookie management, and integration with ASP.NET Identity. Concretely, the controller exposes endpoints such as register, login, refresh, logout, revoke, and current-user; it delegates persistence and credential checks to Identity's UserManager/SignInManager, issues token pairs via an IJwtTokenService, and manipulates HttpOnly cookies with AuthCookies helpers. Relationships: it accepts the [LoginRequest](../Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md) payload and returns the [JwtResponse](../Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md) token pair (and also sets cookies), and it reacts to runtime flags like AuthOptions.RegistrationEnabled to enable/disable registration without restart. + +## ConversationsController.cs +Exposes endpoints to create and manage conversations. +ConversationsController is an [Authorize]-guarded, route-rooted controller that forwards conversation actions — list, get, create, rename, reroll avatar, and skin pinning — to backend services such as IChatService, IAgentService and IProjectService rather than implementing domain logic itself. It keeps HTTP actions thin, uses a private LoadProjectAsync helper to include project metadata on single-conversation responses (for example whether the project is default or what avatar seed applies), and purposely omits messages from the List responses by calling ToResponse(includeMessages: false) to avoid N+1 loads. Relationships: it converts project data into the [ProjectResponse](../Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md) DTO and consults the [SequenceCatalog](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) when working with skin identifiers; the controller is also referenced/used by other controllers in this topic (ProjectsController.cs and SequenceController.cs) for related surface-level operations. + +## DiagnosticsController.cs +Exposes diagnostics endpoints for metrics and health. +DiagnosticsController provides a read-only, authenticated endpoint (GET /diagnostics/web-search) that summarizes recent web_search metric events rather than exposing raw rows. It filters the generic metric event log by the WebSearchSystemPrefix, aggregates per-provider counts (total, success, error), latencies, and records most-recent success/failure details while parsing each metric JSON lazily to minimize overhead. Relationships: the controller surfaces row-level data using [MetricEntryDto]/[MetricEntriesResponse](../Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md) and returns aggregated summaries in a [WebSearchDiagnosticsResponse](../Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md) for UI consumption. + +## ProjectsController.cs +Exposes endpoints to manage projects. +ProjectsController is an authenticated REST surface rooted at "projects" that delegates project CRUD and avatar operations to IProjectService and aggregates the project-level Gabriel sequence via IGabrielSequenceService. It converts domain Project objects into [ProjectResponse](../Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md) DTOs (via ToResponse calls), supports standard REST semantics (GET list/detail, POST create with CreatedAtAction, PATCH partial updates, DELETE), and exposes endpoints to fetch project sequences and reroll avatars. Relationships: the controller depends on the [ProjectResponse](../Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md) DTO and consults the [SequenceCatalog](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) for skin/sequence concerns; it is also surfaced to or used by [SequenceController](../Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md) in scenarios where project-level sequence data is relevant. + +## SequenceController.cs +Exposes endpoints to work with sequences. +SequenceController provides a single, global, authorized endpoint that returns the static catalog of valid skin identifiers (patterns and palettes) exposed by the static [SequenceCatalog](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md). The controller wraps the static lists in a lightweight SequenceCatalogResponse and intentionally keeps the endpoint global and read-only so UI clients can fetch skin options once per session. Relationships: it sources its response from [SequenceCatalog](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) and exists to avoid duplicating skin option lists across per-project or per-conversation controllers (ProjectsController and ConversationsController). + +## LoginRequest.cs +Represents login payload. +[LoginRequest](../Code/src/api/Gabriel.API/Contracts/Auth/LoginRequest.cs.md) is an immutable record that carries the Email and Password pair for authentication. It is the canonical transport shape for login operations; callers should treat Password as sensitive (do not log or persist it) and consider validation at the boundary before constructing this record. Relationships: this DTO is consumed by [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) for the login endpoint. + +## JwtResponse.cs +Represents token response after login. +[JwtResponse](../Code/src/api/Gabriel.API/Contracts/Auth/JwtResponse.cs.md) is the contract returned by authentication endpoints: it bundles the short-lived AccessToken and its expiry together with the opaque RefreshToken and its expiry. The controller policy is to rotate refresh tokens on every refresh; AuthController both sets HttpOnly cookies and returns this record in the response body so browser clients can rely on cookies while external clients can read the tokens from the body. Relationships: this record is produced by [AuthController](../Code/src/api/Gabriel.API/Controllers/AuthController.cs.md) and consumed by clients implementing the refresh flow. + +## ProjectResponse.cs +`ProjectResponse` collaborates directly with `ConversationsController` and other members of this topic (5 dependency links). +The ProjectResponse compilation unit defines several DTOs for project-related traffic (CreateProjectRequest, ProjectFileResponse, ProjectResponse, SetSkinRequest, UpdateProjectRequest) used when creating, updating, listing, or representing project resources over HTTP. These immutable records shape create/update payloads and the project result that controllers return; for example CreateProjectRequest captures Name, optional Description and SystemPrompt, while ProjectFileResponse models uploaded file metadata. Relationships: these DTOs are emitted by controllers such as [ProjectsController](../Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md) and [ConversationsController](../Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md), and the project response types reference the [SequenceCatalog](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) vocabulary when exposing or interpreting skin overrides. + +## SequenceCatalog.cs +`SequenceCatalog` collaborates directly with `ConversationsController` and other members of this topic (3 dependency links). +[SequenceCatalog](../Code/src/api/Gabriel.Engine/Sequence/SequenceCatalog.cs.md) is a static utility that centralizes the canonical, case-insensitive vocabulary of pattern and palette identifiers used by projects and standalone conversations. It exposes lists (Patterns, Palettes) and helpers such as NormalizePattern/NormalizePalette and TryParsePattern to validate, normalize, and map string inputs to internal enums; unknown identifiers are ignored and cause callers to fall back to seed-derived selections. Relationships: the catalog is referenced by [ProjectResponse](../Code/src/api/Gabriel.API/Contracts/Projects/ProjectResponse.cs.md), [ConversationsController](../Code/src/api/Gabriel.API/Controllers/ConversationsController.cs.md), [ProjectsController](../Code/src/api/Gabriel.API/Controllers/ProjectsController.cs.md), and [SequenceController](../Code/src/api/Gabriel.API/Controllers/SequenceController.cs.md) so that skin identifiers remain consistent across the API surface. + +## MetricEntryDto.cs +`MetricEntryDto` collaborates directly with `DiagnosticsController` and other members of this topic (2 dependency links). +This file defines the diagnostics transport records: [MetricEntryDto](../Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md) for individual metric rows (Id, System, Metric as a JsonElement, CreatedAt) and [MetricEntriesResponse] for paged responses. MetricEntryDto preserves the original JSON payload in a JsonElement so callers can lazily parse only the fields they need when aggregating or inspecting metrics, which reduces per-call overhead. Relationships: DiagnosticsController consumes these DTOs to present recent metric rows and to drive aggregation into higher-level summaries. + +## WebSearchDiagnosticsResponse.cs +`WebSearchDiagnosticsResponse` collaborates directly with `DiagnosticsController` and other members of this topic (2 dependency links). +[WebSearchDiagnosticsResponse](../Code/src/api/Gabriel.API/Contracts/Diagnostics/WebSearchDiagnosticsResponse.cs.md) is the aggregated diagnostics DTO returned by the web-search diagnostics endpoint: it contains a list of per-provider [WebSearchProviderStatsDto] entries, a HasUnhealthyProvider flag, and the WindowSize used for the lookback. Each provider DTO carries counts (total, success, error, empty), last success/failure timestamps and messages, and average latency so UIs can render both detailed trends and a top-level health indicator. Relationships: DiagnosticsController builds and returns this response by summarizing metric rows represented as [MetricEntryDto](../Code/src/api/Gabriel.API/Contracts/Diagnostics/MetricEntryDto.cs.md). + +How the pieces fit +The controllers are thin, authenticated HTTP facades: they accept well-defined DTOs (LoginRequest, CreateProjectRequest, UpdateProjectRequest, etc.), delegate domain work to injected services, and return stable response records (JwtResponse, ProjectResponse, WebSearchDiagnosticsResponse). SequenceCatalog centralizes skin/palette vocabulary so both project- and conversation-level controllers present consistent options, while diagnostics uses MetricEntryDto rows as raw inputs to produce aggregated WebSearchDiagnosticsResponse objects for UI consumption. AuthController is the single canonical source of token behavior (setting cookies and returning [JwtResponse] payloads) so clients get consistent login and refresh semantics regardless of whether they rely on cookies or token JSON. + +--- +*Covers 11 of 11 source files identified for this topic.* + +*Synthesised by Aurion on 2026-07-08 05:45:21 UTC* diff --git a/docs/auriondocs/Synthesis/infisical-configuration.md b/docs/auriondocs/Synthesis/infisical-configuration.md new file mode 100644 index 0000000..f2693d4 --- /dev/null +++ b/docs/auriondocs/Synthesis/infisical-configuration.md @@ -0,0 +1,34 @@ +# Configuration and secret management via Infisical + +> How the application reads and applies secrets/configuration from Infisical at startup and exposes it to the configuration system. This enables dynamic secrets handling in startup. + +Configuration and secret management via Infisical + +This guide explains how the application integrates a self-hosted Infisical secrets provider into ASP.NET Core's configuration system so secrets are fetched at startup and participate in normal IConfiguration/IOptions binding. The files below show the small surface: a POCO for options, an IConfigurationSource, a provider that pulls secrets, and an extension helper to wire everything into the IConfigurationBuilder. + +## InfisicalConfigurationProvider.cs +Pulls secrets from a self-hosted Infisical instance at application startup and injects them into IConfiguration. + +[InfisicalConfigurationProvider.cs](Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationProvider.cs.md) is the runtime component that actually contacts Infisical (using the settings captured in the options type) and adds the returned secret key/value pairs into the configuration key/value collection. Implemented as an IConfigurationProvider, it runs during configuration building so the fetched secrets become regular configuration entries that can be read through IConfiguration or bound into IOptions. The provider is the piece that translates remote secrets into the configuration system's in-memory representation. + +## InfisicalConfigurationSource.cs +Provides an IConfigurationSource implementation that captures InfisicalOptions and produces InfisicalConfigurationProvider instances. + +[InfisicalConfigurationSource.cs](Code/src/api/Gabriel.API/Configuration/InfisicalConfigurationSource.cs.md) encapsulates the InfisicalOptions and acts as the factory used by the configuration system. When the ConfigurationBuilder builds its providers, this source produces configured instances of the provider described above. It bridges the builder-time registration (where options are supplied) and the provider-time execution (where secrets are fetched), ensuring the provider receives the bound options needed to connect to Infisical. + +## InfisicalExtensions.cs +Adds Infisical as a configuration source to an IConfigurationBuilder using the options-pattern. + +[InfisicalExtensions.cs](Code/src/api/Gabriel.API/Configuration/InfisicalExtensions.cs.md) contains the extension method(s) used by application startup to register Infisical-backed configuration. Using the familiar options pattern, these extensions let callers supply or bind an InfisicalOptions instance and add the corresponding InfisicalConfigurationSource to the builder. This is the public integration point you call from Program.cs or a host builder to enable Infisical-based secrets in your app configuration. + +## InfisicalOptions.cs +Holds options used to connect to an Infisical secrets provider. + +[InfisicalOptions.cs](Code/src/api/Gabriel.Core/Configuration/InfisicalOptions.cs.md) is the POCO that carries the settings required to talk to the Infisical instance. It is intended to be bound from appsettings, environment variables, or user-secrets and then passed through the extension method into the configuration source so the provider can use those values at runtime. + +How the pieces fit together + +At startup you call the extension on IConfigurationBuilder to register Infisical with a configured InfisicalOptions instance; that extension adds an InfisicalConfigurationSource to the builder. When the configuration system builds, the source creates an InfisicalConfigurationProvider which uses the provided options to fetch secrets from the self-hosted Infisical instance and injects them into the IConfiguration key/value collection. The end result is that remote secrets participate in normal configuration access and IOptions binding without special-case code elsewhere in the application. + +--- +*Synthesised by Aurion on 2026-06-08 22:34:39 UTC* diff --git a/docs/auriondocs/Synthesis/web-search-and-fetch.md b/docs/auriondocs/Synthesis/web-search-and-fetch.md new file mode 100644 index 0000000..23e356e --- /dev/null +++ b/docs/auriondocs/Synthesis/web-search-and-fetch.md @@ -0,0 +1,34 @@ +# Web search and fetch tooling + +> Web search providers and fetch utilities used by the engine tooling. + +# Web search and fetch tooling + +This topic covers the small set of web-search provider implementations and the interface they implement. Read this when you need to understand how the system performs searches (either via a provider API or by scraping) and what guarantees callers may rely on about result shape, cancellation, and error behavior. + +## DuckDuckGoWebSearch + +Implements web search against DuckDuckGo endpoint. + +The [DuckDuckGoWebSearch](../Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md) class is a lightweight, no-API-key implementation of [IWebSearch](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) that scrapes DuckDuckGo’s public HTML endpoints. It prefers the richer html.duckduckgo.com/html/ endpoint and will fall back to lite.duckduckgo.com/lite/ if the HTML parsing yields no results; the file lists concrete helpers and steps such as BuildRequest, FetchAsync, ParseHtmlEndpoint, ParseLiteEndpoint, CleanText, DetectAnomalyPage, UnwrapRedirect, EnsureSessionAsync and ResetSession that implement the fetch/parse and session-warmup flow. Operational choices are explicit in the implementation: a single realistic User-Agent is chosen for a session, the session is pre-warmed by visiting the DuckDuckGo homepage so cookies are populated, concurrent warmups are deduplicated with a semaphore, parsing uses forgiving regexes (parse failures produce zero results rather than exceptions), and anomaly/anti-bot pages are logged in detail to aid diagnostics. Because it implements [IWebSearch](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md), callers receive a read-only collection of [WebSearchResult](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) and should expect HTML-driven limitations such as occasional rate-limiting behavior and less deterministic success compared to a paid API. + +## BraveWebSearch + +Implements Brave Web search provider. + +The [BraveWebSearch](../Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md) class is a concrete [IWebSearch](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) that calls the Brave Search API via an HttpClient. It validates configuration (and throws with clear guidance if the Brave API key is missing), clamps the requested result count to the 1–10 range, constructs the provider-specific request URL, executes the HTTP call, and maps Brave’s response body into the provider-agnostic [WebSearchResult](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) shape (Title, Url, Snippet). The implementation wires HttpClientFactory, BraveSearchOptions, and ILogger so concerns like authentication, timeouts, and error reporting are centralized; non-success HTTP responses are logged and rethrown as HttpRequestException containing the status code, making failures observable to callers. + +## IWebSearch + +`IWebSearch` collaborates directly with `BraveWebSearch` and other members of this topic (4 dependency links). + +The [IWebSearch](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) interface defines the contract that both provider implementations satisfy: an asynchronous SearchAsync method that accepts a query, a limit, and a CancellationToken and returns a Task>. The file also declares the immutable [WebSearchResult](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) record (Title, Url, Snippet) that consumers use to work with results in a stable, provider-agnostic form. Implementations are required to honor the CancellationToken, observe the limit (returning at most that many items), and return a read-only, ordered collection that callers may rely on as an upper bound on length. + +How the pieces fit + +[IWebSearch](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) is the boundary between application logic and concrete search backends: both [BraveWebSearch](../Code/src/api/Gabriel.Infrastructure/Tools/Web/BraveWebSearch.cs.md) and [DuckDuckGoWebSearch](../Code/src/api/Gabriel.Infrastructure/Tools/Web/DuckDuckGoWebSearch.cs.md) implement that contract but differ in trade-offs. BraveWebSearch calls a paid provider API and enforces configuration and HTTP error semantics (throwing on non-success responses), while DuckDuckGoWebSearch scrapes DuckDuckGo’s HTML endpoints, pre-warms sessions, and tolerantly parses HTML (returning zero results on parse failures and logging anomalies). Callers can swap providers without changing consumer code, rely on the [WebSearchResult](../Code/src/api/Gabriel.Engine/Tools/Web/IWebSearch.cs.md) shape, and depend on cancellation and the documented limit behavior. + +--- +*Covers 3 of 3 source files identified for this topic.* + +*Synthesised by Aurion on 2026-07-08 05:46:21 UTC*