Ctrl K
ComunidadGuías…

internal/app/http_router.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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}
}