internal/app/http_router.go
Rama main · réplica de lectura del repositorio original.
package app
import (
"context"
"net/http"
"strings"
"github.com/yeomyeonggeori/blueclaw/internal/adminapi"
"github.com/yeomyeonggeori/blueclaw/internal/agentruntime"
"github.com/yeomyeonggeori/blueclaw/internal/config"
"github.com/yeomyeonggeori/blueclaw/internal/httpserver"
"github.com/yeomyeonggeori/blueclaw/internal/policy"
"github.com/yeomyeonggeori/blueclaw/internal/runtimecontrol"
"github.com/yeomyeonggeori/blueclaw/internal/sessionquery"
"github.com/yeomyeonggeori/blueclaw/internal/store/postgres"
"github.com/yeomyeonggeori/blueclaw/internal/userapi"
)
func newRouterDependencies(components applicationComponents) httpserver.RouterDependencies {
runtimeConfiguration := components.runtimeConfiguration
directory := components.directory
services := components.services
kernel := components.kernel
return httpserver.RouterDependencies{
ServiceTokenPath: runtimeConfiguration.ServiceTokenPath,
HealthHandler: newHealthHandler(components),
WorkspaceFilesHandler: newWorkspaceFilesHandler(runtimeConfiguration, kernel, directory),
PersonaHandler: newPersonaHandler(runtimeConfiguration, kernel, directory),
ToolCatalogHandler: kernel.toolCatalog.handler,
ScriptHostHandler: kernel.toolCatalog.scriptHost.Handler(),
PolicyHandler: newPolicyHandler(components),
IdentityResolve: adminapi.IdentityResolveHandler{PolicyWatcher: directory.policyWatcher, PlatformAccountLister: directory.platformAccountLister},
AuditHandler: services.auditHandler,
AttentionHandler: adminapi.AttentionHandler{LanguageModel: kernel.taskTierLanguageModels.High},
TaskMonitorHandler: newTaskMonitorHandler(services, directory, components.connectorRuntime),
TaskSearchHandler: adminapi.TaskSearchHandler{SessionQuery: sessionquery.New(services.taskRunService)},
TaskRunHandler: newTaskRunHandler(runtimeConfiguration, services, directory, components.taskLauncher, components.taskIntakeController),
HarnessStatusHandler: newHarnessStatusHandler(runtimeConfiguration, kernel.harnessName),
SkillInventoryHandler: newSkillInventoryHandler(runtimeConfiguration, kernel.capabilityRegistry),
LearningHandler: newLearningHandler(components),
ToolInventoryHandler: adminapi.ToolInventoryHandler{ToolCatalogBuilder: components.toolCatalogBuilder},
TaskApprovalHandler: newTaskApprovalHandler(services, directory, components.taskLauncher, components.connectorRuntime),
HeldCallRedemption: adminapi.HeldCallRedemptionHandler{TaskRunService: services.taskRunService},
QuiesceHandler: adminapi.QuiesceHandler{
Controller: components.taskIntakeController,
TaskRunService: services.taskRunService,
},
ScheduleHandler: newScheduleHandler(runtimeConfiguration, services, directory),
ConnectorDiagnostics: adminapi.ConnectorEventDiagnosticHandler{Repository: services.repositories.connectorEventDiagnostic},
LLMCallExchange: llmCallExchangeHandler(services.repositories.llmCall),
TurnInput: turnInputHandler(services.repositories.taskEvent),
ConversationReset: adminapi.ConversationResetHandler{Repository: services.repositories.conversationReset},
MemoryHandler: adminapi.MemoryHandler{Stores: components.memory.stores, IdentityService: directory.identityService},
BackupHandler: adminapi.BackupHandler{Coordinator: components.backupCoordinator},
TaskInboxHandler: userapi.TaskInboxHandler{TaskRunService: services.taskRunService, TaskStepService: services.taskStepService, TaskAuthService: services.taskAuthService},
TaskActionHandler: userapi.TaskActionHandler{TaskRunService: services.taskRunService, TaskAuthService: services.taskAuthService},
SSEHandler: httpserver.SSEHandler{TaskEventService: services.taskEventService},
ConnectorEventHandler: components.connectorEventHandler,
AgentReplyHandler: httpserver.AgentReplyHandler{ReplyStore: components.agentReplyStore},
}
}
func newHealthHandler(components applicationComponents) httpserver.HealthHandler {
return httpserver.HealthHandler{
LanguageModel: languageModelHealth(components.kernel),
Database: components.foundation.database,
ConnectorRuntime: components.connectorRuntime,
MaximumBacklog: 1000,
ProtocolIdentity: components.protocolIdentity.status,
ProtocolIdentityChecker: &components.protocolIdentity.checker,
ProtocolIdentityExpected: components.protocolIdentity.expected,
}
}
func languageModelHealth(kernel agentKernel) httpserver.LanguageModelHealth {
if kernel.languageModelError != nil {
return httpserver.LanguageModelHealth{Error: kernel.languageModelError.Error()}
}
return httpserver.LanguageModelHealth{Configured: kernel.intakeLanguageModelProvider != nil || kernel.taskTierLanguageModels.High != nil}
}
func newWorkspaceFilesHandler(runtimeConfiguration config.RuntimeConfiguration, kernel agentKernel, directory identityDirectory) httpserver.WorkspaceFilesHandler {
return httpserver.WorkspaceFilesHandler{
WorkspaceRootPath: runtimeConfiguration.Terminal.WorkspaceRootPath,
WorkspaceActorFactory: kernel.terminalService.WorkspaceActorFactory(),
PersonAccessResolver: directory.identityService,
}
}
func newPersonaHandler(runtimeConfiguration config.RuntimeConfiguration, kernel agentKernel, directory identityDirectory) httpserver.PersonaHandler {
return httpserver.PersonaHandler{
WorkspaceRootPath: runtimeConfiguration.Terminal.WorkspaceRootPath,
WorkspaceActorFactory: kernel.terminalService.WorkspaceActorFactory(),
PersonAccessResolver: directory.identityService,
AuthorizeRequest: personaRequestAuthorizer(runtimeConfiguration.Memory.AdminAssertionKeyPath),
}
}
func personaRequestAuthorizer(keyPath string) func(*http.Request) bool {
reader := signedPersonaReader(keyPath)
return func(request *http.Request) bool {
principal := reader(request)
switch {
case request.Method == http.MethodGet && request.URL.Path == "/admin/api/persona/user":
return principal != "" && principal == request.URL.Query().Get("personID")
case request.Method == http.MethodPut && request.URL.Path == "/admin/api/persona/user":
return principal != "" && principal == request.URL.Query().Get("personID")
case request.Method == http.MethodPost && request.URL.Path == "/admin/api/persona/user":
return principal == "internkim-persona-seed"
case request.Method == http.MethodGet && request.URL.Path == "/admin/api/persona/agent":
return principal == "internkim-persona-service"
case request.Method == http.MethodPost && request.URL.Path == "/admin/api/persona/agent":
return principal == "internkim-persona-service"
case request.Method == http.MethodPut && request.URL.Path == "/admin/api/persona/agent":
return principal == "internkim-persona-service"
default:
return false
}
}
}
func newPolicyHandler(components applicationComponents) adminapi.PolicyHandler {
return adminapi.PolicyHandler{
PolicyPath: components.policyPath,
PolicyLoader: components.foundation.policyLoader,
PolicySaver: policy.PolicySaver{},
PolicyWatcher: components.directory.policyWatcher,
Validator: policy.PolicyValidator{},
AuditHandler: components.services.auditHandler,
PersonReferenceCanonicalizer: components.services.repositories.personReferenceCanonicalizer,
PlatformAccountLinker: components.directory.identityService,
OnPolicyReload: newPolicyReloadHandler(components),
}
}
func newPolicyReloadHandler(components applicationComponents) func(policy.PolicyDocument) {
database := components.foundation.database
personRepository := components.services.repositories.person
identityService := components.directory.identityService
policyProjectionService := components.directory.policyProjectionService
posixSynchronizer := components.foundation.posixSynchronizer
logger := components.foundation.logger
return func(policyDocument policy.PolicyDocument) {
if database.SQL != nil {
if errorValue := personRepository.ReplacePeople(policyDocument); errorValue != nil {
logger.Error("policy.people_projection_failed", "error", errorValue)
}
}
identityService.ReloadPolicyProjection(policyProjectionService.ReplacePolicyProjectionTransactionally(policyDocument))
_ = posixSynchronizer.Synchronize(context.Background())
}
}
func newTaskMonitorHandler(services taskServices, directory identityDirectory, retryTaskRun adminapi.TaskRunRetryer) adminapi.TaskMonitorHandler {
return adminapi.TaskMonitorHandler{
TaskRunService: services.taskRunService,
TaskStepService: services.taskStepService,
TaskEventService: services.taskEventService,
IdentityService: directory.identityService,
RetryTaskRun: retryTaskRun,
}
}
func newTaskRunHandler(runtimeConfiguration config.RuntimeConfiguration, services taskServices, directory identityDirectory, taskLauncher *agentruntime.TaskLauncher, taskIntakeController *runtimecontrol.TaskIntakeController) adminapi.TaskRunHandler {
return adminapi.TaskRunHandler{
TaskLauncher: taskLauncher,
IdentityService: directory.identityService,
TaskRunService: services.taskRunService,
TaskIntakeGate: taskIntakeController,
AllowTaskDecisionPreset: runtimeConfiguration.Agent.AllowAdminTaskDiagnostic,
}
}
func newHarnessStatusHandler(runtimeConfiguration config.RuntimeConfiguration, harnessName string) adminapi.HarnessStatusHandler {
return adminapi.HarnessStatusHandler{Status: adminapi.HarnessStatus{
Name: harnessName,
AgentCommandPath: runtimeConfiguration.Agent.Harness.AgentCommandPath,
RunsAsRequesterIdentity: strings.TrimSpace(runtimeConfiguration.Terminal.POSIXHelperPath) != "",
ToolCatalogURL: toolCatalogURL(runtimeConfiguration),
}}
}
func newSkillInventoryHandler(runtimeConfiguration config.RuntimeConfiguration, capabilityRegistry *agentruntime.CapabilityRegistry) adminapi.SkillInventoryHandler {
return adminapi.SkillInventoryHandler{InventoryLoader: func() adminapi.SkillInventory {
instructions := loadAgentInstructions(runtimeConfiguration, capabilityRegistry)
return adminapi.SkillInventory{Loaded: instructions.Bundle.Skills, Unavailable: instructions.UnavailableSkills}
}}
}
func newTaskApprovalHandler(services taskServices, directory identityDirectory, taskLauncher *agentruntime.TaskLauncher, liveHolds adminapi.LiveHolds) adminapi.TaskApprovalHandler {
return adminapi.TaskApprovalHandler{
LiveHolds: liveHolds,
TaskLauncher: taskLauncher,
TaskRunService: services.taskRunService,
IdentityService: directory.identityService,
}
}
func newScheduleHandler(runtimeConfiguration config.RuntimeConfiguration, services taskServices, directory identityDirectory) adminapi.ScheduleHandler {
return adminapi.ScheduleHandler{
CompanyProvider: directory.companyProvider,
SummaryRepository: services.repositories.scheduleSummary,
ListRepository: services.repositories.scheduleList,
RepairRepository: services.repositories.scheduleCreatorRepair,
ReaderPersonID: signedReader(runtimeConfiguration.Memory.AdminAssertionKeyPath, true),
}
}
func llmCallExchangeHandler(repository *postgres.LLMCallRepository) adminapi.LLMCallExchangeHandler {
if repository == nil {
return adminapi.LLMCallExchangeHandler{}
}
return adminapi.LLMCallExchangeHandler{Repository: repository}
}
func turnInputHandler(repository *postgres.TaskEventRepository) adminapi.TurnInputHandler {
if repository == nil {
return adminapi.TurnInputHandler{}
}
return adminapi.TurnInputHandler{Repository: repository}
}