package acp import ( "fmt" "os" "strings" "xworkmate-bridge/internal/memory" "xworkmate-bridge/internal/router" "xworkmate-bridge/internal/skills" ) func handleRoutingResolve(params map[string]any) map[string]any { result, _ := resolveRoutingMetadataWithProviders(params, nil) return mergeRoutingResponse(map[string]any{"ok": true}, result) } func resolveRoutingMetadata(params map[string]any) (router.Result, bool) { return resolveRoutingMetadataWithProviders(params, nil) } func resolveRoutingMetadataWithProviders( params map[string]any, availableProviders []string, ) (router.Result, bool) { routingParams := asMap(params["routing"]) if len(routingParams) == 0 { return router.Result{}, false } installApproval := asMap(routingParams["installApproval"]) resolver := router.NewResolver() result := resolver.Resolve(router.Request{ Prompt: strings.TrimSpace(sharedString(params, "taskPrompt")), WorkingDirectory: strings.TrimSpace(sharedString(params, "workingDirectory")), RoutingMode: strings.TrimSpace(sharedString(routingParams, "routingMode")), PreferredGatewayTarget: strings.TrimSpace(sharedString(routingParams, "preferredGatewayTarget")), ExplicitExecutionTarget: strings.TrimSpace(sharedString(routingParams, "explicitExecutionTarget")), ExplicitProviderID: strings.TrimSpace(sharedString(routingParams, "explicitProviderId")), ExplicitModel: strings.TrimSpace(sharedString(routingParams, "explicitModel")), ExplicitSkills: parseRoutingStringSlice(routingParams["explicitSkills"]), AllowSkillInstall: parseBool(routingParams["allowSkillInstall"]), InstallApproval: skills.InstallApproval{ RequestID: strings.TrimSpace(sharedString(installApproval, "requestId")), ApprovedSkillKeys: parseRoutingStringSlice(installApproval["approvedSkillKeys"]), }, AvailableSkills: parseRoutingSkillCandidates(routingParams["availableSkills"]), AvailableProviders: append([]string(nil), availableProviders...), AIGatewayBaseURL: strings.TrimSpace(sharedString(params, "aiGatewayBaseUrl")), AIGatewayAPIKey: strings.TrimSpace(sharedString(params, "aiGatewayApiKey")), }) return result, true } func mergeRoutingResponse(response map[string]any, result router.Result) map[string]any { if response == nil { response = map[string]any{} } response["resolvedExecutionTarget"] = result.ResolvedExecutionTarget response["resolvedEndpointTarget"] = result.ResolvedEndpointTarget response["resolvedProviderId"] = result.ResolvedProviderID response["resolvedModel"] = result.ResolvedModel response["resolvedSkills"] = append([]string(nil), result.ResolvedSkills...) response["skillResolutionSource"] = result.SkillResolutionSource response["needsSkillInstall"] = result.NeedsSkillInstall response["unavailable"] = result.Unavailable if strings.TrimSpace(result.UnavailableCode) != "" { response["unavailableCode"] = result.UnavailableCode } if strings.TrimSpace(result.UnavailableMessage) != "" { response["unavailableMessage"] = result.UnavailableMessage } if strings.TrimSpace(result.SkillInstallRequestID) != "" { response["skillInstallRequestId"] = result.SkillInstallRequestID } if len(result.SkillCandidates) > 0 { response["skillCandidates"] = routingSkillCandidatesMap(result.SkillCandidates) } if len(result.MemorySources) > 0 { response["memorySources"] = routingMemorySourcesMap(result.MemorySources) } return response } func recordRoutingSuccess( params map[string]any, result router.Result, response map[string]any, ) error { routingParams := asMap(params["routing"]) if len(routingParams) == 0 { return nil } if strings.EqualFold( strings.TrimSpace(sharedString(routingParams, "routingMode")), router.RoutingModeExplicit, ) { return nil } if !parseBool(response["success"]) { return nil } workingDirectory := strings.TrimSpace(sharedString(params, "workingDirectory")) if workingDirectory == "" { return nil } homeDir, _ := os.UserHomeDir() service := memory.NewService(homeDir) return service.RecordSuccess(workingDirectory, memory.SuccessEntry{ ResolvedExecutionTarget: result.ResolvedExecutionTarget, ResolvedProviderID: result.ResolvedProviderID, ResolvedModel: result.ResolvedModel, ResolvedSkills: append([]string(nil), result.ResolvedSkills...), }) } func parseRoutingSkillCandidates(raw any) []skills.Candidate { list, ok := raw.([]any) if !ok { return nil } candidates := make([]skills.Candidate, 0, len(list)) for _, item := range list { entry := asMap(item) candidates = append(candidates, skills.Candidate{ ID: strings.TrimSpace(sharedString(entry, "id")), Label: strings.TrimSpace(sharedString(entry, "label")), Description: strings.TrimSpace(sharedString(entry, "description")), Installed: parseBool(entry["installed"]), }) } return candidates } func routingSkillCandidatesMap(candidates []skills.Candidate) []map[string]any { result := make([]map[string]any, 0, len(candidates)) for _, candidate := range candidates { result = append(result, map[string]any{ "id": candidate.ID, "label": candidate.Label, "description": candidate.Description, "installed": candidate.Installed, }) } return result } func routingMemorySourcesMap(sources []memory.Source) []map[string]any { result := make([]map[string]any, 0, len(sources)) for _, source := range sources { result = append(result, map[string]any{ "path": source.Path, "scope": source.Scope, }) } return result } func parseRoutingStringSlice(raw any) []string { list, ok := raw.([]any) if !ok { if typed, ok := raw.([]string); ok { return append([]string(nil), typed...) } return nil } values := make([]string, 0, len(list)) for _, item := range list { value := strings.TrimSpace(sharedStringArg(item)) if value == "" { continue } values = append(values, value) } return values } func sharedString(params map[string]any, key string) string { if params == nil { return "" } return strings.TrimSpace(sharedStringArg(params[key])) } func sharedStringArg(value any) string { if value == nil { return "" } switch typed := value.(type) { case string: return typed default: return fmt.Sprint(value) } }