
    $ji                    4   d dl mZ d dlmZ d dlmZ d dlmZmZ d*d	Z	d+dZ
d+dZd,dZ	 d-d.dZd,dZd/dZ eh d          Z eh d          Z eh d          Z eh d          Z eh d          Z eh d          Z	 	 	 d0d1d%Z	 	 	 	 d2d3d)ZdS )4    )annotations)defaultdict)Any)JsonRefErrorreplace_refsdefsdict[str, Any]returnboolc                "   	  sdS t          t                    dfd                                 D ]\  }} ||           d	\  t          t                    	d 	fdt	          	fd D                       S )uJ  Check whether any definitions in ``$defs`` form a reference cycle.

    A cycle means a definition directly or transitively references itself
    (e.g. Node → children → Node, or A → B → A).  ``jsonref.replace_refs``
    silently produces Python-level object cycles for these, which Pydantic's
    serializer rejects.
    Fobjr   sourcestrr
   Nonec                   t          | t                    r|                     d          }t          |t                    rI|                    d          r4|                             |                    d          d                    |                                 D ]} ||           d S t          | t                    r| D ]} ||           d S d S )N$ref#/$defs//)	
isinstancedictgetr   
startswithaddsplitvalueslist)r   r   refvitem_collect_refsedgess        e/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/fastmcp/utilities/json_schema.pyr!   z(_defs_have_cycles.<locals>._collect_refs   s    c4   	,''&//C#s## 6z(B(B 6f!!#))C.."4555ZZ\\ ) )a(((() )T"" 	, , ,dF++++	, 	,, ,    )r         noder   c                    | <                        | d          D ]0}|vr|         k    r dS |         k    r |          r dS 1| <   dS )N TF)r   )	r'   neighborDONEIN_STACK	UNVISITED
_has_cycler   r"   states	     r#   r.   z%_defs_have_cycles.<locals>._has_cycle)   s    d		$++ 	 	Ht##X(**ttX)++

80D0D+ttdur$   c              3  F   K   | ]}|         k    o
 |          V  d S Nr)   ).0namer-   r.   r/   s     r#   	<genexpr>z$_defs_have_cycles.<locals>.<genexpr>5   s;      NNuT{i'<JJt,<,<NNNNNNr$   )r   r   r   r   r
   r   )r'   r   r
   r   )r   setitemsintany)
r   r3   
definitionr+   r,   r-   r!   r.   r"   r/   s
   `  @@@@@@@r#   _defs_have_cyclesr:   	   s      u "-S!1!1E	, 	, 	, 	, 	, 	, 	, !JJLL ( (jj$'''' !(Ix',,E
 
 
 
 
 
 
 
 
 
 
 
 NNNNNNNNNNNNr$   r   r   c                h   t          | t                    r{|                     d          }t          |t                    r3|                    d          sd |                                 D             S d |                                 D             S t          | t                    rd | D             S | S )a  Return a deep copy of *obj* with non-local ``$ref`` values removed.

    Local refs (starting with ``#``) are kept intact.  Remote refs
    (``http://``, ``https://``, ``file://``, or any other URI scheme) are
    stripped so that ``jsonref.replace_refs`` never attempts to fetch an
    external resource.  This prevents SSRF / LFI when proxying schemas
    from untrusted servers.
    r   #c                @    i | ]\  }}|d k    |t          |          S )r   _strip_remote_refsr2   kr   s      r#   
<dictcomp>z&_strip_remote_refs.<locals>.<dictcomp>E   s,    TTTAVA)!,,r$   c                4    i | ]\  }}|t          |          S r)   r>   r@   s      r#   rB   z&_strip_remote_refs.<locals>.<dictcomp>F   s'    AAATQ%a((AAAr$   c                ,    g | ]}t          |          S r)   r>   r2   r    s     r#   
<listcomp>z&_strip_remote_refs.<locals>.<listcomp>H   s!    999T"4((999r$   )r   r   r   r   r   r6   r   )r   r   s     r#   r?   r?   8   s     #t Bggfooc3 	Us(;(; 	UTTTTTTAASYY[[AAAA#t :99S9999Jr$   c                    t          | t                    r1d| v od| v pd| v h dfd|                                 D             S t          | t                    rd | D             S | S )uo  Recursively remove OpenAPI ``discriminator`` keys from a schema.

    Pydantic emits ``discriminator.mapping`` with values like
    ``#/$defs/ClassName``.  After ``$defs`` are inlined and removed by
    ``dereference_refs``, those mapping entries dangle.  The keyword is an
    OpenAPI extension — the ``anyOf`` variants already carry ``const`` on
    the discriminant field, so the mapping is redundant.

    Only strips ``discriminator`` when it appears alongside ``anyOf`` or
    ``oneOf``, which is where the OpenAPI keyword lives.  A property
    *named* ``discriminator`` (inside ``properties``) is left alone.
    discriminatoranyOfoneOf>   enumconstdefaultexamplesc                R    i | ]#\  }}|d k    r||v r|nt          |          $S )rH   _strip_discriminator)r2   rA   r   
_DATA_KEYSskips      r#   rB   z(_strip_discriminator.<locals>.<dictcomp>]   sN     
 
 
1((T( Q*__*>q*A*A(((r$   c                ,    g | ]}t          |          S r)   rP   rE   s     r#   rF   z(_strip_discriminator.<locals>.<listcomp>c   s!    ;;;t$T**;;;r$   )r   r   r6   r   )r   rR   rS   s    @@r#   rQ   rQ   L   s     #t 
#%L7c>+KW^===

 
 
 
 
		
 
 
 	

 #t <;;s;;;;Jr$   schemac                   t          |           } t          |                     di                     rt          |           S 	 t	          | dd          }|                     di           }t          | ||          }t          |t                    sJ |}d|v rd |                                D             }t          |          }|S # t          $ r t          |           cY S w xY w)a  Resolve all $ref references in a JSON schema by inlining definitions.

    This function resolves $ref references that point to $defs, replacing them
    with the actual definition content while preserving sibling keywords (like
    description, default, examples) that Pydantic places alongside $ref.

    This is necessary because some MCP clients (e.g., VS Code Copilot) don't
    properly handle $ref in tool input schemas.

    For self-referencing/circular schemas where full dereferencing is not possible,
    this function falls back to resolving only the root-level $ref while preserving
    $defs for nested references.

    Only local ``$ref`` values (those starting with ``#``) are resolved.
    Remote URIs (``http://``, ``file://``, etc.) are stripped before
    resolution to prevent SSRF / local-file-inclusion attacks when proxying
    schemas from untrusted servers.

    Args:
        schema: JSON schema dict that may contain $ref references

    Returns:
        A new schema dict with $ref resolved where possible and $defs removed
        when no longer needed

    Example:
        >>> schema = {
        ...     "$defs": {"Category": {"enum": ["a", "b"], "type": "string"}},
        ...     "properties": {"cat": {"$ref": "#/$defs/Category", "default": "a"}}
        ... }
        >>> resolved = dereference_refs(schema)
        >>> # Result: {"properties": {"cat": {"enum": ["a", "b"], "type": "string", "default": "a"}}}
    $defsF)proxies	lazy_loadc                &    i | ]\  }}|d k    ||S )rW   r)   r@   s      r#   rB   z$dereference_refs.<locals>.<dictcomp>   s#    RRRTQQ'\\Aq\\\r$   )r?   r:   r   resolve_root_refr   _merge_ref_siblingsr   r   r6   rQ   r   )rU   dereferencedr   mergeds       r#   dereference_refsr_   g   s   F  ''F GR0011 ('''( $FEUKKK zz'2&&$V\4@@&$''''' l""RR\-?-?-A-ARRRL ,L99 ( ( (  '''''(s   BC C$#C$Noriginalr]   visitedset[str] | Nonec                   t                      t          | t                    rt          |t                    r d| v r| d         }d |                                 D             }t          |t                    rT|                    d          r?|                    d          d         }|v r |vrt          |         ||hz            }|r&t          |          }|                    |           |S |S i }|                                D ]*\  }	}
|	| v rt          | |	         |
          ||	<   %|
||	<   +|S t          | t                    ryt          |t                    rdt          t          |           t          |                    }fdt          | d|         |d|         d	          D             ||d         z   S |S )
a  Merge sibling keywords from original $ref nodes into dereferenced schema.

    When jsonref resolves $ref, it replaces the entire node with the referenced
    definition, losing any sibling keywords like description, default, or examples.
    This function walks both trees in parallel and merges those siblings back.

    Args:
        original: The original schema with $ref and potential siblings
        dereferenced: The schema after jsonref processing
        defs: The $defs from the original schema, for looking up referenced definitions
        visited: Set of definition names already being processed (prevents cycles)

    Returns:
        The dereferenced schema with sibling keywords restored
    Nr   c                "    i | ]\  }}|d v	||S ))r   rW   r)   r@   s      r#   rB   z'_merge_ref_siblings.<locals>.<dictcomp>   s)    XXXAQFW=W=W1=W=W=Wr$   r   r   r   c                :    g | ]\  }}t          ||          S r)   )r\   )r2   odr   ra   s      r#   rF   z'_merge_ref_siblings.<locals>.<listcomp>   s;     
 
 
1  1dG44
 
 
r$   F)strict)r5   r   r   r6   r   r   r   r\   updater   minlenzip)r`   r]   r   ra   r   siblingsdef_namer^   resultkeyvaluemin_lens     ``        r#   r\   r\      s   * %%(D!! &#jt&D&D &#X6"CXX)9)9XXXH #s## z(B(B 99S>>"-t##(?(?#6XdGxj<P$ $L  l++h''' &,,.. 	$ 	$JCh1(3-gVVs#s	Hd	#	# #
<(F(F #c(mmS%6%677
 
 
 
 
HXgX.XgX0FuUUU
 
 
 "# 	#
 r$   c                   d| v r}d| v ryd| vru| d         }t          |t                    rX|                    d          rC|                    d          d         }| d         }||v rt	          ||                   }||d<   |S | S )a  Resolve $ref at root level to meet MCP spec requirements.

    MCP specification requires outputSchema to have "type": "object" at the root level.
    When Pydantic generates schemas for self-referential models, it uses $ref at the
    root level pointing to $defs. This function resolves such references by inlining
    the referenced definition while preserving $defs for nested references.

    Args:
        schema: JSON schema dict that may have $ref at root level

    Returns:
        A new schema dict with root-level $ref resolved, or the original schema
        if no resolution is needed

    Example:
        >>> schema = {
        ...     "$defs": {"Node": {"type": "object", "properties": {...}}},
        ...     "$ref": "#/$defs/Node"
        ... }
        >>> resolved = resolve_root_ref(schema)
        >>> # Result: {"type": "object", "properties": {...}, "$defs": {...}}
    r   rW   typer   r   r   )r   r   r   r   r   )rU   r   rn   r   resolveds        r#   r[   r[      s    0 Gv--&2F2FVnc3 	 CNN:$>$> 	 yy~~b)H'?D4X//$(!Mr$   paramr   c                   |                      di           }|                    |d          }|| S || d<   ||                      dg           v r8| d                             |           | d         s|                     d           | S )zwReturn a new schema with *param* removed from `properties`, `required`,
    and (if no longer referenced) `$defs`.
    
propertiesNrequired)r   popremove)rU   rv   propsremoveds       r#   _prune_paramr~     s     JJ|R((Eiit$$G !F<

:r****z!!%(((j! 	#JJz"""Mr$   >   rt   allOfrI   r6   rJ   ry   rx   r   >   titlereadOnly	writeOnly
deprecateddescription>   rK   rL   rM   examplerN   >   rx   definitionsdependenciesdependentSchemaspatternPropertiesrW   >   ifnotelsethenr6   containscontentSchemapropertyNamesadditionalItemsunevaluatedItemsadditionalPropertiesunevaluatedProperties>   r   rI   rJ   prefixItemsFTprune_titlesprune_additional_properties
prune_defsc                  	
 sss| S t                      	t          t                    |                     d          }	 	 	 	 dd	
fd
 
| dd           r|r|                                D ]\  }} 
||d           dd	fdt          |                                          D ]"} |          s|                    |           #|s|                     dd           | S )aK  
    Optimize JSON schemas in a single traversal for better performance.

    This function combines three schema cleanup operations that would normally require
    separate tree traversals:

    1. **Remove unused definitions** (prune_defs): Finds and removes `$defs` entries
       that aren't referenced anywhere in the schema, reducing schema size.

    2. **Remove titles** (prune_titles): Strips `title` fields throughout the schema
       to reduce verbosity while preserving functional information.

    3. **Remove restrictive additionalProperties** (prune_additional_properties):
       Removes `"additionalProperties": false` constraints to make schemas more flexible.

    **Performance Benefits:**
    - Single tree traversal instead of multiple passes (2-3x faster)
    - Immutable design prevents shared reference bugs
    - Early termination prevents runaway recursion on deeply nested schemas

    **Algorithm Overview:**
    1. Traverse main schema, collecting $ref references and applying cleanups
    2. Traverse $defs section to map inter-definition dependencies
    3. Remove unused definitions based on reference analysis

    Args:
        schema: JSON schema dict to optimize (not modified)
        prune_titles: Remove title fields for cleaner output
        prune_additional_properties: Remove "additionalProperties": false constraints
        prune_defs: Remove unused $defs entries to reduce size

    Returns:
        A new optimized schema dict

    Example:
        >>> schema = {
        ...     "type": "object",
        ...     "title": "MySchema",
        ...     "additionalProperties": False,
        ...     "$defs": {"UnusedDef": {"type": "string"}}
        ... }
        >>> result = _single_pass_optimize(schema, prune_titles=True, prune_defs=True)
        >>> # Result: {"type": "object", "additionalProperties": False}
    rW   NFr   Tr'   objectcurrent_def_name
str | Noneskip_defs_sectionr   depthr7   	in_schemar
   r   c                    |dk    rdS t           t                    rr                     d          }t          |t                    rc|                    d          rN|                    d          d         }|r|                             |           n                    |           |rrmd v rit           d         t                    rNt           fdt          D                       st          d	  D                       r                     d           r,                     d
          du r                     d
                                            D ]\  }}|r|dk    r|s |||dz   d           #|t          v r?t          |t                    r*|                                D ]}	 |	||dz   d           k|t          v ru|t           v r-t          |t"                    r|D ]}
 |
||dz   d           |t$          v r |||dz   d            |||dz   d           dS t           t"                    r D ]}
 |
||dz   |           dS dS )uC  Traverse schema tree, collecting $ref info and applying cleanups.

        The `in_schema` flag tracks whether the current node is reached via a
        known JSON-Schema-valued position (root, `properties` value, `items`,
        `allOf` element, etc.). When False — e.g. we descended through a user
        extension key like `x-ui` whose payload is opaque to us — we still
        collect `$ref` references (they may point at `$defs` the user cares
        about) but we skip all cleanups so we don't mutate user data that
        happens to look metadata-shaped.
        2   Nr   r   r   r   r   c              3      K   | ]}|v V  	d S r1   r)   )r2   rA   r'   s     r#   r4   zD_single_pass_optimize.<locals>.traverse_and_clean.<locals>.<genexpr>  s'      @@!AI@@@@@@r$   c              3  (   K   | ]}|t           v V  d S r1   )_METADATA_KEYS)r2   rA   s     r#   r4   zD_single_pass_optimize.<locals>.traverse_and_clean.<locals>.<genexpr>  s'      AAqqN2AAAAAAr$   r   FrW   r%   )r   r   T)r   r   r   r   r   r   appendr   r8   _SCHEMA_KEYWORDSallrz   r6   _SUBSCHEMA_MAP_KEYSr   _LITERAL_KEYWORDS_SUBSCHEMA_LIST_KEYSr   _SUBSCHEMA_VALUE_KEYS)r'   r   r   r   r   r   referenced_defrp   rq   
sub_schemar    def_dependenciesr   r   r   	root_refstraverse_and_cleans   `          r#   r   z1_single_pass_optimize.<locals>.traverse_and_clean  s   " 2::FdD!! o	  	6hhv&&c3'' 6CNN:,F,F 6%(YYs^^B%7N' 6(8??@PQQQQ "n555  5 !	&4"4=#66 ( @@@@/?@@@@@ ( AADAAAAA	 ( HHW%%% 05!788EAAHH3444 #jjll = =
U$  ! &&/uqyE     ---*UD2I2I-&+llnn  
**&,"'!)&*	      +++ ...:eT3J3J. %  ** ,"'!)&*	     ///&&(#ai"&	    
 #"+519    w= =~ d## 	  ""*%!)y    	 	 r$   )r   r   )r   r   rn   r   visitingrb   c                    | v rdS                      | g           }|r4|t                      }| |v rdS || hz  }|D ]}||vr ||          r dS dS )z<Check if a definition is used, handling circular references.TNF)r   r5   )rn   r   referencing_defsreferencing_defr   is_def_usedr   s       r#   r   z*_single_pass_optimize.<locals>.is_def_usedM  s    9$$t  033HbAA $#"uuH x'' 5#xj0 (8 $ $O&h66;;'< <6  $tt5r$   )NFr   T)r'   r   r   r   r   r   r   r7   r   r   r
   r   r1   )rn   r   r   rb   r
   r   )r5   r   r   r   r6   keysrz   )rU   r   r   r   r   rn   
def_schemar   r   r   r   s    ```   @@@@r#   _single_pass_optimizer     s   d  , *E  %%I4?5 5 ::gD (,"'C C C C C C C C C C C CL vFFFF  %&d %&$(JJLL 	V 	V HjzHPTUUUUU	 	 	 	 	 	 	 	 	4 TYY[[)) 	# 	#H;x(( #"""  	&JJw%%%Mr$   prune_paramslist[str] | Nonedereferencec                    |rt          |           } t          |           } |pg D ]}t          | |          } t          | ||d          } | S )a  
    Compress and optimize a JSON schema for MCP compatibility.

    Args:
        schema: The schema to compress
        prune_params: List of parameter names to remove from properties
        prune_additional_properties: Whether to remove additionalProperties: false.
            Defaults to False to maintain MCP client compatibility, as some clients
            (e.g., Claude) require additionalProperties: false for strict validation.
        prune_titles: Whether to remove title fields from the schema
        dereference: Whether to dereference $ref by inlining definitions.
            Defaults to False; dereferencing is typically handled by
            middleware at serve-time instead.
    )rv   T)r   r   r   )r_   r[   r~   r   )rU   r   r   r   r   rv   s         r#   compress_schemar   r  s{    *  *!&)) f%%F # 3 3fE222 #!$?	  F Mr$   )r   r	   r
   r   )r   r   r
   r   )rU   r	   r
   r	   r1   )
r`   r   r]   r   r   r	   ra   rb   r
   r   )rU   r	   rv   r   r
   r	   )FFT)
rU   r	   r   r   r   r   r   r   r
   r	   )NFFF)rU   r	   r   r   r   r   r   r   r   r   r
   r	   )
__future__r   collectionsr   typingr   jsonrefr   r   r:   r?   rQ   r_   r\   r[   r~   	frozensetr   r   r   r   r   r   r   r   r)   r$   r#   <module>r      s   " " " " " " # # # # # #       . . . . . . . .,O ,O ,O ,O^   (   6J( J( J( J(b  $	@ @ @ @ @F$ $ $ $N   . 9	 	 	        IQQQRR   i  	 	  "	    $ !y!K!K!KLL 
 (-	m m m m md &*(-( ( ( ( ( ( (r$   