
    i/G                        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dZdd	Zdd
Z	 	 	 d	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 ddZy)    )annotations)defaultdict)Any)JsonRefErrorreplace_refsc                    	  syt        t              dfd j                         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.
    Fc                V   t        | t              rw| j                  d      }t        |t              r7|j	                  d      r&|   j                  |j                  d      d          | j                         D ]  } ||        y t        | t              r| D ]  } ||        y y )N$ref#/$defs//)	
isinstancedictgetstr
startswithaddsplitvalueslist)objsourcerefvitem_collect_refsedgess        m/Users/bowang/.openclaw/workspace/ChatDev/.venv/lib/python3.12/site-packages/fastmcp/utilities/json_schema.pyr   z(_defs_have_cycles.<locals>._collect_refs   s    c4 ''&/C#s#z(Bf!!#))C."45ZZ\a( "T"dF+  #    )r         c                    | <   j                  | d      D ]$  }|vr|   k(  r y|   k(  s |      s$ y | <   y)N TF)r   )	nodeneighborDONEIN_STACK	UNVISITED
_has_cycledefsr   states	     r   r)   z%_defs_have_cycles.<locals>._has_cycle)   s_    d		$+Ht#X(*X)+
80D , dr   c              3  B   K   | ]  }|   k(  xr  |        y wNr#   ).0namer(   r)   r+   s     r   	<genexpr>z$_defs_have_cycles.<locals>.<genexpr>5   s(     NuT{i'<Jt,<<s   )r   r   r   r   returnNone)r$   r   r1   bool)r   setitemsintany)
r*   r/   
definitionr&   r'   r(   r   r)   r   r+   s
   `  @@@@@@@r   _defs_have_cyclesr9   	   sr      "-S!1E	, !JJLjj$' ) !(Ix',E
 
 NNNNr   c                p   t        | j                  di             rt        |       S 	 t        | dd      }| j                  di       }t	        | ||      }t        |t              sJ |}d|v r)|j                         D ci c]  \  }}|dk7  s|| }}}|S c c}}w # 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.

    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_load)	r9   r   resolve_root_refr   _merge_ref_siblingsr   r   r5   r   )schemadereferencedr*   mergedkr   s         r   dereference_refsrD   8   s    B GR01''( $FEUK zz'2&$V\4@&$''' l"-9-?-?-AR-ATQQ'\AqD-ALR S  (  ''(s*   AB  BBB B B54B5Nc           
     "   |
t               }t        | t              rt        |t              rd| v r| d   }| j                         D ci c]  \  }}|dvs|| }}}t        |t              rB|j                  d      r1|j                  d      d   }||v r||vrt        ||   ||||hz        }|rt        |      }	|	j                  |       |	S |S i }
|j                         D ]#  \  }}|| v rt        | |   |||      |
|<   ||
|<   % |
S t        | t              rgt        |t              rWt        t        |       t        |            }t        | d| |d| d      D cg c]  \  }}t        ||||       c}}||d z   S |S c c}}w c c}}w )	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
   )r
   r;   r   r   r   F)strict)r4   r   r   r5   r   r   r   r?   updater   minlenzip)originalrA   r*   visitedr   rC   r   siblingsdef_namerB   resultkeyvaluemin_lenods                   r   r?   r?   v   s   * %(D!jt&DX6"C)1)9X)9AQFW=W1)9HX #s#z(B99S>"-t#(?#6XdGxj<P$L l+h' &,,.JCh1(3-gVs#s	 /
 	Hd	#
<(Fc(mS%67 HXg.Xg0FuU
U1  1dG4U
 "# 	#
 I Y>
s   	FF!Fc                    d| v r`d| v r\d| vrX| d   }t        |t              rC|j                  d      r2|j                  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
   r;   typer   r   r   )r   r   r   r   r   )r@   r   rN   r*   resolveds        r   r>   r>      s}    0 Gv-&2FVnc3CNN:$>yy~b)H'?D4X/$(!Mr   c                    | j                  di       }|j                  |d      }|| S || d<   || j                  dg       v r*| d   j                  |       | d   s| j                  d       | S )zwReturn a new schema with *param* removed from `properties`, `required`,
    and (if no longer referenced) `$defs`.
    
propertiesNrequired)r   popremove)r@   parampropsremoveds       r   _prune_paramr`      sy     JJ|R(Eiit$G !F<

:r**z!!%(j!JJz"Mr   c                  	
 sss| S t               	t        t              | j                  d      }	 	 	 d	 	 	 	 	 	 	 	 	 d		
fd
 
| d       ry|rw|j	                         D ]  \  }} 
||        d
d	fdt        |j                               D ]  } |      r|j                  |        |s| j                  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}
    r;   Nc                    |dkD  ryt         t              rrn j                  d      }t        |t              rM|j	                  d      r<|j                  d      d   }|r	|   j                  |       nj                  |       r)d v r%t         fdd	D              r j                  d       
r$ j                  d
      du r j                  d
        j                         D ]E  \  }}|r|dk(  r|dv r&t        |t              r|D ]  } |||dz           8 |||dz          G yt         t              r D ]  } |||dz           yy)zATraverse schema tree, collecting $ref info and applying cleanups.2   Nr
   r   r   r   titlec              3  &   K   | ]  }|v  
 y wr-   r#   )r.   rC   r$   s     r   r0   zD_single_pass_optimize.<locals>.traverse_and_clean.<locals>.<genexpr>O  s!      	 I	s   )rV   rY   r
   r5   allOfoneOfanyOfrZ   additionalPropertiesFr;   )rf   rg   rh   r    )depth)r   r   r   r   r   r   appendr   r7   r[   r5   r   )r$   current_def_nameskip_defs_sectionrj   r   referenced_defrP   rQ   r   def_dependenciesprune_additional_properties
prune_defsprune_titles	root_refstraverse_and_cleans   `        r   rt   z1_single_pass_optimize.<locals>.traverse_and_clean2  s[    2:dD!hhv&c3'CNN:,F%(YYs^B%7N'(8??@PQ "n5 4 	  HHW% ,HH34=/0 #jjl
U$ 55*UD:Q %*41AQRS !& 'u.>eaiP + d#"4)9K  $r   T)rm   )rl   c                    | v ryj                  | g       }|r.|
t               }| |v ry|| hz  }|D ]  }||vs ||      s y y)z<Check if a definition is used, handling circular references.TF)r   r4   )rN   visitingreferencing_defsreferencing_defro   is_def_usedrs   s       r   ry   z*_single_pass_optimize.<locals>.is_def_used}  sv    9$  033HbA#"uH x' #xj0 (8O&h6;'<  $	 (8 r   )NFr   )
r$   objectrl   z
str | Nonerm   r3   rj   r6   r1   r2   r-   )rN   r   rv   set[str] | Noner1   r3   )r4   r   r   r   r5   keysr[   )r@   rr   rp   rq   r*   rN   
def_schemaro   ry   rs   rt   s    ```   @@@@r   _single_pass_optimizer~      s   d ,*E %I4?5 ::gD (,"'	@L@L$@L  @L 	@L
 
@L @LF v6 d$(JJL HjzHE %1	 	4 TYY[)Hx(" *
 JJw%Mr   c                    |rt        |       } t        |       } |xs 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.
    )r]   T)rr   rp   rq   )rD   r>   r`   r~   )r@   prune_paramsrp   rr   dereferencer]   s         r   compress_schemar     s\    * !&) f%F ##fE2 $
 #!$?	F Mr   )r*   dict[str, Any]r1   r3   )r@   r   r1   r   r-   )
rK   r   rA   r   r*   r   rL   r{   r1   r   )r@   r   r]   r   r1   r   )FFT)
r@   r   rr   r3   rp   r3   rq   r3   r1   r   )NFFF)r@   r   r   zlist[str] | Nonerp   r3   rr   r3   r   r3   r1   r   )
__future__r   collectionsr   typingr   jsonrefr   r   r9   rD   r?   r>   r`   r~   r   r#   r   r   <module>r      s    " #  .,O^;(D  $	@@@ @ 	@
 	@F$N. (-	jjj "&j 	j
 j^ &*(-(("( "&( 	(
 ( (r   