Ë
    ÛJêjæ  ã                  óŽ   — d Z ddlmZ ddlZddlZddlZddlZddlmZ  ej                  e
«      Z G d„ de«      Z G d„ d«      Zy)	an  Kilo configuration builder for CyberBrain26 with a strict free-only whitelist.

Only models whose registry record reports ``is_free`` may appear in the
generated configuration.  When no free-eligible model exists the builder
raises instead of emitting an empty configuration, so a caller can never
install a provider block that would silently route to a paid model.
é    )ÚannotationsN)ÚAnyc                  ó   — e Zd ZdZy)ÚNoFreeModelAvailablez=Raised when the registry holds no free-eligible active model.N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© ó    úJ/home/oem/CyberBrain26-unified/src/cyberbrain26/model_layer/kilo_config.pyr   r      s   „ ÚGr   r   c                  ó”   — e Zd ZdZddd„Zdd„Zdd„Zddœdd„Z	 dddœ	 	 	 	 	 	 	 dd„Zdd	„Z	dd
„Z
	 dddœ	 	 	 	 	 	 	 	 	 dd„Zdd„Zy)ÚKiloConfigBuilderzEBuild Kilo provider configuration restricted to free-eligible models.c                ó    — || _         || _        y ©N)Úmodel_registryÚprovider_id)Úselfr   r   s      r   Ú__init__zKiloConfigBuilder.__init__   s   € Ø,ˆÔØ&ˆÕr   c                ó2   — |€yt        |j                  «      S )a,  Return ``True`` only for registry records explicitly marked free.

        ``ModelRecord.is_free`` is a property that maps ``free_status`` onto
        the FREE / FREE_FOR_TEST / FREE_FOR_STUDY set.  Anything unknown or paid
        is therefore rejected, which is the fail-closed direction.
        F)ÚboolÚis_free)r   Úmodels     r   Ú_is_free_eligiblez#KiloConfigBuilder._is_free_eligible   s   € ð ˆ=ØÜ�E—M‘MÓ"Ð"r   c                óš   — | j                   j                  «       D �cg c]  }| j                  |«      sŒ|‘Œ }}t        |d„ ¬«      S c c}w )z>Return the sorted free-eligible active models of the registry.c                ó   — | j                   S r   )Úmodel_id)Úms    r   ú<lambda>z0KiloConfigBuilder._free_models.<locals>.<lambda>-   s
   € ¨!¯*©*€ r   )Úkey)r   Úlist_activer   Úsorted)r   r   Úfrees      r   Ú_free_modelszKiloConfigBuilder._free_models*   sF   € à×.Ñ.×:Ñ:Ó<ÖZ�aÀ×@VÑ@VÐWXÕ@Y’ÐZˆÐZÜ�dÑ 4Ô5Ð5ùò [s
   �A´AN©r   c          
     ór  — |xs | j                   }| j                  «       }|st        d«      ‚i }|D ]K  }|j                  |j                  |j                  dd|j
                  xs dddœdddœd	œ||j                  <   ŒM |||D �cg c]  }|j                  ‘Œ c}d
œi|› d|d   j                  › �dœS c c}w )a_  Build the provider configuration block for ``provider_id``.

        Raises:
            NoFreeModelAvailable: if the registry has no free-eligible active
                model.  Emitting an empty whitelist would be indistinguishable
                from a working configuration at the call site, so the builder
                fails instead.
        zwrefusing to build a Kilo provider configuration without a free-eligible model: a paid fallback must never be configuredFTi €  i   )ÚcontextÚoutputr   )Úinputr(   )ÚnameÚidÚproviderÚ	tool_callÚ	reasoningÚlimitÚcost)ÚmodelsÚ	whitelistú/)r,   r   )r   r$   r   r,   r   Úcontext_window)r   r   Útargetr1   Úentriesr   r   s          r   Úbuild_kilo_provider_configz,KiloConfigBuilder.build_kilo_provider_config/   sç   € ð Ò0 × 0Ñ 0ˆØ×"Ñ"Ó$ˆÙÜ&ðPóð ð
 #%ˆØò 	ˆEàŸ™Ø—n‘nØ!ŸN™NØ"Ø!à$×3Ñ3Ò<°uØ"ñð #$¨qÑ1ñ'ˆG�E—N‘NÒ#ð	ð  Ø%Ø6<Ö!=° !§*£*Ò!=ñðð �x˜q ¨¡×!3Ñ!3Ð 4Ð5ñ
ð 	
ùò ">s   ÂB4c               óª  — |xs | j                   }|xs | j                  }g }|j                  di «      }|j                  «       D ]ó  \  }}	|	j                  di «      D ]e  }
|j                  |
«      }|€|j	                  d|
›d|›d�«       Œ/| j                  |«      rŒA|j	                  d|
›d|›d|j                  ›d�«       Œg |	j                  dg «      D ]_  }
|j                  |
«      }|€|j	                  d	|
›d�«       Œ,| j                  |«      rŒ>|j	                  d	|
›d|j                  ›d�«       Œa Œõ ||vr|j	                  d
|›�«       |S )zFReport every model in ``config`` that is unknown or not free-eligible.r,   r1   zmodel z from provider z is not in the registryz# is not free-eligible (free_status=ú)r2   zwhitelisted model z-configuration declares no provider block for )r   r   ÚgetÚitemsÚappendr   Úfree_status)r   Úconfigr   r   Úregistryr5   ÚproblemsÚ	providersr,   Úblockr   Úrecords               r   Úvalidate_kilo_provider_configz/KiloConfigBuilder.validate_kilo_provider_configY   s‹  € ð "Ò8 T×%8Ñ%8ˆØÒ0 × 0Ñ 0ˆØ ˆà—J‘J˜z¨2Ó.ˆ	Ø(Ÿ™Ó0ò 	‰OˆH�eØ!ŸI™I h°Ó3ò �ð "Ÿ™ hÓ/�Ø�>Ø—O‘OØ   ¨O¸H¸<ÐG^Ð_õð ×/Ñ/°Õ7Ø—O‘OØ   ¨O¸H¸<ð H(Ø(.×(:Ñ(:Ð'=¸Qð@õðð "ŸI™I k°2Ó6ò 
�Ø!Ÿ™ hÓ/�Ø�>Ø—O‘OØ,¨X¨LÐ8OÐPõð ×/Ñ/°Õ7Ø—O‘OØ,¨X¨Lð 9(Ø(.×(:Ñ(:Ð'=¸Qð@õñ
ð	ð8 ˜Ñ"Ø�O‰OÐKÈFÈ:ÐVÔWàˆr   c           	     óR  — g }t        t        |«      t        |«      z  «      D ]r  }|j                  |«      |j                  |«      k7  sŒ'|j                  d|› d|j                  |«      ›�«       |j                  d|› d|j                  |«      ›�«       Œt dj	                  |«      S )z@Render a human-readable line diff of two configuration mappings.z- z: z+ ú
)r"   Úsetr:   r<   Újoin)r   ÚcurrentÚnewÚlinesr    s        r   Údiff_kilo_configz"KiloConfigBuilder.diff_kilo_config‡   s—   € àˆÜœ#˜g›,¬¨S«Ñ1Ó2ò 	;ˆCØ�{‰{˜3Ó 3§7¡7¨3£<Ó/Ø—‘˜r #  b¨¯©°SÓ)9Ð(<Ð=Ô>Ø—‘˜r #  b¨¯©°«Ð(8Ð9Õ:ð	;ð �y‰y˜ÓÐr   c                óÔ   — t         j                  j                  |«      st        j	                  d|«       y|› d�}t        j                  ||«       t        j                  d|«       |S )zÇCopy ``path`` to ``<path>.bak`` and return the backup path.

        Returns ``None`` when the source file does not exist: a missing file
        must not be reported as a successful backup.
        z(cannot back up missing Kilo config at %sNz.bakzKilo config backed up to %s)ÚosÚpathÚisfileÚloggerÚwarningÚshutilÚcopy2Úinfo©r   rO   Úbackup_paths      r   Úbackup_kilo_configz$KiloConfigBuilder.backup_kilo_config�   sT   € ô �w‰w�~‰~˜dÔ#Ü�N‰NÐEÀtÔLØØ˜˜d�mˆÜ�‰�T˜;Ô'Ü�‰Ð1°;Ô?ØÐr   c               ó\  — | j                  |||¬«      }|r*t        j                  ddj                  |«      «       d|dœS | j	                  |«      }t
        j                  j                  t
        j                  j                  |«      «      xs d}t        j                  |d¬«       |› d	�}t        |d
d¬«      5 }	t        j                  ||	ddd¬«       |	j                  d«       ddd«       t        j                  ||«       t        j                  d|«       d||dœS # 1 sw Y   Œ;xY w)züValidate and atomically write ``new_config`` to ``path``.

        The write is atomic (temp file in the same directory followed by
        ``os.replace``) so a failed write can never leave a half-written
        provider configuration behind.
        r%   z!refusing to apply Kilo config: %sz; F)Úappliedr@   ú.T)Úexist_okz.tmpÚwzutf-8)Úencodingé   )ÚindentÚensure_asciiÚ	sort_keysrF   Nzapplied Kilo config to %s)rZ   ÚbackuprO   )rD   rQ   ÚerrorrH   rX   rN   rO   ÚdirnameÚabspathÚmakedirsÚopenÚjsonÚdumpÚwriteÚreplacerU   )
r   rO   Ú
new_configr   r   r@   rW   Ú	directoryÚtmp_pathÚhandles
             r   Úapply_kilo_configz#KiloConfigBuilder.apply_kilo_configž   s  € ð ×5Ñ5Ø˜°Kð 6ó 
ˆñ Ü�L‰LÐ<¸d¿i¹iÈÓ>QÔRØ$°(Ñ;Ð;à×-Ñ-¨dÓ3ˆä—G‘G—O‘O¤B§G¡G§O¡O°DÓ$9Ó:ÒA¸cˆ	Ü
�‰�I¨Õ-Ø�V˜4�=ˆÜ�(˜C¨'Ô2ð 	°fÜ�I‰I�j &°ÀÐRVÕWØ�L‰L˜Ô÷	ô 	�
‰
�8˜TÔ"ä�‰Ð/°Ô6Ø¨;ÀÑEÐE÷	ð 	ús   Â<,D"Ä"D+c                óÈ   — t         j                  j                  |«      st        j	                  d|«       yt        j                  ||«       t        j                  d|«       y)z¹Restore ``path`` from ``backup_path``.

        Returns ``False`` when the backup is absent instead of raising, so the
        caller keeps the current configuration untouched.
        z(cannot restore: backup %s does not existFzrestored Kilo config from %sT)rN   rO   rP   rQ   rd   rS   rT   rU   rV   s      r   Úrestore_kilo_configz%KiloConfigBuilder.restore_kilo_configÀ   sF   € ô �w‰w�~‰~˜kÔ*Ü�L‰LÐCÀ[ÔQØÜ�‰�[ $Ô'Ü�‰Ð2°KÔ@Ør   )Úcb26)r   r   r   ÚstrÚreturnÚNone)r   r   rv   r   )rv   z	list[Any])r   ú
str | Nonerv   údict[str, Any]r   )r>   ry   r   ú
Any | Noner   rx   rv   z	list[str])rI   ry   rJ   ry   rv   ru   )rO   ru   rv   rx   )
rO   ru   rm   ry   r   rz   r   rx   rv   ry   )rO   ru   rW   ru   rv   r   )r   r   r	   r
   r   r   r$   r7   rD   rL   rX   rq   rs   r   r   r   r   r      s·   „ ÙOô'ó	#ó6ð
 GKõ (
ðZ &*ð,ð
 #'ñ,àð,ð #ð,ð
  ð,ð 
ó,ó\ óð$ &*ð	 Fð #'ñ Fàð Fð #ð Fð #ð	 Fð  ð Fð 
ó FôDr   r   )r
   Ú
__future__r   ri   ÚloggingrN   rS   Útypingr   Ú	getLoggerr   rQ   ÚRuntimeErrorr   r   r   r   r   ú<module>r€      sJ   ðñõ #ã Û Û 	Û Ý à	ˆ×	Ñ	˜8Ó	$€ôH˜<ô H÷sò sr   