"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""

from .basesdk import BaseSDK
from .httpclient import AsyncHttpClient, ClientOwner, HttpClient, close_clients
from .sdkconfiguration import SDKConfiguration
from .utils.logger import Logger, get_default_logger
from .utils.retries import RetryConfig
import httpx
import importlib
from opperai import errors, models, utils
from opperai._hooks import HookContext, SDKHooks
from opperai.types import OptionalNullable, UNSET
from opperai.utils import eventstreaming, get_security_from_env
from opperai.utils.unmarshal_json_response import unmarshal_json_response
import sys
from typing import (
    Any,
    Callable,
    Dict,
    List,
    Mapping,
    Optional,
    TYPE_CHECKING,
    Union,
    cast,
)
import weakref

if TYPE_CHECKING:
    from opperai.analytics import Analytics
    from opperai.datasets import Datasets
    from opperai.embeddings import Embeddings
    from opperai.functions import Functions
    from opperai.knowledge import Knowledge
    from opperai.language_models import LanguageModels
    from opperai.openai import Openai
    from opperai.rerank import Rerank
    from opperai.spanmetrics import SpanMetrics
    from opperai.spans import Spans
    from opperai.traces import Traces


class Opper(BaseSDK):
    knowledge: "Knowledge"
    traces: "Traces"
    spans: "Spans"
    span_metrics: "SpanMetrics"
    datasets: "Datasets"
    functions: "Functions"
    embeddings: "Embeddings"
    language_models: "LanguageModels"
    openai: "Openai"
    rerank: "Rerank"
    analytics: "Analytics"
    _sub_sdk_map = {
        "knowledge": ("opperai.knowledge", "Knowledge"),
        "traces": ("opperai.traces", "Traces"),
        "spans": ("opperai.spans", "Spans"),
        "span_metrics": ("opperai.spanmetrics", "SpanMetrics"),
        "datasets": ("opperai.datasets", "Datasets"),
        "functions": ("opperai.functions", "Functions"),
        "embeddings": ("opperai.embeddings", "Embeddings"),
        "language_models": ("opperai.language_models", "LanguageModels"),
        "openai": ("opperai.openai", "Openai"),
        "rerank": ("opperai.rerank", "Rerank"),
        "analytics": ("opperai.analytics", "Analytics"),
    }

    def __init__(
        self,
        http_bearer: Optional[Union[Optional[str], Callable[[], Optional[str]]]] = None,
        server_idx: Optional[int] = None,
        server_url: Optional[str] = None,
        url_params: Optional[Dict[str, str]] = None,
        client: Optional[HttpClient] = None,
        async_client: Optional[AsyncHttpClient] = None,
        retry_config: OptionalNullable[RetryConfig] = UNSET,
        timeout_ms: Optional[int] = None,
        debug_logger: Optional[Logger] = None,
    ) -> None:
        r"""Instantiates the SDK configuring it with the provided parameters.

        :param http_bearer: The http_bearer required for authentication
        :param server_idx: The index of the server to use for all methods
        :param server_url: The server URL to use for all methods
        :param url_params: Parameters to optionally template the server URL with
        :param client: The HTTP client to use for all synchronous methods
        :param async_client: The Async HTTP client to use for all asynchronous methods
        :param retry_config: The retry configuration to use for all supported methods
        :param timeout_ms: Optional request timeout applied to each operation in milliseconds
        """
        client_supplied = True
        if client is None:
            client = httpx.Client(follow_redirects=True)
            client_supplied = False

        assert issubclass(
            type(client), HttpClient
        ), "The provided client must implement the HttpClient protocol."

        async_client_supplied = True
        if async_client is None:
            async_client = httpx.AsyncClient(follow_redirects=True)
            async_client_supplied = False

        if debug_logger is None:
            debug_logger = get_default_logger()

        assert issubclass(
            type(async_client), AsyncHttpClient
        ), "The provided async_client must implement the AsyncHttpClient protocol."

        security: Any = None
        if callable(http_bearer):
            # pylint: disable=unnecessary-lambda-assignment
            security = lambda: models.Security(http_bearer=http_bearer())
        else:
            security = models.Security(http_bearer=http_bearer)

        if server_url is not None:
            if url_params is not None:
                server_url = utils.template_url(server_url, url_params)

        BaseSDK.__init__(
            self,
            SDKConfiguration(
                client=client,
                client_supplied=client_supplied,
                async_client=async_client,
                async_client_supplied=async_client_supplied,
                security=security,
                server_url=server_url,
                server_idx=server_idx,
                retry_config=retry_config,
                timeout_ms=timeout_ms,
                debug_logger=debug_logger,
            ),
            parent_ref=self,
        )

        hooks = SDKHooks()

        # pylint: disable=protected-access
        self.sdk_configuration.__dict__["_hooks"] = hooks

        self.sdk_configuration = hooks.sdk_init(self.sdk_configuration)

        weakref.finalize(
            self,
            close_clients,
            cast(ClientOwner, self.sdk_configuration),
            self.sdk_configuration.client,
            self.sdk_configuration.client_supplied,
            self.sdk_configuration.async_client,
            self.sdk_configuration.async_client_supplied,
        )

    def dynamic_import(self, modname, retries=3):
        for attempt in range(retries):
            try:
                return importlib.import_module(modname)
            except KeyError:
                # Clear any half-initialized module and retry
                sys.modules.pop(modname, None)
                if attempt == retries - 1:
                    break
        raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")

    def __getattr__(self, name: str):
        if name in self._sub_sdk_map:
            module_path, class_name = self._sub_sdk_map[name]
            try:
                module = self.dynamic_import(module_path)
                klass = getattr(module, class_name)
                instance = klass(self.sdk_configuration, parent_ref=self)
                setattr(self, name, instance)
                return instance
            except ImportError as e:
                raise AttributeError(
                    f"Failed to import module {module_path} for attribute {name}: {e}"
                ) from e
            except AttributeError as e:
                raise AttributeError(
                    f"Failed to find class {class_name} in module {module_path} for attribute {name}: {e}"
                ) from e

        raise AttributeError(
            f"'{type(self).__name__}' object has no attribute '{name}'"
        )

    def __dir__(self):
        default_attrs = list(super().__dir__())
        lazy_attrs = list(self._sub_sdk_map.keys())
        return sorted(list(set(default_attrs + lazy_attrs)))

    def __enter__(self):
        return self

    async def __aenter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if (
            self.sdk_configuration.client is not None
            and not self.sdk_configuration.client_supplied
        ):
            self.sdk_configuration.client.close()
        self.sdk_configuration.client = None

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if (
            self.sdk_configuration.async_client is not None
            and not self.sdk_configuration.async_client_supplied
        ):
            await self.sdk_configuration.async_client.aclose()
        self.sdk_configuration.async_client = None

    def call(
        self,
        *,
        name: str,
        instructions: OptionalNullable[str] = UNSET,
        input_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        output_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        input: OptionalNullable[Any] = UNSET,
        model: Optional[Union[models.TModel, models.TModelTypedDict]] = None,
        examples: OptionalNullable[
            Union[List[models.Example], List[models.ExampleTypedDict]]
        ] = UNSET,
        parent_span_id: OptionalNullable[str] = UNSET,
        tags: OptionalNullable[Dict[str, str]] = UNSET,
        configuration: OptionalNullable[
            Union[
                models.FunctionCallConfigurationInput,
                models.FunctionCallConfigurationInputTypedDict,
            ]
        ] = UNSET,
        retries: OptionalNullable[utils.RetryConfig] = UNSET,
        server_url: Optional[str] = None,
        timeout_ms: Optional[int] = None,
        http_headers: Optional[Mapping[str, str]] = None,
    ) -> models.AppAPIPublicV2FunctionCallCallFunctionResponse:
        r"""Function Call

        The Call endpoint is a simple interface to issue a task to an LLM.
        It is a declarative interface with input and output schemas that supports text, image, audio inputs and outputs and is highly model agnostic.

        :param name: Provide a unique name of the task. A function with this name will be created in the project. Functions configuration is overridden by the request parameters.
        :param instructions: Optionally provide an instruction for the model to complete the task. Recommended to be concise and to the point
        :param input_schema: Optionally provide an input schema for the task. Can preferably include field descriptions to allow the model to reason about the input variables. Schema is validated against the input data and issues an error if it does not match. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.
        :param output_schema: Optionally provide an output schema for the task. Response is guaranteed to match the schema or throw an error. Can preferably include field descriptions to allow the model to reason about the output variables. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.

            **Streaming with output_schema:** When used with streaming endpoints, enables precise field tracking via json_path. Each streaming chunk includes the exact schema field being populated (e.g., 'response.people[0].name'), allowing real-time UI updates by routing content to specific components.
        :param input: Optionally provide input data as context to complete the task. Could be a text, image, audio or a combination of these.
        :param model:
        :param examples: Optionally provide examples of successful task completions. Will be added to the prompt to help the model understand the task from examples.
        :param parent_span_id: Optionally provide the parent span ID to add to the call event. This will automatically tie the call to a parent span in the UI.
        :param tags: Optionally provide a list of tags to add to the call event. Useful for being able to understand aggregate analytics on some dimension.
        :param configuration: Optional configuration for the function.Configuration is a dictionary of key-value pairs that can be used to override the default configuration for the function.
        :param retries: Override the default retry configuration for this method
        :param server_url: Override the default server URL for this method
        :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
        :param http_headers: Additional headers to set or replace on requests.
        """
        base_url = None
        url_variables = None
        if timeout_ms is None:
            timeout_ms = self.sdk_configuration.timeout_ms

        if server_url is not None:
            base_url = server_url
        else:
            base_url = self._get_url(base_url, url_variables)

        # region convert-pydantic-schemas
        if input_schema is not UNSET and hasattr(input_schema, 'model_json_schema'):
            input_schema = input_schema.model_json_schema()
        if output_schema is not UNSET and hasattr(output_schema, 'model_json_schema'):
            output_schema = output_schema.model_json_schema()
        # endregion convert-pydantic-schemas

        request = models.AppAPIPublicV2FunctionCallCallFunctionRequest(
            name=name,
            instructions=instructions,
            input_schema=input_schema,
            output_schema=output_schema,
            input=input,
            model=utils.get_pydantic_model(model, Optional[models.TModel]),
            examples=utils.get_pydantic_model(
                examples, OptionalNullable[List[models.Example]]
            ),
            parent_span_id=parent_span_id,
            tags=tags,
            configuration=utils.get_pydantic_model(
                configuration, OptionalNullable[models.FunctionCallConfigurationInput]
            ),
        )

        req = self._build_request(
            method="POST",
            path="/call",
            base_url=base_url,
            url_variables=url_variables,
            request=request,
            request_body_required=True,
            request_has_path_params=False,
            request_has_query_params=True,
            user_agent_header="user-agent",
            accept_header_value="application/json",
            http_headers=http_headers,
            security=self.sdk_configuration.security,
            get_serialized_body=lambda: utils.serialize_request_body(
                request,
                False,
                False,
                "json",
                models.AppAPIPublicV2FunctionCallCallFunctionRequest,
            ),
            allow_empty_value=None,
            timeout_ms=timeout_ms,
        )

        if retries == UNSET:
            if self.sdk_configuration.retry_config is not UNSET:
                retries = self.sdk_configuration.retry_config

        retry_config = None
        if isinstance(retries, utils.RetryConfig):
            retry_config = (retries, ["429", "500", "502", "503", "504"])

        http_res = self.do_request(
            hook_ctx=HookContext(
                config=self.sdk_configuration,
                base_url=base_url or "",
                operation_id="function_call_call_post",
                oauth2_scopes=None,
                security_source=get_security_from_env(
                    self.sdk_configuration.security, models.Security
                ),
            ),
            request=req,
            error_status_codes=["400", "401", "404", "422", "4XX", "5XX"],
            retry_config=retry_config,
        )

        response_data: Any = None
        if utils.match_response(http_res, "200", "application/json"):
            return unmarshal_json_response(
                models.AppAPIPublicV2FunctionCallCallFunctionResponse, http_res
            )
        if utils.match_response(http_res, "400", "application/json"):
            response_data = unmarshal_json_response(
                errors.BadRequestErrorData, http_res
            )
            raise errors.BadRequestError(response_data, http_res)
        if utils.match_response(http_res, "401", "application/json"):
            response_data = unmarshal_json_response(
                errors.UnauthorizedErrorData, http_res
            )
            raise errors.UnauthorizedError(response_data, http_res)
        if utils.match_response(http_res, "404", "application/json"):
            response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res)
            raise errors.NotFoundError(response_data, http_res)
        if utils.match_response(http_res, "422", "application/json"):
            response_data = unmarshal_json_response(
                errors.RequestValidationErrorData, http_res
            )
            raise errors.RequestValidationError(response_data, http_res)
        if utils.match_response(http_res, "4XX", "*"):
            http_res_text = utils.stream_to_text(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)
        if utils.match_response(http_res, "5XX", "*"):
            http_res_text = utils.stream_to_text(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)

        raise errors.APIError("Unexpected response received", http_res)

    async def call_async(
        self,
        *,
        name: str,
        instructions: OptionalNullable[str] = UNSET,
        input_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        output_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        input: OptionalNullable[Any] = UNSET,
        model: Optional[Union[models.TModel, models.TModelTypedDict]] = None,
        examples: OptionalNullable[
            Union[List[models.Example], List[models.ExampleTypedDict]]
        ] = UNSET,
        parent_span_id: OptionalNullable[str] = UNSET,
        tags: OptionalNullable[Dict[str, str]] = UNSET,
        configuration: OptionalNullable[
            Union[
                models.FunctionCallConfigurationInput,
                models.FunctionCallConfigurationInputTypedDict,
            ]
        ] = UNSET,
        retries: OptionalNullable[utils.RetryConfig] = UNSET,
        server_url: Optional[str] = None,
        timeout_ms: Optional[int] = None,
        http_headers: Optional[Mapping[str, str]] = None,
    ) -> models.AppAPIPublicV2FunctionCallCallFunctionResponse:
        r"""Function Call

        The Call endpoint is a simple interface to issue a task to an LLM.
        It is a declarative interface with input and output schemas that supports text, image, audio inputs and outputs and is highly model agnostic.

        :param name: Provide a unique name of the task. A function with this name will be created in the project. Functions configuration is overridden by the request parameters.
        :param instructions: Optionally provide an instruction for the model to complete the task. Recommended to be concise and to the point
        :param input_schema: Optionally provide an input schema for the task. Can preferably include field descriptions to allow the model to reason about the input variables. Schema is validated against the input data and issues an error if it does not match. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.
        :param output_schema: Optionally provide an output schema for the task. Response is guaranteed to match the schema or throw an error. Can preferably include field descriptions to allow the model to reason about the output variables. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.

            **Streaming with output_schema:** When used with streaming endpoints, enables precise field tracking via json_path. Each streaming chunk includes the exact schema field being populated (e.g., 'response.people[0].name'), allowing real-time UI updates by routing content to specific components.
        :param input: Optionally provide input data as context to complete the task. Could be a text, image, audio or a combination of these.
        :param model:
        :param examples: Optionally provide examples of successful task completions. Will be added to the prompt to help the model understand the task from examples.
        :param parent_span_id: Optionally provide the parent span ID to add to the call event. This will automatically tie the call to a parent span in the UI.
        :param tags: Optionally provide a list of tags to add to the call event. Useful for being able to understand aggregate analytics on some dimension.
        :param configuration: Optional configuration for the function.Configuration is a dictionary of key-value pairs that can be used to override the default configuration for the function.
        :param retries: Override the default retry configuration for this method
        :param server_url: Override the default server URL for this method
        :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
        :param http_headers: Additional headers to set or replace on requests.
        """
        base_url = None
        url_variables = None
        if timeout_ms is None:
            timeout_ms = self.sdk_configuration.timeout_ms

        if server_url is not None:
            base_url = server_url
        else:
            base_url = self._get_url(base_url, url_variables)

        # region convert-pydantic-schemas
        if input_schema is not UNSET and hasattr(input_schema, 'model_json_schema'):
            input_schema = input_schema.model_json_schema()
        if output_schema is not UNSET and hasattr(output_schema, 'model_json_schema'):
            output_schema = output_schema.model_json_schema()
        # endregion convert-pydantic-schemas

        request = models.AppAPIPublicV2FunctionCallCallFunctionRequest(
            name=name,
            instructions=instructions,
            input_schema=input_schema,
            output_schema=output_schema,
            input=input,
            model=utils.get_pydantic_model(model, Optional[models.TModel]),
            examples=utils.get_pydantic_model(
                examples, OptionalNullable[List[models.Example]]
            ),
            parent_span_id=parent_span_id,
            tags=tags,
            configuration=utils.get_pydantic_model(
                configuration, OptionalNullable[models.FunctionCallConfigurationInput]
            ),
        )

        req = self._build_request_async(
            method="POST",
            path="/call",
            base_url=base_url,
            url_variables=url_variables,
            request=request,
            request_body_required=True,
            request_has_path_params=False,
            request_has_query_params=True,
            user_agent_header="user-agent",
            accept_header_value="application/json",
            http_headers=http_headers,
            security=self.sdk_configuration.security,
            get_serialized_body=lambda: utils.serialize_request_body(
                request,
                False,
                False,
                "json",
                models.AppAPIPublicV2FunctionCallCallFunctionRequest,
            ),
            allow_empty_value=None,
            timeout_ms=timeout_ms,
        )

        if retries == UNSET:
            if self.sdk_configuration.retry_config is not UNSET:
                retries = self.sdk_configuration.retry_config

        retry_config = None
        if isinstance(retries, utils.RetryConfig):
            retry_config = (retries, ["429", "500", "502", "503", "504"])

        http_res = await self.do_request_async(
            hook_ctx=HookContext(
                config=self.sdk_configuration,
                base_url=base_url or "",
                operation_id="function_call_call_post",
                oauth2_scopes=None,
                security_source=get_security_from_env(
                    self.sdk_configuration.security, models.Security
                ),
            ),
            request=req,
            error_status_codes=["400", "401", "404", "422", "4XX", "5XX"],
            retry_config=retry_config,
        )

        response_data: Any = None
        if utils.match_response(http_res, "200", "application/json"):
            return unmarshal_json_response(
                models.AppAPIPublicV2FunctionCallCallFunctionResponse, http_res
            )
        if utils.match_response(http_res, "400", "application/json"):
            response_data = unmarshal_json_response(
                errors.BadRequestErrorData, http_res
            )
            raise errors.BadRequestError(response_data, http_res)
        if utils.match_response(http_res, "401", "application/json"):
            response_data = unmarshal_json_response(
                errors.UnauthorizedErrorData, http_res
            )
            raise errors.UnauthorizedError(response_data, http_res)
        if utils.match_response(http_res, "404", "application/json"):
            response_data = unmarshal_json_response(errors.NotFoundErrorData, http_res)
            raise errors.NotFoundError(response_data, http_res)
        if utils.match_response(http_res, "422", "application/json"):
            response_data = unmarshal_json_response(
                errors.RequestValidationErrorData, http_res
            )
            raise errors.RequestValidationError(response_data, http_res)
        if utils.match_response(http_res, "4XX", "*"):
            http_res_text = await utils.stream_to_text_async(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)
        if utils.match_response(http_res, "5XX", "*"):
            http_res_text = await utils.stream_to_text_async(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)

        raise errors.APIError("Unexpected response received", http_res)

    def stream(
        self,
        *,
        name: str,
        instructions: OptionalNullable[str] = UNSET,
        input_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        output_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        input: OptionalNullable[Any] = UNSET,
        model: Optional[Union[models.TModel, models.TModelTypedDict]] = None,
        examples: OptionalNullable[
            Union[List[models.Example], List[models.ExampleTypedDict]]
        ] = UNSET,
        parent_span_id: OptionalNullable[str] = UNSET,
        tags: OptionalNullable[Dict[str, str]] = UNSET,
        configuration: OptionalNullable[
            Union[
                models.FunctionCallConfigurationInput,
                models.FunctionCallConfigurationInputTypedDict,
            ]
        ] = UNSET,
        retries: OptionalNullable[utils.RetryConfig] = UNSET,
        server_url: Optional[str] = None,
        timeout_ms: Optional[int] = None,
        http_headers: Optional[Mapping[str, str]] = None,
    ) -> models.FunctionStreamCallStreamPostResponse:
        r"""Function Stream

        Stream a function call execution in real-time using Server-Sent Events (SSE).

        This endpoint provides continuous streaming of function execution results, supporting both
        unstructured text streaming and structured JSON streaming with precise field tracking.

        ## Streaming Modes

        **Text Mode (no output_schema):**
        - Streams incremental text content via the `delta` field
        - `chunk_type` will be \"text\"
        - Best for conversational AI, creative writing, open-ended responses

        **Structured Mode (with output_schema):**
        - Streams structured JSON with precise field tracking via `json_path`
        - `chunk_type` will be \"json\"
        - Enables real-time UI updates by showing which schema field is being populated
        - Perfect for forms, dashboards, structured data display

        ## JSON Path Feature

        When using `output_schema`, each streaming chunk includes a `json_path` field showing exactly
        which field in your schema is being populated:

        - `response.summary` → Top-level string field
        - `response.people[0].name` → Name of first person in array
        - `response.people[1].role` → Role of second person
        - `response.metadata.created_at` → Nested object field

        This enables precise UI updates where you can route streaming content to specific components
        based on the path, creating responsive real-time interfaces.

        ## Response Structure

        Each Server-Sent Event contains:
        - `id`: Optional event identifier
        - `event`: Optional event type (typically \"message\")
        - `data`: StreamingChunk with the actual streaming content
        - `retry`: Optional retry interval for reconnection

        The StreamingChunk data payload varies by mode:

        **Text Mode:**
        - `delta`: Incremental text content
        - `span_id`: Execution span ID (first chunk)
        - `chunk_type`: \"text\"

        **Structured Mode:**
        - `delta`: Actual field values being streamed
        - `json_path`: Dot-notation path to current field
        - `span_id`: Execution span ID (first chunk)
        - `chunk_type`: \"json\"

        ## Examples

        Text streaming events:
        ```
        data: {\"span_id\": \"123e4567-e89b-12d3-a456-426614174000\"}
        data: {\"delta\": \"Hello\", \"chunk_type\": \"text\"}
        data: {\"delta\": \" world\", \"chunk_type\": \"text\"}
        ```

        Structured streaming events:
        ```
        data: {\"span_id\": \"123e4567-e89b-12d3-a456-426614174000\"}
        data: {\"delta\": \"John\", \"json_path\": \"response.name\", \"chunk_type\": \"json\"}
        data: {\"delta\": \" Doe\", \"json_path\": \"response.name\", \"chunk_type\": \"json\"}
        data: {\"delta\": \"Engineer\", \"json_path\": \"response.role\", \"chunk_type\": \"json\"}
        ```

        :param name: Provide a unique name of the task. A function with this name will be created in the project. Functions configuration is overridden by the request parameters.
        :param instructions: Optionally provide an instruction for the model to complete the task. Recommended to be concise and to the point
        :param input_schema: Optionally provide an input schema for the task. Can preferably include field descriptions to allow the model to reason about the input variables. Schema is validated against the input data and issues an error if it does not match. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.
        :param output_schema: Optionally provide an output schema for the task. Response is guaranteed to match the schema or throw an error. Can preferably include field descriptions to allow the model to reason about the output variables. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.

            **Streaming with output_schema:** When used with streaming endpoints, enables precise field tracking via json_path. Each streaming chunk includes the exact schema field being populated (e.g., 'response.people[0].name'), allowing real-time UI updates by routing content to specific components.
        :param input: Optionally provide input data as context to complete the task. Could be a text, image, audio or a combination of these.
        :param model:
        :param examples: Optionally provide examples of successful task completions. Will be added to the prompt to help the model understand the task from examples.
        :param parent_span_id: Optionally provide the parent span ID to add to the call event. This will automatically tie the call to a parent span in the UI.
        :param tags: Optionally provide a list of tags to add to the call event. Useful for being able to understand aggregate analytics on some dimension.
        :param configuration: Optional configuration for the function.Configuration is a dictionary of key-value pairs that can be used to override the default configuration for the function.
        :param retries: Override the default retry configuration for this method
        :param server_url: Override the default server URL for this method
        :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
        :param http_headers: Additional headers to set or replace on requests.
        """
        base_url = None
        url_variables = None
        if timeout_ms is None:
            timeout_ms = self.sdk_configuration.timeout_ms

        if server_url is not None:
            base_url = server_url
        else:
            base_url = self._get_url(base_url, url_variables)

        # region convert-pydantic-schemas
        if input_schema is not UNSET and hasattr(input_schema, 'model_json_schema'):
            input_schema = input_schema.model_json_schema()
        if output_schema is not UNSET and hasattr(output_schema, 'model_json_schema'):
            output_schema = output_schema.model_json_schema()
        # endregion convert-pydantic-schemas

        request = models.AppAPIPublicV2FunctionCallCallFunctionRequest(
            name=name,
            instructions=instructions,
            input_schema=input_schema,
            output_schema=output_schema,
            input=input,
            model=utils.get_pydantic_model(model, Optional[models.TModel]),
            examples=utils.get_pydantic_model(
                examples, OptionalNullable[List[models.Example]]
            ),
            parent_span_id=parent_span_id,
            tags=tags,
            configuration=utils.get_pydantic_model(
                configuration, OptionalNullable[models.FunctionCallConfigurationInput]
            ),
        )

        req = self._build_request(
            method="POST",
            path="/call/stream",
            base_url=base_url,
            url_variables=url_variables,
            request=request,
            request_body_required=True,
            request_has_path_params=False,
            request_has_query_params=True,
            user_agent_header="user-agent",
            accept_header_value="text/event-stream",
            http_headers=http_headers,
            security=self.sdk_configuration.security,
            get_serialized_body=lambda: utils.serialize_request_body(
                request,
                False,
                False,
                "json",
                models.AppAPIPublicV2FunctionCallCallFunctionRequest,
            ),
            allow_empty_value=None,
            timeout_ms=timeout_ms,
        )

        if retries == UNSET:
            if self.sdk_configuration.retry_config is not UNSET:
                retries = self.sdk_configuration.retry_config

        retry_config = None
        if isinstance(retries, utils.RetryConfig):
            retry_config = (retries, ["429", "500", "502", "503", "504"])

        http_res = self.do_request(
            hook_ctx=HookContext(
                config=self.sdk_configuration,
                base_url=base_url or "",
                operation_id="function_stream_call_stream_post",
                oauth2_scopes=None,
                security_source=get_security_from_env(
                    self.sdk_configuration.security, models.Security
                ),
            ),
            request=req,
            error_status_codes=["400", "401", "404", "422", "4XX", "5XX"],
            stream=True,
            retry_config=retry_config,
        )

        response_data: Any = None
        if utils.match_response(http_res, "200", "text/event-stream"):
            return models.FunctionStreamCallStreamPostResponse(
                result=eventstreaming.EventStream(
                    http_res,
                    lambda raw: utils.unmarshal_json(
                        raw, models.FunctionStreamCallStreamPostResponseBody
                    ),
                    client_ref=self,
                ),
                headers=utils.get_response_headers(http_res.headers),
            )
        if utils.match_response(http_res, "400", "application/json"):
            http_res_text = utils.stream_to_text(http_res)
            response_data = unmarshal_json_response(
                errors.BadRequestErrorData, http_res, http_res_text
            )
            raise errors.BadRequestError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "401", "application/json"):
            http_res_text = utils.stream_to_text(http_res)
            response_data = unmarshal_json_response(
                errors.UnauthorizedErrorData, http_res, http_res_text
            )
            raise errors.UnauthorizedError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "404", "application/json"):
            http_res_text = utils.stream_to_text(http_res)
            response_data = unmarshal_json_response(
                errors.NotFoundErrorData, http_res, http_res_text
            )
            raise errors.NotFoundError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "422", "application/json"):
            http_res_text = utils.stream_to_text(http_res)
            response_data = unmarshal_json_response(
                errors.RequestValidationErrorData, http_res, http_res_text
            )
            raise errors.RequestValidationError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "4XX", "*"):
            http_res_text = utils.stream_to_text(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)
        if utils.match_response(http_res, "5XX", "*"):
            http_res_text = utils.stream_to_text(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)

        http_res_text = utils.stream_to_text(http_res)
        raise errors.APIError("Unexpected response received", http_res, http_res_text)

    async def stream_async(
        self,
        *,
        name: str,
        instructions: OptionalNullable[str] = UNSET,
        input_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        output_schema: OptionalNullable[Dict[str, Any]] = UNSET,
        input: OptionalNullable[Any] = UNSET,
        model: Optional[Union[models.TModel, models.TModelTypedDict]] = None,
        examples: OptionalNullable[
            Union[List[models.Example], List[models.ExampleTypedDict]]
        ] = UNSET,
        parent_span_id: OptionalNullable[str] = UNSET,
        tags: OptionalNullable[Dict[str, str]] = UNSET,
        configuration: OptionalNullable[
            Union[
                models.FunctionCallConfigurationInput,
                models.FunctionCallConfigurationInputTypedDict,
            ]
        ] = UNSET,
        retries: OptionalNullable[utils.RetryConfig] = UNSET,
        server_url: Optional[str] = None,
        timeout_ms: Optional[int] = None,
        http_headers: Optional[Mapping[str, str]] = None,
    ) -> models.FunctionStreamCallStreamPostResponse:
        r"""Function Stream

        Stream a function call execution in real-time using Server-Sent Events (SSE).

        This endpoint provides continuous streaming of function execution results, supporting both
        unstructured text streaming and structured JSON streaming with precise field tracking.

        ## Streaming Modes

        **Text Mode (no output_schema):**
        - Streams incremental text content via the `delta` field
        - `chunk_type` will be \"text\"
        - Best for conversational AI, creative writing, open-ended responses

        **Structured Mode (with output_schema):**
        - Streams structured JSON with precise field tracking via `json_path`
        - `chunk_type` will be \"json\"
        - Enables real-time UI updates by showing which schema field is being populated
        - Perfect for forms, dashboards, structured data display

        ## JSON Path Feature

        When using `output_schema`, each streaming chunk includes a `json_path` field showing exactly
        which field in your schema is being populated:

        - `response.summary` → Top-level string field
        - `response.people[0].name` → Name of first person in array
        - `response.people[1].role` → Role of second person
        - `response.metadata.created_at` → Nested object field

        This enables precise UI updates where you can route streaming content to specific components
        based on the path, creating responsive real-time interfaces.

        ## Response Structure

        Each Server-Sent Event contains:
        - `id`: Optional event identifier
        - `event`: Optional event type (typically \"message\")
        - `data`: StreamingChunk with the actual streaming content
        - `retry`: Optional retry interval for reconnection

        The StreamingChunk data payload varies by mode:

        **Text Mode:**
        - `delta`: Incremental text content
        - `span_id`: Execution span ID (first chunk)
        - `chunk_type`: \"text\"

        **Structured Mode:**
        - `delta`: Actual field values being streamed
        - `json_path`: Dot-notation path to current field
        - `span_id`: Execution span ID (first chunk)
        - `chunk_type`: \"json\"

        ## Examples

        Text streaming events:
        ```
        data: {\"span_id\": \"123e4567-e89b-12d3-a456-426614174000\"}
        data: {\"delta\": \"Hello\", \"chunk_type\": \"text\"}
        data: {\"delta\": \" world\", \"chunk_type\": \"text\"}
        ```

        Structured streaming events:
        ```
        data: {\"span_id\": \"123e4567-e89b-12d3-a456-426614174000\"}
        data: {\"delta\": \"John\", \"json_path\": \"response.name\", \"chunk_type\": \"json\"}
        data: {\"delta\": \" Doe\", \"json_path\": \"response.name\", \"chunk_type\": \"json\"}
        data: {\"delta\": \"Engineer\", \"json_path\": \"response.role\", \"chunk_type\": \"json\"}
        ```

        :param name: Provide a unique name of the task. A function with this name will be created in the project. Functions configuration is overridden by the request parameters.
        :param instructions: Optionally provide an instruction for the model to complete the task. Recommended to be concise and to the point
        :param input_schema: Optionally provide an input schema for the task. Can preferably include field descriptions to allow the model to reason about the input variables. Schema is validated against the input data and issues an error if it does not match. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.
        :param output_schema: Optionally provide an output schema for the task. Response is guaranteed to match the schema or throw an error. Can preferably include field descriptions to allow the model to reason about the output variables. With the Opper SDKs you can define these schemas through libraries like Pydantic and Zod. For schemas with definitions, prefer using '$defs' and '#/$defs/...' references.

            **Streaming with output_schema:** When used with streaming endpoints, enables precise field tracking via json_path. Each streaming chunk includes the exact schema field being populated (e.g., 'response.people[0].name'), allowing real-time UI updates by routing content to specific components.
        :param input: Optionally provide input data as context to complete the task. Could be a text, image, audio or a combination of these.
        :param model:
        :param examples: Optionally provide examples of successful task completions. Will be added to the prompt to help the model understand the task from examples.
        :param parent_span_id: Optionally provide the parent span ID to add to the call event. This will automatically tie the call to a parent span in the UI.
        :param tags: Optionally provide a list of tags to add to the call event. Useful for being able to understand aggregate analytics on some dimension.
        :param configuration: Optional configuration for the function.Configuration is a dictionary of key-value pairs that can be used to override the default configuration for the function.
        :param retries: Override the default retry configuration for this method
        :param server_url: Override the default server URL for this method
        :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
        :param http_headers: Additional headers to set or replace on requests.
        """
        base_url = None
        url_variables = None
        if timeout_ms is None:
            timeout_ms = self.sdk_configuration.timeout_ms

        if server_url is not None:
            base_url = server_url
        else:
            base_url = self._get_url(base_url, url_variables)

        # region convert-pydantic-schemas
        if input_schema is not UNSET and hasattr(input_schema, 'model_json_schema'):
            input_schema = input_schema.model_json_schema()
        if output_schema is not UNSET and hasattr(output_schema, 'model_json_schema'):
            output_schema = output_schema.model_json_schema()
        # endregion convert-pydantic-schemas

        request = models.AppAPIPublicV2FunctionCallCallFunctionRequest(
            name=name,
            instructions=instructions,
            input_schema=input_schema,
            output_schema=output_schema,
            input=input,
            model=utils.get_pydantic_model(model, Optional[models.TModel]),
            examples=utils.get_pydantic_model(
                examples, OptionalNullable[List[models.Example]]
            ),
            parent_span_id=parent_span_id,
            tags=tags,
            configuration=utils.get_pydantic_model(
                configuration, OptionalNullable[models.FunctionCallConfigurationInput]
            ),
        )

        req = self._build_request_async(
            method="POST",
            path="/call/stream",
            base_url=base_url,
            url_variables=url_variables,
            request=request,
            request_body_required=True,
            request_has_path_params=False,
            request_has_query_params=True,
            user_agent_header="user-agent",
            accept_header_value="text/event-stream",
            http_headers=http_headers,
            security=self.sdk_configuration.security,
            get_serialized_body=lambda: utils.serialize_request_body(
                request,
                False,
                False,
                "json",
                models.AppAPIPublicV2FunctionCallCallFunctionRequest,
            ),
            allow_empty_value=None,
            timeout_ms=timeout_ms,
        )

        if retries == UNSET:
            if self.sdk_configuration.retry_config is not UNSET:
                retries = self.sdk_configuration.retry_config

        retry_config = None
        if isinstance(retries, utils.RetryConfig):
            retry_config = (retries, ["429", "500", "502", "503", "504"])

        http_res = await self.do_request_async(
            hook_ctx=HookContext(
                config=self.sdk_configuration,
                base_url=base_url or "",
                operation_id="function_stream_call_stream_post",
                oauth2_scopes=None,
                security_source=get_security_from_env(
                    self.sdk_configuration.security, models.Security
                ),
            ),
            request=req,
            error_status_codes=["400", "401", "404", "422", "4XX", "5XX"],
            stream=True,
            retry_config=retry_config,
        )

        response_data: Any = None
        if utils.match_response(http_res, "200", "text/event-stream"):
            return models.FunctionStreamCallStreamPostResponse(
                result=eventstreaming.EventStreamAsync(
                    http_res,
                    lambda raw: utils.unmarshal_json(
                        raw, models.FunctionStreamCallStreamPostResponseBody
                    ),
                    client_ref=self,
                ),
                headers=utils.get_response_headers(http_res.headers),
            )
        if utils.match_response(http_res, "400", "application/json"):
            http_res_text = await utils.stream_to_text_async(http_res)
            response_data = unmarshal_json_response(
                errors.BadRequestErrorData, http_res, http_res_text
            )
            raise errors.BadRequestError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "401", "application/json"):
            http_res_text = await utils.stream_to_text_async(http_res)
            response_data = unmarshal_json_response(
                errors.UnauthorizedErrorData, http_res, http_res_text
            )
            raise errors.UnauthorizedError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "404", "application/json"):
            http_res_text = await utils.stream_to_text_async(http_res)
            response_data = unmarshal_json_response(
                errors.NotFoundErrorData, http_res, http_res_text
            )
            raise errors.NotFoundError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "422", "application/json"):
            http_res_text = await utils.stream_to_text_async(http_res)
            response_data = unmarshal_json_response(
                errors.RequestValidationErrorData, http_res, http_res_text
            )
            raise errors.RequestValidationError(response_data, http_res, http_res_text)
        if utils.match_response(http_res, "4XX", "*"):
            http_res_text = await utils.stream_to_text_async(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)
        if utils.match_response(http_res, "5XX", "*"):
            http_res_text = await utils.stream_to_text_async(http_res)
            raise errors.APIError("API error occurred", http_res, http_res_text)

        http_res_text = await utils.stream_to_text_async(http_res)
        raise errors.APIError("Unexpected response received", http_res, http_res_text)
