
    RmjI                       U d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	mZm
Z
mZmZmZ ddlmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z' ddlm(Z) ddlm*Z+ dd	lm,Z- dd
lm.Z/ e0e1z  e2z  e3z  dz  Z4de5d<   e4e6d         z  e7e0df         z  Z8de5d<   e7e0e8f         Z9de5d<   e8Z:de5d<   e
d         Z;de5d<    G d de          Z<ee0e:ge:f         Z=de5d<   ede<ge<f         Z>de5d<   ee0e:gee0         f         Z?de5d<   eegef         Z@de5d<   ee9ge9f         ZAde5d<   eegee0         f         ZBde5d<   ee0e:ge:f         ZCde5d<   ee0e:ee:ge	e:         f         ge&e	e&         z  f         ZDde5d<   ee0eedz  gef         ZEde5d<   ee0eeege	e:         f         ge:e	e:         z  f         ZFde5d<   eeeege	ee:                  f         gee:         e	ee:                  z  f         ZGde5d<   dd lHmIZImJZJmKZKmLZLmMZMmNZNmOZOmPZPmQZQmRZRmSZSmTZTmUZUmVZV  ejW        d!          ZXd"e5d#<   d/d&ZYd0d(Z*d/d)ZZd/d*Z(d1d-Z,ej[        e!ef         Z\g d.Z]dS )2a  Python bindings for the NeMo Relay runtime.

This package exposes the runtime's scope stack, lifecycle events, middleware
registries, typed wrappers, and adaptive helpers from Python.

The main entry points are:

- ``nemo_relay.scope`` for creating and nesting scopes
- ``nemo_relay.tools`` for tool lifecycle management
- ``nemo_relay.llm`` for non-streaming and streaming LLM lifecycle management
- ``nemo_relay.guardrails`` and ``nemo_relay.intercepts`` for global middleware
- ``nemo_relay.scope_local`` for middleware scoped to a specific ``ScopeHandle``
- ``nemo_relay.typed`` for codec-based typed wrappers
- ``nemo_relay.plugin`` for global plugin configuration and custom plugin registration
- ``nemo_relay.adaptive`` for adaptive component configuration helpers
- ``nemo_relay.observability`` for observability component configuration helpers
- ``nemo_relay.pii_redaction`` for PII redaction component configuration helpers
- ``nemo_relay.model_pricing`` for model pricing component configuration helpers

Top-level exports also include:

- scope stack helpers such as ``get_scope_stack()``, ``create_scope_stack()``,
  ``set_thread_scope_stack()``, and ``scope_stack_active()``
- native runtime types such as ``ScopeHandle``, ``ToolHandle``, ``LLMHandle``,
  ``LLMRequest``, ``ScopeType``, and the lifecycle event classes
- observability helpers such as ``AtifExporter``, ``AtofExporter``,
  ``OpenTelemetrySubscriber``, and ``OpenInferenceSubscriber``
- JSON and callback type aliases used by middleware, typed wrappers, and
  plugin-facing configuration helpers

Example::

    import asyncio

    import nemo_relay

    def redact_args(tool_name, args):
        return {**args, "api_key": "***"}

    def add_header(
        name: str,
        request: nemo_relay.LLMRequest,
        annotated: nemo_relay.AnnotatedLLMRequest | None
    ) -> nemo_relay.LLMRequestInterceptOutcome:
        # The request object is immutable, however we can return a new instance with updated headers.
        headers = request.headers.copy()
        headers["Authorization"] = "Bearer test-token"
        return nemo_relay.LLMRequestInterceptOutcome(
            nemo_relay.LLMRequest(headers=headers, content=request.content), annotated
        )

    async def tool_impl(args):
        return {"echo": args["query"]}

    async def llm_impl(request):
        return {"messages": request.content["messages"], "ok": True}

    async def main():
        nemo_relay.guardrails.register_tool_sanitize_request("redact", 10, redact_args)
        nemo_relay.intercepts.register_llm_request("auth", 10, False, add_header)

        with nemo_relay.scope.scope("demo-agent", nemo_relay.ScopeType.Agent):
            tool_result = await nemo_relay.tools.execute("search", {"query": "hello"}, tool_impl)
            llm_result = await nemo_relay.llm.execute(
                "demo-model",
                nemo_relay.LLMRequest({}, {"messages": [{"role": "user", "content": "hi"}]}),
                llm_impl,
            )

            print(tool_result, llm_result)

    asyncio.run(main())
    )annotationsN)Callable)AsyncIterator	Awaitabler   LiteralOptional	TypeAlias	TypedDict)AnnotatedLLMRequestAnnotatedLLMResponseAtifExporterAtofExporterAtofExporterConfigAtofExporterModeAtofStreamSinkConfigLLMAttributes	LLMHandle
LLMRequestLLMRequestInterceptOutcome	MarkEventOpenInferenceConfigOpenInferenceSubscriberOpenTelemetryConfigOpenTelemetrySubscriberPendingMarkSpecScopeAttributes
ScopeEventScopeHandle
ScopeStack	ScopeTypeToolAttributesToolExecutionInterceptOutcome
ToolHandle)create_scope_stack)scope_stack_active)set_thread_scope_stack)sync_thread_scope_stackr	   JsonPrimitive	JsonValue
JsonObjectJson)ignorewarnerrorUnsupportedBehaviorc                  2    e Zd ZU dZded<   ded<   ded<   dS )EventSanitizeFieldszAObservability fields returned by mark and scope event sanitizers.zJson | NonedatazJsonObject | Nonecategory_profilemetadataN)__name__
__module____qualname____doc____annotations__     [/home/thesage/.hermes/hermes-agent/venv/lib/python3.11/site-packages/nemo_relay/__init__.pyr1   r1      s<         KK''''r;   r1   ToolSanitizeGuardrailEventEventSanitizeGuardrail!ToolConditionalExecutionGuardrailLlmSanitizeRequestGuardrailLlmSanitizeResponseGuardrail LlmConditionalExecutionGuardrailToolRequestInterceptToolExecutionInterceptLlmRequestInterceptLlmExecutionInterceptLlmStreamExecutionIntercept)adaptivecodecs
guardrails
interceptsllmmodel_pricingobservabilitypii_redactionpluginscopescope_localsubscriberstoolstypedscope_stackz"contextvars.ContextVar[ScopeStack]_scope_stack_varreturnr   c                     t                               d          } | (t                      } t                               |            t	          |            | S )a  Return the current task's active scope stack.

    If the current async context does not yet own a scope stack, this function
    creates one and synchronizes it into the Rust thread-local storage used by
    the native runtime. Most callers do not need to invoke this directly
    because higher-level helpers such as ``nemo_relay.scope.push()`` do it
    automatically.

    Returns:
        ScopeStack: The scope stack associated with the current Python context.

    Raises:
        Exception: Propagates any exception raised by native scope-stack
            creation or synchronization.

    Behavior:
        The function first checks the Python ``ContextVar``. If no stack is
        present, it creates one with the native runtime, stores it in the
        current context, and synchronizes that stack into native thread-local
        storage before returning.

    Notes:
        Calling this function synchronizes the Python ``ContextVar`` state into
        the native thread-local slot so subsequent native runtime calls observe
        the same scope hierarchy.

    Example::

        import nemo_relay

        stack = nemo_relay.get_scope_stack()
        assert stack is not None
    N)rX   get_create_scope_stackset_sync_thread_scope_stackstacks    r<   get_scope_stackra      sQ    D   &&E}#%%U### U###Lr;   boolc                 V    t                               d          dS t                      S )a  Report whether the current context already owns a scope stack.

    Returns:
        bool: ``True`` when the current Python context already has an active
        stack, either because it was created in this context or because a stack
        was explicitly installed for the current thread.

    Raises:
        Exception: Propagates any exception raised by the native active-stack
            status check.

    Behavior:
        The Python ``ContextVar`` is checked first. If it has no stack, the
        native runtime is asked whether the current thread has an explicitly
        active stack.

    Notes:
        This function does not create a scope stack. It is a pure status check
        used to decide whether scope propagation work is required.

    Example::

        import nemo_relay

        assert nemo_relay.scope_stack_active() is False
        nemo_relay.get_scope_stack()
        assert nemo_relay.scope_stack_active() is True
    NT)rX   r[   _native_scope_stack_activer:   r;   r<   r%   r%     s)    : D!!-t%'''r;   c                     t                      st          d          t                              d          } | | S t	                      S )a  Capture the active scope stack for use in another thread.

    The returned stack can be passed to ``set_thread_scope_stack()`` inside a
    worker thread so that the worker emits events into the same scope hierarchy
    as the parent context.

    Returns:
        ScopeStack: The active stack from the current context.

    Raises:
        RuntimeError: If the current context does not yet have an active scope
            stack to propagate.
        Exception: Propagates any exception raised while synchronizing an
            already-active native stack into the Python context.

    Behavior:
        This function does not clone the scope hierarchy. It shares the current
        stack reference with the target thread, which is appropriate when the
        worker should contribute events to the same logical trace.

    Example::

        from concurrent.futures import ThreadPoolExecutor

        import nemo_relay

        with nemo_relay.scope.scope("parent", nemo_relay.ScopeType.Agent) as handle:
            stack = nemo_relay.propagate_scope_to_thread()

            def worker() -> None:
                nemo_relay.set_thread_scope_stack(stack)
                nemo_relay.scope.event(
                    "worker-ran",
                    handle=handle,
                    data={"source": "thread"},
                    metadata={"thread": "pool-1"},
                )

            with ThreadPoolExecutor() as pool:
                pool.submit(worker).result()
    zlno active scope stack in current context; call nemo_relay.get_scope_stack() or nemo_relay.scope.push() firstN)r%   RuntimeErrorrX   r[   ra   r_   s    r<   propagate_scope_to_threadrg   -  sX    T  
/
 
 	
   &&E r;   c                     t                      S )a  Create a new isolated scope stack.

    Returns:
        ScopeStack: A fresh scope stack that is not yet attached to the current
        Python context or thread.

    Raises:
        Exception: Propagates any native error raised while allocating the
            stack.

    Behavior:
        This is a direct top-level wrapper around the native stack factory. It
        does not mutate the Python ``ContextVar`` and does not install the stack
        into native thread-local storage.

    Notes:
        Use this helper when you need explicit scope isolation, such as test
        fixtures, manual context propagation, or framework-managed request
        boundaries. Most application code should prefer ``get_scope_stack()``
        so the current context is initialized lazily.

    Example::

        import nemo_relay

        stack = nemo_relay.create_scope_stack()
        nemo_relay.set_thread_scope_stack(stack)
    )r\   r:   r;   r<   r$   r$   f  s    :    r;   r`   Nonec                $    t          |            dS )a  Install a scope stack into the current thread's native runtime context.

    Args:
        stack: Scope stack that should become active for subsequent NeMo Relay
            API calls on the current thread.

    Returns:
        None: This function returns after the native thread-local slot has been
        updated.

    Raises:
        Exception: Propagates native errors raised when installing ``stack``.

    Behavior:
        The supplied stack is installed into the native thread-local slot for
        the current OS thread. The function does not create, clone, or validate
        a Python ``ContextVar`` entry.

    Notes:
        This helper is primarily used when propagating an existing logical
        trace into worker threads. It does not create or clone a scope stack;
        it installs the supplied stack reference for the current thread.

    Example::

        from concurrent.futures import ThreadPoolExecutor

        import nemo_relay

        with nemo_relay.scope.scope("parent", nemo_relay.ScopeType.Agent):
            stack = nemo_relay.propagate_scope_to_thread()

            def worker() -> None:
                nemo_relay.set_thread_scope_stack(stack)

            with ThreadPoolExecutor() as pool:
                pool.submit(worker).result()
    N)_set_thread_scope_stackr_   s    r<   r&   r&     s    N E"""""r;   )>rR   rU   rM   rK   rL   rT   rS   rJ   rV   rQ   rI   rO   rP   rN   r   r$   ra   r%   rg   r&   r   r!   r   r    r   r   r   r#   r"   r   r   r   r   r>   r   r   r   r   r   r   r   r   r   r   r   r(   r)   r*   r+   r/   r1   r?   r=   r@   rA   rB   rC   rD   rE   rF   rG   rH   )rY   r   )rY   rb   )r`   r   rY   ri   )^r8   
__future__r   contextvarstypingcollections.abcr   AbcCallabler   r   r   r   r	   r
   nemo_relay._nativer   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r    r!   r"   r#   r$   r\   r%   rd   r&   rk   r'   r^   strintfloatrb   r(   r9   listdictr)   r*   r+   r/   r1   r=   r?   r@   rA   rB   rC   rD   rE   rF   rG   rH   
nemo_relayrI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   rU   rV   
ContextVarrX   ra   rg   Unionr>   __all__r:   r;   r<   <module>r{      s#  H H HT # " " " " "      3 3 3 3 3 3 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^                                                     6 I H H H H H O O O O O O P P P P P P R R R R R R
 9u,t3d: : : : : %tK'884[@P;QQ	 Q Q Q Q S)^,
 , , , ,      "))B!C  C C C C    )    $,S$K,=#>  > > > >$,g7J-KM`-`$a  a a a a 08dXc]8R/S ! S S S S *2:,
2J)K  K K K K +3J<3K*L  L L L L /7
|Xc]7R.S   S S S S #.sDk4.?"@  @ @ @ @ %-$$4012!I.K$LLN%      "**)D01 "      $,*h
|Yt_<=>9T?$      *2:,	-2E(FFGH$)M$$788:*                                    " 8N{7Mm7\7\  \ \ \ \* * * *Z( ( ( (D6 6 6 6r! ! ! !@'# '# '# '#Z 		
B B Br;   