# Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. from __future__ import annotations from collections.abc import Iterator from dataclasses import dataclass, field from pathlib import Path from typing import Any import yaml HEADER = "# yaml-language-server: $schema=https://app.stainlessapi.com/config-internal.schema.json\n\n" SECTION_ORDER = [ "organization", "security", "security_schemes", "targets", "client_settings", "environments", "pagination", "settings", "openapi", "readme", "resources", ] ORGANIZATION = { "name": "llama-stack-client", "docs": "https://llama-stack.readthedocs.io/en/latest/", "contact": "llamastack@meta.com", } SECURITY = [{}, {"BearerAuth": []}] SECURITY_SCHEMES = {"BearerAuth": {"type": "http", "scheme": "bearer"}} TARGETS = { "node": { "package_name": "llama-stack-client", "production_repo": "llamastack/llama-stack-client-typescript", "publish": {"npm": False}, }, "python": { "package_name": "llama_stack_client", "production_repo": "llamastack/llama-stack-client-python", "options": {"use_uv": True}, "publish": {"pypi": True}, "project_name": "llama_stack_client", }, "kotlin": { "reverse_domain": "com.llama_stack_client.api", "production_repo": None, "publish": {"maven": False}, }, "go": { "package_name": "llama-stack-client", "production_repo": "llamastack/llama-stack-client-go", "options": {"enable_v2": True, "back_compat_use_shared_package": False}, }, } CLIENT_SETTINGS = { "default_env_prefix": "LLAMA_STACK_CLIENT", "opts": { "api_key": { "type": "string", "read_env": "LLAMA_STACK_CLIENT_API_KEY", "auth": {"security_scheme": "BearerAuth"}, "nullable": True, } }, } ENVIRONMENTS = {"production": "http://any-hosted-llama-stack.com"} PAGINATION = [ { "name": "datasets_iterrows", "type": "offset", "request": { "dataset_id": {"type": "string"}, "start_index": { "type": "integer", "x-stainless-pagination-property": {"purpose": "offset_count_param"}, }, "limit": {"type": "integer"}, }, "response": { "data": {"type": "array", "items": {"type": "object"}}, "next_index": { "type": "integer", "x-stainless-pagination-property": {"purpose": "offset_count_start_field"}, }, }, }, { "name": "openai_cursor_page", "type": "cursor", "request": { "limit": {"type": "integer"}, "after": { "type": "string", "x-stainless-pagination-property": {"purpose": "next_cursor_param"}, }, }, "response": { "data": {"type": "array", "items": {}}, "has_more": {"type": "boolean"}, "last_id": { "type": "string", "x-stainless-pagination-property": {"purpose": "next_cursor_field"}, }, }, }, ] SETTINGS = { "license": "MIT", "unwrap_response_fields": ["data"], "file_header": "Copyright (c) Meta Platforms, Inc. and affiliates.\n" "All rights reserved.\n" "\n" "This source code is licensed under the terms described in the " "LICENSE file in\n" "the root directory of this source tree.\n", } OPENAPI = { "transformations": [ { "command": "mergeObject", "reason": "Better return_type using enum", "args": { "target": ["$.components.schemas"], "object": { "ReturnType": { "additionalProperties": False, "properties": { "type": { "enum": [ "string", "number", "boolean", "array", "object", "json", "union", "chat_completion_input", "completion_input", "agent_turn_input", ] } }, "required": ["type"], "type": "object", } }, }, }, { "command": "replaceProperties", "reason": "Replace return type properties with better model (see above)", "args": { "filter": { "only": [ "$.components.schemas.ScoringFn.properties.return_type", "$.components.schemas.RegisterScoringFunctionRequest.properties.return_type", ] }, "value": {"$ref": "#/components/schemas/ReturnType"}, }, }, { "command": "oneOfToAnyOf", "reason": "Prism (mock server) doesn't like one of our " "requests as it technically matches multiple " "variants", }, ] } README = { "example_requests": { "default": { "type": "request", "endpoint": "post /v1/chat/completions", "params": {}, }, "headline": {"type": "request", "endpoint": "get /v1/models", "params": {}}, "pagination": { "type": "request", "endpoint": "post /v1/chat/completions", "params": {}, }, } } ALL_RESOURCES = { "$shared": { "models": { "interleaved_content_item": "InterleavedContentItem", "interleaved_content": "InterleavedContent", "param_type": "ParamType", "safety_violation": "SafetyViolation", "sampling_params": "SamplingParams", "scoring_result": "ScoringResult", "system_message": "SystemMessage", } }, "toolgroups": { "models": { "tool_group": "ToolGroup", "list_tool_groups_response": "ListToolGroupsResponse", }, "methods": { "register": "post /v1/toolgroups", "get": "get /v1/toolgroups/{toolgroup_id}", "list": "get /v1/toolgroups", "unregister": "delete /v1/toolgroups/{toolgroup_id}", }, }, "tools": { "methods": { "get": "get /v1/tools/{tool_name}", "list": {"paginated": False, "endpoint": "get /v1/tools"}, } }, "tool_runtime": { "models": { "tool_def": "ToolDef", "tool_invocation_result": "ToolInvocationResult", }, "methods": { "list_tools": { "paginated": False, "endpoint": "get /v1/tool-runtime/list-tools", }, "invoke_tool": "post /v1/tool-runtime/invoke", }, }, "responses": { "models": { "response_object_stream": "OpenAIResponseObjectStream", "response_object": "OpenAIResponseObject", }, "methods": { "create": { "type": "http", "streaming": { "stream_event_model": "responses.response_object_stream", "param_discriminator": "stream", }, "endpoint": "post /v1/responses", }, "retrieve": "get /v1/responses/{response_id}", "list": {"type": "http", "endpoint": "get /v1/responses"}, "delete": { "type": "http", "endpoint": "delete /v1/responses/{response_id}", }, }, "subresources": { "input_items": { "methods": { "list": { "type": "http", "paginated": False, "endpoint": "get /v1/responses/{response_id}/input_items", } } } }, }, "prompts": { "models": {"prompt": "Prompt", "list_prompts_response": "ListPromptsResponse"}, "methods": { "create": "post /v1/prompts", "list": {"paginated": False, "endpoint": "get /v1/prompts"}, "retrieve": "get /v1/prompts/{prompt_id}", "update": "post /v1/prompts/{prompt_id}", "delete": "delete /v1/prompts/{prompt_id}", "set_default_version": "post /v1/prompts/{prompt_id}/set-default-version", }, "subresources": { "versions": { "methods": { "list": { "paginated": False, "endpoint": "get /v1/prompts/{prompt_id}/versions", } } } }, }, "conversations": { "models": {"conversation_object": "Conversation"}, "methods": { "create": {"type": "http", "endpoint": "post /v1/conversations"}, "retrieve": "get /v1/conversations/{conversation_id}", "update": { "type": "http", "endpoint": "post /v1/conversations/{conversation_id}", }, "delete": { "type": "http", "endpoint": "delete /v1/conversations/{conversation_id}", }, }, "subresources": { "items": { "methods": { "get": { "type": "http", "endpoint": "get /v1/conversations/{conversation_id}/items/{item_id}", }, "list": { "type": "http", "endpoint": "get /v1/conversations/{conversation_id}/items", }, "create": { "type": "http", "endpoint": "post /v1/conversations/{conversation_id}/items", }, "delete": { "type": "http", "endpoint": "delete /v1/conversations/{conversation_id}/items/{item_id}", }, } } }, }, "inspect": { "models": { "healthInfo": "HealthInfo", "providerInfo": "ProviderInfo", "routeInfo": "RouteInfo", "versionInfo": "VersionInfo", }, "methods": {"health": "get /v1/health", "version": "get /v1/version"}, }, "embeddings": { "models": {"create_embeddings_response": "OpenAIEmbeddingsResponse"}, "methods": {"create": "post /v1/embeddings"}, }, "chat": { "models": {"chat_completion_chunk": "OpenAIChatCompletionChunk"}, "subresources": { "completions": { "methods": { "create": { "type": "http", "streaming": { "stream_event_model": "chat.chat_completion_chunk", "param_discriminator": "stream", }, "endpoint": "post /v1/chat/completions", }, "list": { "type": "http", "paginated": False, "endpoint": "get /v1/chat/completions", }, "retrieve": { "type": "http", "endpoint": "get /v1/chat/completions/{completion_id}", }, } } }, }, "completions": { "methods": { "create": { "type": "http", "streaming": {"param_discriminator": "stream"}, "endpoint": "post /v1/completions", } } }, "vector_io": { "models": {"queryChunksResponse": "QueryChunksResponse"}, "methods": { "insert": "post /v1/vector-io/insert", "query": "post /v1/vector-io/query", }, }, "vector_stores": { "models": { "vector_store": "VectorStoreObject", "list_vector_stores_response": "VectorStoreListResponse", "vector_store_delete_response": "VectorStoreDeleteResponse", "vector_store_search_response": "VectorStoreSearchResponsePage", }, "methods": { "create": "post /v1/vector_stores", "list": "get /v1/vector_stores", "retrieve": "get /v1/vector_stores/{vector_store_id}", "update": "post /v1/vector_stores/{vector_store_id}", "delete": "delete /v1/vector_stores/{vector_store_id}", "search": "post /v1/vector_stores/{vector_store_id}/search", }, "subresources": { "files": { "models": {"vector_store_file": "VectorStoreFileObject"}, "methods": { "list": "get /v1/vector_stores/{vector_store_id}/files", "retrieve": "get /v1/vector_stores/{vector_store_id}/files/{file_id}", "update": "post /v1/vector_stores/{vector_store_id}/files/{file_id}", "delete": "delete /v1/vector_stores/{vector_store_id}/files/{file_id}", "create": "post /v1/vector_stores/{vector_store_id}/files", "content": "get /v1/vector_stores/{vector_store_id}/files/{file_id}/content", }, }, "file_batches": { "models": { "vector_store_file_batches": "VectorStoreFileBatchObject", "list_vector_store_files_in_batch_response": "VectorStoreFilesListInBatchResponse", }, "methods": { "create": "post /v1/vector_stores/{vector_store_id}/file_batches", "retrieve": "get /v1/vector_stores/{vector_store_id}/file_batches/{batch_id}", "list_files": "get /v1/vector_stores/{vector_store_id}/file_batches/{batch_id}/files", "cancel": "post /v1/vector_stores/{vector_store_id}/file_batches/{batch_id}/cancel", }, }, }, }, "models": { "models": { "model": "OpenAIModel", "list_models_response": "OpenAIListModelsResponse", }, "methods": { "list": {"paginated": False, "endpoint": "get /v1/models"}, "retrieve": "get /v1/models/{model_id}", "register": "post /v1/models", "unregister": "delete /v1/models/{model_id}", }, "subresources": {"openai": {"methods": {"list": {"paginated": False, "endpoint": "get /v1/models"}}}}, }, "providers": { "models": {"list_providers_response": "ListProvidersResponse"}, "methods": { "list": {"paginated": False, "endpoint": "get /v1/providers"}, "retrieve": "get /v1/providers/{provider_id}", }, }, "routes": { "models": {"list_routes_response": "ListRoutesResponse"}, "methods": {"list": {"paginated": False, "endpoint": "get /v1/inspect/routes"}}, }, "moderations": { "models": {"create_response": "ModerationObject"}, "methods": {"create": "post /v1/moderations"}, }, "safety": { "models": {"run_shield_response": "RunShieldResponse"}, "methods": {"run_shield": "post /v1/safety/run-shield"}, }, "shields": { "models": {"shield": "Shield", "list_shields_response": "ListShieldsResponse"}, "methods": { "retrieve": "get /v1/shields/{identifier}", "list": {"paginated": False, "endpoint": "get /v1/shields"}, "register": "post /v1/shields", "delete": "delete /v1/shields/{identifier}", }, }, "scoring": { "methods": { "score": "post /v1/scoring/score", "score_batch": "post /v1/scoring/score-batch", } }, "scoring_functions": { "models": { "scoring_fn": "ScoringFn", "scoring_fn_params": "ScoringFnParams", "list_scoring_functions_response": "ListScoringFunctionsResponse", }, "methods": { "retrieve": "get /v1/scoring-functions/{scoring_fn_id}", "list": {"paginated": False, "endpoint": "get /v1/scoring-functions"}, "register": "post /v1/scoring-functions", "unregister": "delete /v1/scoring-functions/{scoring_fn_id}", }, }, "files": { "models": { "file": "OpenAIFileObject", "list_files_response": "ListOpenAIFileResponse", "delete_file_response": "OpenAIFileDeleteResponse", }, "methods": { "create": "post /v1/files", "list": "get /v1/files", "retrieve": "get /v1/files/{file_id}", "delete": "delete /v1/files/{file_id}", "content": "get /v1/files/{file_id}/content", }, }, "batches": { "methods": { "create": "post /v1/batches", "list": "get /v1/batches", "retrieve": "get /v1/batches/{batch_id}", "cancel": "post /v1/batches/{batch_id}/cancel", } }, "alpha": { "subresources": { "inference": {"methods": {"rerank": "post /v1alpha/inference/rerank"}}, "post_training": { "models": { "algorithm_config": "AlgorithmConfig", "post_training_job": "PostTrainingJob", "list_post_training_jobs_response": "ListPostTrainingJobsResponse", }, "methods": { "preference_optimize": "post /v1alpha/post-training/preference-optimize", "supervised_fine_tune": "post /v1alpha/post-training/supervised-fine-tune", }, "subresources": { "job": { "methods": { "artifacts": "get /v1alpha/post-training/job/artifacts", "cancel": "post /v1alpha/post-training/job/cancel", "status": "get /v1alpha/post-training/job/status", "list": { "paginated": False, "endpoint": "get /v1alpha/post-training/jobs", }, } } }, }, "benchmarks": { "models": { "benchmark": "Benchmark", "list_benchmarks_response": "ListBenchmarksResponse", }, "methods": { "retrieve": "get /v1alpha/eval/benchmarks/{benchmark_id}", "list": { "paginated": False, "endpoint": "get /v1alpha/eval/benchmarks", }, "register": "post /v1alpha/eval/benchmarks", "unregister": "delete /v1alpha/eval/benchmarks/{benchmark_id}", }, }, "eval": { "models": { "evaluate_response": "EvaluateResponse", "benchmark_config": "BenchmarkConfig", "job": "Job", }, "methods": { "evaluate_rows": "post /v1alpha/eval/benchmarks/{benchmark_id}/evaluations", "run_eval": "post /v1alpha/eval/benchmarks/{benchmark_id}/jobs", "evaluate_rows_alpha": "post /v1alpha/eval/benchmarks/{benchmark_id}/evaluations", "run_eval_alpha": "post /v1alpha/eval/benchmarks/{benchmark_id}/jobs", }, "subresources": { "jobs": { "methods": { "cancel": "delete /v1alpha/eval/benchmarks/{benchmark_id}/jobs/{job_id}", "status": "get /v1alpha/eval/benchmarks/{benchmark_id}/jobs/{job_id}", "retrieve": "get /v1alpha/eval/benchmarks/{benchmark_id}/jobs/{job_id}/result", } } }, }, } }, "beta": { "subresources": { "datasets": { "models": {"list_datasets_response": "ListDatasetsResponse"}, "methods": { "register": "post /v1beta/datasets", "retrieve": "get /v1beta/datasets/{dataset_id}", "list": {"paginated": False, "endpoint": "get /v1beta/datasets"}, "unregister": "delete /v1beta/datasets/{dataset_id}", "iterrows": "get /v1beta/datasetio/iterrows/{dataset_id}", "appendrows": "post /v1beta/datasetio/append-rows/{dataset_id}", }, } } }, } HTTP_METHODS = {"get", "post", "put", "patch", "delete", "options", "head"} @dataclass class Endpoint: method: str path: str extra: dict[str, Any] = field(default_factory=dict) @classmethod def from_config(cls, value: Any) -> Endpoint: if isinstance(value, str): method, _, path = value.partition(" ") return cls._from_parts(method, path) if isinstance(value, dict) and "endpoint" in value: method, _, path = value["endpoint"].partition(" ") extra = {k: v for k, v in value.items() if k != "endpoint"} endpoint = cls._from_parts(method, path) endpoint.extra.update(extra) return endpoint raise ValueError(f"Unsupported endpoint value: {value!r}") @classmethod def _from_parts(cls, method: str, path: str) -> Endpoint: method = method.strip().lower() path = path.strip() if method not in HTTP_METHODS: raise ValueError(f"Unsupported HTTP method for Stainless config: {method!r}") if not path.startswith("/"): raise ValueError(f"Endpoint path must start with '/': {path!r}") return cls(method=method, path=path) def to_config(self) -> Any: if not self.extra: return f"{self.method} {self.path}" data = dict(self.extra) data["endpoint"] = f"{self.method} {self.path}" return data def route_key(self) -> str: return f"{self.method} {self.path}" @dataclass class Resource: models: dict[str, str] | None = None methods: dict[str, Endpoint] = field(default_factory=dict) subresources: dict[str, Resource] = field(default_factory=dict) @classmethod def from_dict(cls, data: dict[str, Any]) -> Resource: models = data.get("models") methods = {name: Endpoint.from_config(value) for name, value in data.get("methods", {}).items()} subresources = {name: cls.from_dict(value) for name, value in data.get("subresources", {}).items()} return cls(models=models, methods=methods, subresources=subresources) def to_config(self) -> dict[str, Any]: result: dict[str, Any] = {} if self.models: result["models"] = self.models if self.methods: result["methods"] = {name: endpoint.to_config() for name, endpoint in self.methods.items()} if self.subresources: result["subresources"] = {name: resource.to_config() for name, resource in self.subresources.items()} return result def collect_endpoint_paths(self) -> set[str]: paths = {endpoint.route_key() for endpoint in self.methods.values()} for subresource in self.subresources.values(): paths.update(subresource.collect_endpoint_paths()) return paths def iter_endpoints(self, prefix: str) -> Iterator[tuple[str, str]]: for method_name, endpoint in self.methods.items(): label = f"{prefix}.{method_name}" if prefix else method_name yield endpoint.route_key(), label for sub_name, subresource in self.subresources.items(): sub_prefix = f"{prefix}.{sub_name}" if prefix else sub_name yield from subresource.iter_endpoints(sub_prefix) _RESOURCES = {name: Resource.from_dict(data) for name, data in ALL_RESOURCES.items()} def _load_openapi_paths(openapi_path: Path) -> set[str]: spec = yaml.safe_load(openapi_path.read_text()) or {} paths: set[str] = set() for path, path_item in (spec.get("paths") or {}).items(): if not isinstance(path_item, dict): continue for method, operation in path_item.items(): if not isinstance(operation, dict): continue paths.add(f"{str(method).lower()} {path}") return paths @dataclass(frozen=True) class StainlessConfig: organization: dict[str, Any] security: list[Any] security_schemes: dict[str, Any] targets: dict[str, Any] client_settings: dict[str, Any] environments: dict[str, Any] pagination: list[dict[str, Any]] settings: dict[str, Any] openapi: dict[str, Any] readme: dict[str, Any] resources: dict[str, Resource] @classmethod def make(cls) -> StainlessConfig: return cls( organization=ORGANIZATION, security=SECURITY, security_schemes=SECURITY_SCHEMES, targets=TARGETS, client_settings=CLIENT_SETTINGS, environments=ENVIRONMENTS, pagination=PAGINATION, settings=SETTINGS, openapi=OPENAPI, readme=README, resources=dict(_RESOURCES), ) def referenced_paths(self) -> set[str]: paths: set[str] = set() for resource in self.resources.values(): paths.update(resource.collect_endpoint_paths()) paths.update(self.readme_endpoint_paths()) return paths def readme_endpoint_paths(self) -> set[str]: example_requests = self.readme.get("example_requests", {}) if self.readme else {} paths: set[str] = set() for entry in example_requests.values(): endpoint = entry.get("endpoint") if isinstance(entry, dict) else None if isinstance(endpoint, str): method, _, route = endpoint.partition(" ") method = method.strip().lower() route = route.strip() if method and route: paths.add(f"{method} {route}") return paths def endpoint_map(self) -> dict[str, list[str]]: mapping: dict[str, list[str]] = {} for resource_name, resource in self.resources.items(): for route, label in resource.iter_endpoints(resource_name): mapping.setdefault(route, []).append(label) return mapping def validate_unique_endpoints(self) -> None: duplicates: dict[str, list[str]] = {} for route, labels in self.endpoint_map().items(): top_levels = {label.split(".", 1)[0] for label in labels} if len(top_levels) > 1: duplicates[route] = labels if duplicates: formatted = "\n".join( f" - {route} defined in: {', '.join(sorted(labels))}" for route, labels in sorted(duplicates.items()) ) raise ValueError("Duplicate endpoints found across resources:\n" + formatted) def validate_readme_endpoints(self) -> None: resource_paths: set[str] = set() for resource in self.resources.values(): resource_paths.update(resource.collect_endpoint_paths()) missing = sorted(path for path in self.readme_endpoint_paths() if path not in resource_paths) if missing: formatted = "\n".join(f" - {path}" for path in missing) raise ValueError("README example endpoints are not present in Stainless resources:\n" + formatted) def to_dict(self) -> dict[str, Any]: cfg: dict[str, Any] = {} for section in SECTION_ORDER: if section == "resources": cfg[section] = {name: resource.to_config() for name, resource in self.resources.items()} continue cfg[section] = getattr(self, section) return cfg def validate_against_openapi(self, openapi_path: Path) -> None: if not openapi_path.exists(): raise FileNotFoundError(f"OpenAPI spec not found at {openapi_path}") spec_paths = _load_openapi_paths(openapi_path) config_paths = self.referenced_paths() missing = sorted(path for path in config_paths if path not in spec_paths) if missing: formatted = "\n".join(f" - {path}" for path in missing) raise ValueError("Stainless config references missing endpoints:\n" + formatted) def validate(self, openapi_path: Path | None = None) -> None: self.validate_unique_endpoints() self.validate_readme_endpoints() if openapi_path is not None: self.validate_against_openapi(openapi_path) def build_config() -> dict[str, Any]: return StainlessConfig.make().to_dict() def write_config(repo_root: Path, openapi_path: Path | None = None) -> Path: stainless_config = StainlessConfig.make() spec_path = (openapi_path or (repo_root / "client-sdks" / "stainless" / "openapi.yml")).resolve() stainless_config.validate(spec_path) yaml_text = yaml.safe_dump(stainless_config.to_dict(), sort_keys=False) output = repo_root / "client-sdks" / "stainless" / "config.yml" output.write_text(HEADER + yaml_text) return output def main() -> None: repo_root = Path(__file__).resolve().parents[3] output = write_config(repo_root) print(f"Wrote Stainless config: {output}") if __name__ == "__main__": main()