Skip to main content
Glama

Jentic

Official
by jentic
models.py17.1 kB
from typing import Any, Dict, cast from pydantic import BaseModel, Field, model_validator # Represents a reference to a file ID class FileId(BaseModel): """Lightweight wrapper representing a file's unique identifier returned by the API Hub.""" id: str = Field(..., description="Unique identifier of the file (UUID)") # Represents the detailed file entry class FileEntry(BaseModel): """ Parameters ---------- id : Unique identifier of the file on the API Hub. filename : Original filename as stored on the Hub. type : Logical file type (``open_api``, ``arazzo`` …) used to group files. content : Parsed JSON/YAML content of the file. source_path : Original path inside the vendor bundle. Present for Arazzo specs. """ id: str = Field(..., description="Unique identifier of the file on the API Hub (UUID)") filename: str = Field(..., description="Original filename as uploaded to the Hub") type: str = Field(..., description="Logical file type category, e.g. 'open_api', 'arazzo'") content: dict[str, Any] = Field( default_factory=dict, description="Full parsed contents of the file (JSON object)", ) source_path: str | None = Field( default=None, description="Original relative path inside the vendor bundle (if any)", ) @model_validator(mode="before") @classmethod def handle_oak_path_alias(cls, values) -> Any: # type: ignore[no-untyped-def] """Handle backward compatibility for oak_path field name.""" if isinstance(values, dict): # If oak_path is provided but source_path is not, use oak_path if "oak_path" in values and "source_path" not in values: values["source_path"] = values.pop("oak_path") # If both are provided, prefer source_path and remove oak_path elif "oak_path" in values and "source_path" in values: values.pop("oak_path") return values @property def oak_path(self) -> str | None: """Backward compatibility property for oak_path.""" return self.source_path # Represents an API reference within a workflow class APIReference(BaseModel): """Reference to an external API used inside a workflow definition.""" api_id: str = Field(..., description="UUID of the referenced API") api_name: str = Field(..., description="Human-readable name of the API") api_version: str = Field(..., description="Version string of the referenced API") class APIIdentifier(BaseModel): """Canonical identifier for an API as returned by :py:meth:`Jentic.list_apis`.""" api_vendor: str = Field(..., description="Vendor domain, e.g. 'hubapi.com'") api_name: str | None = Field( default=None, description="API name (set when targeting a specific API; otherwise None)", ) api_version: str | None = Field( default=None, description="Version string (set when targeting a specific version; otherwise None)", ) # Represents the spec info of an operation or workflow class SpecInfo(BaseModel): """Metadata tuple identifying an OpenAPI specification or Arazzo bundle. This object is stored on each :class:`WorkflowEntry` / :class:`OperationEntry` so you can quickly tell which vendor and version a piece of spec data belongs to. """ api_vendor: str = Field(..., description="Vendor domain, e.g. 'hubapi.com'") api_name: str = Field(..., description="Canonical API name inside the vendor") api_version: str | None = Field( default=None, description="Optional version string – None means latest / unspecified.", ) # Represents the file references associated with a workflow/operation, keyed by file type class AssociatedFiles(BaseModel): """Grouped file references belonging to an operation or workflow.""" arazzo: list[FileId] = Field( default_factory=list, description="List of Arazzo workflow/runner documents", ) open_api: list[FileId] = Field( default_factory=list, description="List of OpenAPI specification files referenced by this entity", ) # Represents a single workflow entry in the 'workflows' dictionary class WorkflowEntry(BaseModel): """A single workflow returned by :py:meth:`Jentic.load` (raw form). It keeps both the *friendly* ``workflow_id`` and the underlying ``workflow_uuid`` so you can correlate search hits with runtime UUIDs. """ workflow_id: str = Field(..., description="Human-readable ID inside the Arazzo doc") workflow_uuid: str = Field(..., description="Primary UUID assigned by the API Hub") name: str = Field(..., description="Workflow display name / summary") api_references: list[APIReference] = Field( default_factory=list, description="List of APIs invoked by this workflow", ) files: AssociatedFiles = Field( ..., description="File IDs for all specs referenced by the workflow" ) api_name: str = Field( default="", description="Back-compat field; primary API name if there is a single default", ) api_names: list[str] | None = Field( default=None, description="All API names referenced by this workflow (if >1)", ) # Represents a single operation entry in the 'operations' dictionary class OperationEntry(BaseModel): """Entry describing a single REST operation exposed by an API version.""" id: str = Field(..., description="UUID of the operation (op_…)") api_name: str = Field( default="", description="Primary API name – kept for legacy reasons; prefer spec_info", ) api_version_id: str = Field(..., description="UUID of the parent API version") operation_id: str | None = Field( default=None, description="OperationId inside the OpenAPI spec (if provided)", ) path: str = Field(..., description="HTTP path template of the operation") method: str = Field(..., description="HTTP method in upper-case (GET, POST, …)") summary: str | None = Field(default=None, description="Short human-readable summary") files: AssociatedFiles = Field(..., description="Spec files associated with this operation") api_references: list[APIReference] | None = Field( default=None, description="Resolved API references (populated by load)", ) spec_info: SpecInfo | None = Field( default=None, description="Vendor / name / version triple identifying the spec this operation belongs to", ) # The main response model class GetFilesResponse(BaseModel): """Raw response from the *get-execution-files* endpoint.""" files: dict[str, dict[str, FileEntry]] = Field( default_factory=dict, description="Mapping *file_type* → *file_id* → FileEntry for every spec file", ) workflows: dict[str, WorkflowEntry] = Field( default_factory=dict, description="All workflows returned, keyed by workflow UUID", ) operations: dict[str, OperationEntry] | None = Field( default=None, description="Operations returned (if requested), keyed by operation UUID", ) # Represents the details needed to execute a specific workflow class WorkflowExecutionDetails(BaseModel): """Intermediate helper model used when generating runtime config.""" arazzo_doc: dict[str, Any] | None = Field( default=None, description="Parsed Arazzo document for the workflow (None if missing)", ) source_descriptions: dict[str, dict[str, Any]] = Field( default_factory=dict, description="OpenAPI specs keyed by filename for auth analysis", ) friendly_workflow_id: str | None = Field( default=None, description="Human-readable ID inside the Arazzo document", ) class SearchResult(BaseModel): """Single hit returned by the semantic search endpoint.""" id: str = Field(..., description="UUID of the matched entity (op_… / wf_…)") path: str = Field(..., description="HTTP path template of the operation (empty for workflows)") method: str = Field(..., description="HTTP method in upper-case (GET, POST, …)") api_name: str = Field(..., description="API name that owns the match") entity_type: str = Field(..., description="'operation' or 'workflow'") summary: str = Field(..., description="Short label or summary for the hit") description: str = Field(..., description="Longer textual description from the spec") match_score: float = Field(0.0, description="Semantic distance score (lower = better)") operation_id: str | None = Field( default=None, description="OpenAPI operationId (present for operation hits)", ) workflow_id: str | None = Field( default=None, description="Friendly workflow_id (present for workflow hits)", ) @model_validator(mode="before") @classmethod def set_data(cls, data: Any) -> dict[str, Any]: if data.get("entity_type") == "operation": summary = data.get("summary", "") else: summary = data.get("name", data.get("workflow_id", "")) if isinstance(data, dict): return { "id": data.get("id", ""), "entity_type": data.get("entity_type", ""), "summary": summary, "description": data.get("description", ""), "path": data.get("path", ""), "method": data.get("method", ""), "api_name": data.get("api_name", ""), "match_score": data.get("distance", 0.0), "operation_id": data.get("operation_id", None), "workflow_id": data.get("workflow_id", None), } return data # Search request and response models # class SearchRequest(BaseModel): """Parameters accepted by :py:meth:`Jentic.search`.""" query: str = Field(..., description="Free-text query string (natural language)") limit: int = Field(default=5, description="Maximum number of results to return") apis: list[str] | None = Field( default=None, description="Optional list of API names to restrict the search to", ) keywords: list[str] | None = Field( default=None, description="Optional list of keywords used to boost semantic search", ) filter_by_credentials: bool = Field( default=True, description="When True, hide results the agent lacks credentials for", ) class SearchResponse(BaseModel): """Return value of :py:meth:`Jentic.search`.""" results: list[SearchResult] = Field( default_factory=list, description="Rank-ordered list of operations and workflows that match the query", ) total_count: int = Field(0, description="Total number of hits") query: str = Field(..., description="Echo of the original search string") # Load Request class LoadRequest(BaseModel): """Request model for :py:meth:`Jentic.load`.""" ids: list[str] | None = Field( default=None, description="List of operation/workflow UUIDs to load", ) def to_dict(self) -> dict[str, Any]: if self.ids is None: return {} workflow_uuids = [] operation_uuids = [] for id in self.ids: if id.startswith("wf_"): workflow_uuids.append(id) elif id.startswith("op_"): operation_uuids.append(id) return { "workflow_uuids": workflow_uuids, "operation_uuids": operation_uuids, } class WorkflowDetail(BaseModel): """Detailed metadata for a workflow included in a runtime config.""" description: str = Field( "", description="Markdown-compatible long description of the workflow", ) id: str = Field(..., description="UUID of the workflow (wf_…)") summary: str = Field("", description="Short single-line summary") inputs: dict[str, Any] = Field( default_factory=dict, description="JSON-schema for workflow inputs (merged across steps)", ) outputs: dict[str, Any] = Field( default_factory=dict, description="JSON-schema describing the workflow outputs", ) api_names: list[str] = Field( default_factory=list, description="List of API names invoked somewhere inside the workflow", ) security_requirements: dict[str, list[dict[str, Any]]] = Field( default_factory=dict, description="Flattened security requirements keyed by source filename", ) class OperationDetail(BaseModel): """Rich description for an individual REST operation included in a runtime config. This structure is consumed by UI generators and by the AgentToolManager to create invocation-ready tool definitions. """ id: str = Field(..., description="UUID of the operation (op_…)") method: str | None = Field( default=None, description="HTTP method (GET, POST, …). None for workflow-only entries.", ) path: str | None = Field( default=None, description="HTTP path template. None when not applicable.", ) summary: str | None = Field( default=None, description="Short summary extracted from the OpenAPI spec" ) api_name: str | None = Field( default=None, description="API this operation belongs to (vendor/name)" ) inputs: dict[str, Any] | None = Field( default=None, description="JSON-schema fragment describing the operation inputs", ) outputs: dict[str, Any] | None = Field( default=None, description="JSON-schema fragment describing the operation outputs", ) security_requirements: list[dict[str, Any]] | None = Field( default=None, description="Flattened list of security requirements for the operation", ) class LoadResponse(BaseModel): """Return value of :py:meth:`Jentic.load`.""" tool_info: dict[str, OperationDetail | WorkflowDetail | None] = Field( default_factory=dict, description="Mapping of every requested UUID to its detailed schema", ) @classmethod def from_get_files_response(cls, get_files_response: GetFilesResponse) -> "LoadResponse": # Transform LoadResponse to dict[str, Any] # This matches the agent_runtime.config parsing from jentic.lib.agent_runtime.config import JenticConfig # Get workflow and operation UUIDs workflow_uuids = ( list(get_files_response.workflows.keys()) if get_files_response.workflows else [] ) operation_uuids = ( list(get_files_response.operations.keys()) if get_files_response.operations else [] ) # Extract workflow details all_arazzo_specs, extracted_workflow_details = JenticConfig._extract_all_workflow_details( get_files_response, workflow_uuids ) # Step 3: Extract operation details if present extracted_operation_details = {} if operation_uuids: extracted_operation_details = JenticConfig._extract_all_operation_details( get_files_response, operation_uuids ) return LoadResponse( tool_info=cast( dict[str, OperationDetail | WorkflowDetail | None], {**extracted_operation_details, **extracted_workflow_details}, ), ) class ExecutionRequest(BaseModel): """Payload expected by :py:meth:`Jentic.execute`.""" id: str = Field( ..., description="UUID of the operation/workflow to execute (op_… or wf_…)", ) inputs: Dict[str, Any] = Field( default_factory=dict, description="Input object matching the JSON schema returned by :py:meth:`Jentic.load`", ) def to_dict(self) -> dict[str, Any]: # Transform the id to execution_type and uuid if self.id.startswith("op_"): execution_type = "operation" else: execution_type = "workflow" return { "execution_type": execution_type, "uuid": self.id, "inputs": self.inputs, } class ExecuteResponse(BaseModel): """Return value of :py:meth:`Jentic.execute`.""" success: bool = Field(..., description="True when the remote execution succeeded") status_code: int = Field(..., description="HTTP status code for the execution") output: Any | None = Field( default=None, description="Raw output produced by the operation/workflow (if any)", ) error: str | None = Field(default=None, description="Error message when *success* is False") step_results: dict[str, Any] | None = Field( default=None, description="Per-step execution traces when the backend provides them", ) inputs: dict[str, Any] | None = Field( default=None, description="Echo of the original inputs (useful for debugging)", )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jentic/jentic-tools'

If you have feedback or need assistance with the MCP directory API, please join our Discord server