diff --git a/.vscode/settings.json b/.vscode/settings.json index b29c0a856..0a1f0f638 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ }, "editor.defaultFormatter": "oxc.oxc-vscode", "editor.quickSuggestions": { + "other": "on", "strings": "on" }, "eslint.format.enable": true, diff --git a/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py b/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py index ce0c8aabf..8a0b1da14 100644 --- a/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py +++ b/packages/openapi-python-tests/pydantic/v2/__snapshots__/3.1.x/opencode/pydantic_gen.py @@ -2,7 +2,7 @@ from enum import Enum from pydantic import BaseModel, Field -from typing import Any, List, Literal, Optional, TypeAlias, Union +from typing import Any, Literal, Optional, TypeAlias, Union class EventInstallationUpdatedProperties(BaseModel): @@ -87,7 +87,7 @@ class UserMessageTime(BaseModel): class UserMessageSummary(BaseModel): title: Optional[str] = Field(default=None) body: Optional[str] = Field(default=None) - diffs: List[FileDiff] + diffs: list[FileDiff] class UserMessageModel(BaseModel): @@ -330,7 +330,7 @@ class ToolStateCompleted(BaseModel): title: str metadata: dict[str, Any] time: ToolStateCompletedTime - attachments: Optional[List[FilePart]] = Field(default=None) + attachments: Optional[list[FilePart]] = Field(default=None) class ToolStateErrorTime(BaseModel): @@ -405,7 +405,7 @@ class PatchPart(BaseModel): message_id: str = Field(..., alias="messageID") type_: Literal["patch"] = Field(..., alias="type") hash_: str = Field(..., alias="hash") - files: List[str] + files: list[str] class AgentPartSource(BaseModel): @@ -489,9 +489,9 @@ class PermissionRequest(BaseModel): id_: str = Field(..., alias="id", pattern="^per.*") session_id: str = Field(..., alias="sessionID", pattern="^ses.*") permission: str - patterns: List[str] + patterns: list[str] metadata: dict[str, Any] - always: List[str] + always: list[str] tool: Optional[PermissionRequestTool] = Field(default=None) @@ -581,7 +581,7 @@ class Todo(BaseModel): class EventTodoUpdatedProperties(BaseModel): session_id: str = Field(..., alias="sessionID") - todos: List[Todo] + todos: list[Todo] class EventTodoUpdated(BaseModel): @@ -676,14 +676,14 @@ class PermissionRule(BaseModel): action: PermissionAction -PermissionRuleset: TypeAlias = List[PermissionRule] +PermissionRuleset: TypeAlias = list[PermissionRule] class SessionSummary(BaseModel): additions: float deletions: float files: float - diffs: Optional[List[FileDiff]] = Field(default=None) + diffs: Optional[list[FileDiff]] = Field(default=None) class SessionShare(BaseModel): @@ -747,7 +747,7 @@ class EventSessionDeleted(BaseModel): class EventSessionDiffProperties(BaseModel): session_id: str = Field(..., alias="sessionID") - diff: List[FileDiff] + diff: list[FileDiff] class EventSessionDiff(BaseModel): @@ -793,7 +793,7 @@ class Pty(BaseModel): id_: str = Field(..., alias="id", pattern="^pty.*") title: str command: str - args: List[str] + args: list[str] cwd: str status: PtyStatus pid: float @@ -856,7 +856,7 @@ class GlobalEvent(BaseModel): class BadRequestError(BaseModel): data: Any - errors: List[dict[str, Any]] + errors: list[dict[str, Any]] success: Literal[False] @@ -970,7 +970,7 @@ class ServerConfig(BaseModel): port: Optional[int] = Field(default=None, description="Port to listen on", gt=0, le=9007199254740991) hostname: Optional[str] = Field(default=None, description="Hostname to listen on") mdns: Optional[bool] = Field(default=None) - cors: Optional[List[str]] = Field(default=None, description="Additional domains to allow for CORS") + cors: Optional[list[str]] = Field(default=None, description="Additional domains to allow for CORS") class PermissionActionConfig(Enum, str): @@ -1055,8 +1055,8 @@ class ProviderConfigModelsValueModalitiesOutput(Enum, str): class ProviderConfigModelsValueModalities(BaseModel): - input_: List[ProviderConfigModelsValueModalitiesInput] = Field(..., alias="input") - output: List[ProviderConfigModelsValueModalitiesOutput] + input_: list[ProviderConfigModelsValueModalitiesInput] = Field(..., alias="input") + output: list[ProviderConfigModelsValueModalitiesOutput] class ProviderConfigModelsValueStatus(Enum, str): @@ -1099,18 +1099,18 @@ ProviderConfigOptions: TypeAlias = dict[str, Any] class ProviderConfig(BaseModel): api: Optional[str] = Field(default=None) name: Optional[str] = Field(default=None) - env: Optional[List[str]] = Field(default=None) + env: Optional[list[str]] = Field(default=None) id_: Optional[str] = Field(default=None, alias="id") npm: Optional[str] = Field(default=None) models: Optional[dict[str, Any]] = Field(default=None) - whitelist: Optional[List[str]] = Field(default=None) - blacklist: Optional[List[str]] = Field(default=None) + whitelist: Optional[list[str]] = Field(default=None) + blacklist: Optional[list[str]] = Field(default=None) options: Optional[ProviderConfigOptions] = Field(default=None) class McpLocalConfig(BaseModel): type_: Literal["local"] = Field(..., alias="type") - command: List[str] = Field(..., description="Command and arguments to run the MCP server") + command: list[str] = Field(..., description="Command and arguments to run the MCP server") environment: Optional[dict[str, Any]] = Field(default=None) enabled: Optional[bool] = Field(default=None) timeout: Optional[int] = Field(default=None, description="Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", gt=0, le=9007199254740991) @@ -1160,7 +1160,7 @@ class ConfigCommandValue(BaseModel): class ConfigWatcher(BaseModel): - ignore: Optional[List[str]] = Field(default=None) + ignore: Optional[list[str]] = Field(default=None) class ConfigShare(Enum, str): @@ -1186,9 +1186,9 @@ class ConfigAgent(BaseModel): class ConfigFormatterValue(BaseModel): disabled: Optional[bool] = Field(default=None) - command: Optional[List[str]] = Field(default=None) + command: Optional[list[str]] = Field(default=None) environment: Optional[dict[str, Any]] = Field(default=None) - extensions: Optional[List[str]] = Field(default=None) + extensions: Optional[list[str]] = Field(default=None) class ConfigEnterprise(BaseModel): @@ -1201,18 +1201,18 @@ class ConfigCompaction(BaseModel): class ConfigExperimentalHookFileEditedValue(BaseModel): - command: List[str] + command: list[str] environment: Optional[dict[str, Any]] = Field(default=None) class ConfigExperimentalHookSessionCompleted(BaseModel): - command: List[str] + command: list[str] environment: Optional[dict[str, Any]] = Field(default=None) class ConfigExperimentalHook(BaseModel): file_edited: Optional[dict[str, Any]] = Field(default=None) - session_completed: Optional[List[ConfigExperimentalHookSessionCompleted]] = Field(default=None) + session_completed: Optional[list[ConfigExperimentalHookSessionCompleted]] = Field(default=None) class ConfigExperimental(BaseModel): @@ -1221,7 +1221,7 @@ class ConfigExperimental(BaseModel): disable_paste_summary: Optional[bool] = Field(default=None) batch_tool: Optional[bool] = Field(default=None) open_telemetry: Optional[bool] = Field(default=None, alias="openTelemetry") - primary_tools: Optional[List[str]] = Field(default=None, description="Tools that should only be available to primary agents.") + primary_tools: Optional[list[str]] = Field(default=None, description="Tools that should only be available to primary agents.") continue_loop_on_deny: Optional[bool] = Field(default=None) mcp_timeout: Optional[int] = Field(default=None, description="Timeout in milliseconds for model context protocol (MCP) requests", gt=0, le=9007199254740991) @@ -1235,13 +1235,13 @@ class Config(BaseModel): server: Optional[ServerConfig] = Field(default=None) command: Optional[dict[str, Any]] = Field(default=None) watcher: Optional[ConfigWatcher] = Field(default=None) - plugin: Optional[List[str]] = Field(default=None) + plugin: Optional[list[str]] = Field(default=None) snapshot: Optional[bool] = Field(default=None) share: Optional[ConfigShare] = Field(default=None) autoshare: Optional[bool] = Field(default=None) autoupdate: Optional[Union[bool, Literal["notify"]]] = Field(default=None, description="Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications") - disabled_providers: Optional[List[str]] = Field(default=None, description="Disable providers that are loaded automatically") - enabled_providers: Optional[List[str]] = Field(default=None, description="When set, ONLY these providers will be enabled. All other providers will be ignored") + disabled_providers: Optional[list[str]] = Field(default=None, description="Disable providers that are loaded automatically") + enabled_providers: Optional[list[str]] = Field(default=None, description="When set, ONLY these providers will be enabled. All other providers will be ignored") model: Optional[str] = Field(default=None, description="Model to use in the format of provider/model, eg anthropic/claude-2") small_model: Optional[str] = Field(default=None, description="Small model to use for tasks like title generation in the format of provider/model") default_agent: Optional[str] = Field(default=None, description="Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.") @@ -1252,7 +1252,7 @@ class Config(BaseModel): mcp: Optional[dict[str, Any]] = Field(default=None) formatter: Optional[Union[Literal[False], dict[str, Any]]] = Field(default=None) lsp: Optional[Union[Literal[False], dict[str, Any]]] = Field(default=None) - instructions: Optional[List[str]] = Field(default=None, description="Additional instruction files or patterns to include") + instructions: Optional[list[str]] = Field(default=None, description="Additional instruction files or patterns to include") layout: Optional[LayoutConfig] = Field(default=None) permission: Optional[PermissionConfig_2] = Field(default=None) tools: Optional[dict[str, Any]] = Field(default=None) @@ -1261,7 +1261,7 @@ class Config(BaseModel): experimental: Optional[ConfigExperimental] = Field(default=None) -ToolIds: TypeAlias = List[str] +ToolIds: TypeAlias = list[str] class ToolListItem(BaseModel): @@ -1270,7 +1270,7 @@ class ToolListItem(BaseModel): parameters: Any -ToolList: TypeAlias = List[ToolListItem] +ToolList: TypeAlias = list[ToolListItem] class Path(BaseModel): @@ -1339,7 +1339,7 @@ class Command(BaseModel): mcp: Optional[bool] = Field(default=None) template: str subtask: Optional[bool] = Field(default=None) - hints: List[str] + hints: list[str] class ModelApi(BaseModel): @@ -1445,7 +1445,7 @@ class Provider(BaseModel): id_: str = Field(..., alias="id") name: str source: ProviderSource - env: List[str] + env: list[str] key: Optional[str] = Field(default=None) options: dict[str, Any] models: dict[str, Any] @@ -1491,7 +1491,7 @@ class FileContentPatchHunks(BaseModel): old_lines: float = Field(..., alias="oldLines") new_start: float = Field(..., alias="newStart") new_lines: float = Field(..., alias="newLines") - lines: List[str] + lines: list[str] class FileContentPatch(BaseModel): @@ -1499,7 +1499,7 @@ class FileContentPatch(BaseModel): new_file_name: str = Field(..., alias="newFileName") old_header: Optional[str] = Field(default=None, alias="oldHeader") new_header: Optional[str] = Field(default=None, alias="newHeader") - hunks: List[FileContentPatchHunks] + hunks: list[FileContentPatchHunks] index: Optional[str] = Field(default=None) @@ -1586,7 +1586,7 @@ class LspStatus(BaseModel): class FormatterStatus(BaseModel): name: str - extensions: List[str] + extensions: list[str] enabled: bool diff --git a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/__init__.py b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/__init__.py index cb4d47f04..2575421d7 100644 --- a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/__init__.py +++ b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/__init__.py @@ -1,5 +1,5 @@ # This file is auto-generated by @hey-api/openapi-python -from .client_gen import Client, create_client +from .client_gen import Client, build_client_params, create_client -__all__ = ["Client", "create_client"] +__all__ = ["Client", "build_client_params", "create_client"] diff --git a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/client_gen.py b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/client_gen.py index b57048821..532f05e2f 100644 --- a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/client_gen.py +++ b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/client/client_gen.py @@ -1,9 +1,75 @@ # This file is auto-generated by @hey-api/openapi-python -from typing import Optional +from typing import Any, Optional import httpx +EXTRA_PREFIXES_MAP = { + "$body_": "json", + "$headers_": "headers", + "$path_": "path", + "$query_": "params", +} + + +def build_client_params(fields: list[dict[str, Any]], **kwargs) -> dict[str, Any]: + """Build client parameters from flat keyword arguments. + + Args: + fields: List of field configurations with 'in', 'key', and optional 'map'. + **kwargs: Flat parameters passed to the SDK method. + + Returns: + Dict suitable for httpx client methods: {params: {...}, headers: {...}, json: Any} + """ + result: dict[str, Any] = {} + + key_map = {} + for field in fields: + key = field.get("key") + if key: + key_map[key] = { + "in": field.get("in"), + "map": field.get("map", key), + } + + for key, value in kwargs.items(): + if value is None: + continue + + field = key_map.get(key) + + if field: + in_slot = field["in"] + map_key = field["map"] + slot = "json" if in_slot == "body" else in_slot + + if in_slot == "body": + result[slot] = value + else: + if slot not in result: + result[slot] = {} + result[slot][map_key] = value + else: + for prefix, slot in EXTRA_PREFIXES_MAP.items(): + if key.startswith(prefix): + actual_key = key[len(prefix) :] + if slot not in result: + result[slot] = {} + result[slot][actual_key] = value + break + else: + if "params" not in result: + result["params"] = {} + result["params"][key] = value + + for slot in list(result.keys()): + if not result[slot]: + del result[slot] + + return result + + class BaseClient: """Base HTTP client using httpx that SDK classes extend.""" diff --git a/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/pydantic_gen.py b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/pydantic_gen.py new file mode 100644 index 000000000..8a0b1da14 --- /dev/null +++ b/packages/openapi-python-tests/sdks/__snapshots__/opencode/default/pydantic_gen.py @@ -0,0 +1,1612 @@ +# This file is auto-generated by @hey-api/openapi-python + +from enum import Enum +from pydantic import BaseModel, Field +from typing import Any, Literal, Optional, TypeAlias, Union + + +class EventInstallationUpdatedProperties(BaseModel): + version: str + + +class EventInstallationUpdated(BaseModel): + type_: Literal["installation.updated"] = Field(..., alias="type") + properties: EventInstallationUpdatedProperties + + +class EventInstallationUpdateAvailableProperties(BaseModel): + version: str + + +class EventInstallationUpdateAvailable(BaseModel): + type_: Literal["installation.update-available"] = Field(..., alias="type") + properties: EventInstallationUpdateAvailableProperties + + +class ProjectIcon(BaseModel): + url: Optional[str] = Field(default=None) + color: Optional[str] = Field(default=None) + + +class ProjectTime(BaseModel): + created: float + updated: float + initialized: Optional[float] = Field(default=None) + + +class Project(BaseModel): + id_: str = Field(..., alias="id") + worktree: str + vcs: Optional[Literal["git"]] = Field(default=None) + name: Optional[str] = Field(default=None) + icon: Optional[ProjectIcon] = Field(default=None) + time: ProjectTime + + +class EventProjectUpdated(BaseModel): + type_: Literal["project.updated"] = Field(..., alias="type") + properties: Project + + +class EventServerInstanceDisposedProperties(BaseModel): + directory: str + + +class EventServerInstanceDisposed(BaseModel): + type_: Literal["server.instance.disposed"] = Field(..., alias="type") + properties: EventServerInstanceDisposedProperties + + +class EventLspClientDiagnosticsProperties(BaseModel): + server_id: str = Field(..., alias="serverID") + path: str + + +class EventLspClientDiagnostics(BaseModel): + type_: Literal["lsp.client.diagnostics"] = Field(..., alias="type") + properties: EventLspClientDiagnosticsProperties + + +class EventLspUpdated(BaseModel): + type_: Literal["lsp.updated"] = Field(..., alias="type") + properties: dict[str, Any] + + +class FileDiff(BaseModel): + file: str + before: str + after: str + additions: float + deletions: float + + +class UserMessageTime(BaseModel): + created: float + + +class UserMessageSummary(BaseModel): + title: Optional[str] = Field(default=None) + body: Optional[str] = Field(default=None) + diffs: list[FileDiff] + + +class UserMessageModel(BaseModel): + provider_id: str = Field(..., alias="providerID") + model_id: str = Field(..., alias="modelID") + + +class UserMessage(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + role: Literal["user"] + time: UserMessageTime + summary: Optional[UserMessageSummary] = Field(default=None) + agent: str + model: UserMessageModel + system: Optional[str] = Field(default=None) + tools: Optional[dict[str, Any]] = Field(default=None) + variant: Optional[str] = Field(default=None) + + +class ProviderAuthErrorData(BaseModel): + provider_id: str = Field(..., alias="providerID") + message: str + + +class ProviderAuthError(BaseModel): + name: Literal["ProviderAuthError"] + data: ProviderAuthErrorData + + +class UnknownErrorData(BaseModel): + message: str + + +class UnknownError(BaseModel): + name: Literal["UnknownError"] + data: UnknownErrorData + + +class MessageOutputLengthError(BaseModel): + name: Literal["MessageOutputLengthError"] + data: dict[str, Any] + + +class MessageAbortedErrorData(BaseModel): + message: str + + +class MessageAbortedError(BaseModel): + name: Literal["MessageAbortedError"] + data: MessageAbortedErrorData + + +class ApiErrorData(BaseModel): + message: str + status_code: Optional[float] = Field(default=None, alias="statusCode") + is_retryable: bool = Field(..., alias="isRetryable") + response_headers: Optional[dict[str, Any]] = Field(default=None, alias="responseHeaders") + response_body: Optional[str] = Field(default=None, alias="responseBody") + metadata: Optional[dict[str, Any]] = Field(default=None) + + +class ApiError(BaseModel): + name: Literal["APIError"] + data: ApiErrorData + + +class AssistantMessageTime(BaseModel): + created: float + completed: Optional[float] = Field(default=None) + + +class AssistantMessagePath(BaseModel): + cwd: str + root: str + + +class AssistantMessageTokensCache(BaseModel): + read: float + write: float + + +class AssistantMessageTokens(BaseModel): + input_: float = Field(..., alias="input") + output: float + reasoning: float + cache: AssistantMessageTokensCache + + +class AssistantMessage(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + role: Literal["assistant"] + time: AssistantMessageTime + error: Optional[Union[ProviderAuthError, UnknownError, MessageOutputLengthError, MessageAbortedError, ApiError]] = Field(default=None) + parent_id: str = Field(..., alias="parentID") + model_id: str = Field(..., alias="modelID") + provider_id: str = Field(..., alias="providerID") + mode: str + agent: str + path: AssistantMessagePath + summary: Optional[bool] = Field(default=None) + cost: float + tokens: AssistantMessageTokens + finish: Optional[str] = Field(default=None) + + +Message: TypeAlias = Union[UserMessage, AssistantMessage] + + +class EventMessageUpdatedProperties(BaseModel): + info: Message + + +class EventMessageUpdated(BaseModel): + type_: Literal["message.updated"] = Field(..., alias="type") + properties: EventMessageUpdatedProperties + + +class EventMessageRemovedProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + + +class EventMessageRemoved(BaseModel): + type_: Literal["message.removed"] = Field(..., alias="type") + properties: EventMessageRemovedProperties + + +class TextPartTime(BaseModel): + start: float + end: Optional[float] = Field(default=None) + + +class TextPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["text"] = Field(..., alias="type") + text: str + synthetic: Optional[bool] = Field(default=None) + ignored: Optional[bool] = Field(default=None) + time: Optional[TextPartTime] = Field(default=None) + metadata: Optional[dict[str, Any]] = Field(default=None) + + +class ReasoningPartTime(BaseModel): + start: float + end: Optional[float] = Field(default=None) + + +class ReasoningPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["reasoning"] = Field(..., alias="type") + text: str + metadata: Optional[dict[str, Any]] = Field(default=None) + time: ReasoningPartTime + + +class FilePartSourceText(BaseModel): + value: str + start: int = Field(..., ge=-9007199254740991, le=9007199254740991) + end: int = Field(..., ge=-9007199254740991, le=9007199254740991) + + +class FileSource(BaseModel): + text: FilePartSourceText + type_: Literal["file"] = Field(..., alias="type") + path: str + + +class RangeStart(BaseModel): + line: float + character: float + + +class RangeEnd(BaseModel): + line: float + character: float + + +class Range(BaseModel): + start: RangeStart + end: RangeEnd + + +class SymbolSource(BaseModel): + text: FilePartSourceText + type_: Literal["symbol"] = Field(..., alias="type") + path: str + range_: Range = Field(..., alias="range") + name: str + kind: int = Field(..., ge=-9007199254740991, le=9007199254740991) + + +FilePartSource: TypeAlias = Union[FileSource, SymbolSource] + + +class FilePart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["file"] = Field(..., alias="type") + mime: str + filename: Optional[str] = Field(default=None) + url: str + source: Optional[FilePartSource] = Field(default=None) + + +class ToolStatePending(BaseModel): + status: Literal["pending"] + input_: dict[str, Any] = Field(..., alias="input") + raw: str + + +class ToolStateRunningTime(BaseModel): + start: float + + +class ToolStateRunning(BaseModel): + status: Literal["running"] + input_: dict[str, Any] = Field(..., alias="input") + title: Optional[str] = Field(default=None) + metadata: Optional[dict[str, Any]] = Field(default=None) + time: ToolStateRunningTime + + +class ToolStateCompletedTime(BaseModel): + start: float + end: float + compacted: Optional[float] = Field(default=None) + + +class ToolStateCompleted(BaseModel): + status: Literal["completed"] + input_: dict[str, Any] = Field(..., alias="input") + output: str + title: str + metadata: dict[str, Any] + time: ToolStateCompletedTime + attachments: Optional[list[FilePart]] = Field(default=None) + + +class ToolStateErrorTime(BaseModel): + start: float + end: float + + +class ToolStateError(BaseModel): + status: Literal["error"] + input_: dict[str, Any] = Field(..., alias="input") + error: str + metadata: Optional[dict[str, Any]] = Field(default=None) + time: ToolStateErrorTime + + +ToolState: TypeAlias = Union[ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError] + + +class ToolPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["tool"] = Field(..., alias="type") + call_id: str = Field(..., alias="callID") + tool: str + state: ToolState + metadata: Optional[dict[str, Any]] = Field(default=None) + + +class StepStartPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["step-start"] = Field(..., alias="type") + snapshot: Optional[str] = Field(default=None) + + +class StepFinishPartTokensCache(BaseModel): + read: float + write: float + + +class StepFinishPartTokens(BaseModel): + input_: float = Field(..., alias="input") + output: float + reasoning: float + cache: StepFinishPartTokensCache + + +class StepFinishPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["step-finish"] = Field(..., alias="type") + reason: str + snapshot: Optional[str] = Field(default=None) + cost: float + tokens: StepFinishPartTokens + + +class SnapshotPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["snapshot"] = Field(..., alias="type") + snapshot: str + + +class PatchPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["patch"] = Field(..., alias="type") + hash_: str = Field(..., alias="hash") + files: list[str] + + +class AgentPartSource(BaseModel): + value: str + start: int = Field(..., ge=-9007199254740991, le=9007199254740991) + end: int = Field(..., ge=-9007199254740991, le=9007199254740991) + + +class AgentPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["agent"] = Field(..., alias="type") + name: str + source: Optional[AgentPartSource] = Field(default=None) + + +class RetryPartTime(BaseModel): + created: float + + +class RetryPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["retry"] = Field(..., alias="type") + attempt: float + error: ApiError + time: RetryPartTime + + +class CompactionPart(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["compaction"] = Field(..., alias="type") + auto: bool + + +class Part(BaseModel): + id_: str = Field(..., alias="id") + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + type_: Literal["subtask"] = Field(..., alias="type") + prompt: str + description: str + agent: str + command: Optional[str] = Field(default=None) + + +Part_2: TypeAlias = Union[TextPart, Part, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart] + + +class EventMessagePartUpdatedProperties(BaseModel): + part: Part_2 + delta: Optional[str] = Field(default=None) + + +class EventMessagePartUpdated(BaseModel): + type_: Literal["message.part.updated"] = Field(..., alias="type") + properties: EventMessagePartUpdatedProperties + + +class EventMessagePartRemovedProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + message_id: str = Field(..., alias="messageID") + part_id: str = Field(..., alias="partID") + + +class EventMessagePartRemoved(BaseModel): + type_: Literal["message.part.removed"] = Field(..., alias="type") + properties: EventMessagePartRemovedProperties + + +class PermissionRequestTool(BaseModel): + message_id: str = Field(..., alias="messageID") + call_id: str = Field(..., alias="callID") + + +class PermissionRequest(BaseModel): + id_: str = Field(..., alias="id", pattern="^per.*") + session_id: str = Field(..., alias="sessionID", pattern="^ses.*") + permission: str + patterns: list[str] + metadata: dict[str, Any] + always: list[str] + tool: Optional[PermissionRequestTool] = Field(default=None) + + +class EventPermissionAsked(BaseModel): + type_: Literal["permission.asked"] = Field(..., alias="type") + properties: PermissionRequest + + +class EventPermissionRepliedPropertiesReply(Enum, str): + ONCE = "once" + ALWAYS = "always" + REJECT = "reject" + + +class EventPermissionRepliedProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + request_id: str = Field(..., alias="requestID") + reply: EventPermissionRepliedPropertiesReply + + +class EventPermissionReplied(BaseModel): + type_: Literal["permission.replied"] = Field(..., alias="type") + properties: EventPermissionRepliedProperties + + +class SessionStatus(BaseModel): + type_: Literal["idle"] = Field(..., alias="type") + + +class SessionStatus_2(BaseModel): + type_: Literal["retry"] = Field(..., alias="type") + attempt: float + message: str + next_: float = Field(..., alias="next") + + +class SessionStatus_3(BaseModel): + type_: Literal["busy"] = Field(..., alias="type") + + +SessionStatus_4: TypeAlias = Union[SessionStatus, SessionStatus_2, SessionStatus_3] + + +class EventSessionStatusProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + status: SessionStatus_4 + + +class EventSessionStatus(BaseModel): + type_: Literal["session.status"] = Field(..., alias="type") + properties: EventSessionStatusProperties + + +class EventSessionIdleProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + + +class EventSessionIdle(BaseModel): + type_: Literal["session.idle"] = Field(..., alias="type") + properties: EventSessionIdleProperties + + +class EventSessionCompactedProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + + +class EventSessionCompacted(BaseModel): + type_: Literal["session.compacted"] = Field(..., alias="type") + properties: EventSessionCompactedProperties + + +class EventFileEditedProperties(BaseModel): + file: str + + +class EventFileEdited(BaseModel): + type_: Literal["file.edited"] = Field(..., alias="type") + properties: EventFileEditedProperties + + +class Todo(BaseModel): + content: str = Field(..., description="Brief description of the task") + status: str = Field(..., description="Current status of the task: pending, in_progress, completed, cancelled") + priority: str = Field(..., description="Priority level of the task: high, medium, low") + id_: str = Field(..., alias="id", description="Unique identifier for the todo item") + + +class EventTodoUpdatedProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + todos: list[Todo] + + +class EventTodoUpdated(BaseModel): + type_: Literal["todo.updated"] = Field(..., alias="type") + properties: EventTodoUpdatedProperties + + +class EventTuiPromptAppendProperties(BaseModel): + text: str + + +class EventTuiPromptAppend(BaseModel): + type_: Literal["tui.prompt.append"] = Field(..., alias="type") + properties: EventTuiPromptAppendProperties + + +class EventTuiCommandExecutePropertiesCommand(Enum, str): + SESSION_LIST = "session.list" + SESSION_NEW = "session.new" + SESSION_SHARE = "session.share" + SESSION_INTERRUPT = "session.interrupt" + SESSION_COMPACT = "session.compact" + SESSION_PAGE_UP = "session.page.up" + SESSION_PAGE_DOWN = "session.page.down" + SESSION_HALF_PAGE_UP = "session.half.page.up" + SESSION_HALF_PAGE_DOWN = "session.half.page.down" + SESSION_FIRST = "session.first" + SESSION_LAST = "session.last" + PROMPT_CLEAR = "prompt.clear" + PROMPT_SUBMIT = "prompt.submit" + AGENT_CYCLE = "agent.cycle" + + +class EventTuiCommandExecuteProperties(BaseModel): + command: Union[EventTuiCommandExecutePropertiesCommand, str] + + +class EventTuiCommandExecute(BaseModel): + type_: Literal["tui.command.execute"] = Field(..., alias="type") + properties: EventTuiCommandExecuteProperties + + +class EventTuiToastShowPropertiesVariant(Enum, str): + INFO = "info" + SUCCESS = "success" + WARNING = "warning" + ERROR = "error" + + +class EventTuiToastShowProperties(BaseModel): + title: Optional[str] = Field(default=None) + message: str + variant: EventTuiToastShowPropertiesVariant + duration: Optional[float] = Field(default=5000, description="Duration in milliseconds") + + +class EventTuiToastShow(BaseModel): + type_: Literal["tui.toast.show"] = Field(..., alias="type") + properties: EventTuiToastShowProperties + + +class EventMcpToolsChangedProperties(BaseModel): + server: str + + +class EventMcpToolsChanged(BaseModel): + type_: Literal["mcp.tools.changed"] = Field(..., alias="type") + properties: EventMcpToolsChangedProperties + + +class EventCommandExecutedProperties(BaseModel): + name: str + session_id: str = Field(..., alias="sessionID", pattern="^ses.*") + arguments: str + message_id: str = Field(..., alias="messageID", pattern="^msg.*") + + +class EventCommandExecuted(BaseModel): + type_: Literal["command.executed"] = Field(..., alias="type") + properties: EventCommandExecutedProperties + + +class PermissionAction(Enum, str): + ALLOW = "allow" + DENY = "deny" + ASK = "ask" + + +class PermissionRule(BaseModel): + permission: str + pattern: str + action: PermissionAction + + +PermissionRuleset: TypeAlias = list[PermissionRule] + + +class SessionSummary(BaseModel): + additions: float + deletions: float + files: float + diffs: Optional[list[FileDiff]] = Field(default=None) + + +class SessionShare(BaseModel): + url: str + + +class SessionTime(BaseModel): + created: float + updated: float + compacting: Optional[float] = Field(default=None) + archived: Optional[float] = Field(default=None) + + +class SessionRevert(BaseModel): + message_id: str = Field(..., alias="messageID") + part_id: Optional[str] = Field(default=None, alias="partID") + snapshot: Optional[str] = Field(default=None) + diff: Optional[str] = Field(default=None) + + +class Session(BaseModel): + id_: str = Field(..., alias="id", pattern="^ses.*") + project_id: str = Field(..., alias="projectID") + directory: str + parent_id: Optional[str] = Field(default=None, alias="parentID", pattern="^ses.*") + summary: Optional[SessionSummary] = Field(default=None) + share: Optional[SessionShare] = Field(default=None) + title: str + version: str + time: SessionTime + permission: Optional[PermissionRuleset] = Field(default=None) + revert: Optional[SessionRevert] = Field(default=None) + + +class EventSessionCreatedProperties(BaseModel): + info: Session + + +class EventSessionCreated(BaseModel): + type_: Literal["session.created"] = Field(..., alias="type") + properties: EventSessionCreatedProperties + + +class EventSessionUpdatedProperties(BaseModel): + info: Session + + +class EventSessionUpdated(BaseModel): + type_: Literal["session.updated"] = Field(..., alias="type") + properties: EventSessionUpdatedProperties + + +class EventSessionDeletedProperties(BaseModel): + info: Session + + +class EventSessionDeleted(BaseModel): + type_: Literal["session.deleted"] = Field(..., alias="type") + properties: EventSessionDeletedProperties + + +class EventSessionDiffProperties(BaseModel): + session_id: str = Field(..., alias="sessionID") + diff: list[FileDiff] + + +class EventSessionDiff(BaseModel): + type_: Literal["session.diff"] = Field(..., alias="type") + properties: EventSessionDiffProperties + + +class EventSessionErrorProperties(BaseModel): + session_id: Optional[str] = Field(default=None, alias="sessionID") + error: Optional[Union[ProviderAuthError, UnknownError, MessageOutputLengthError, MessageAbortedError, ApiError]] = Field(default=None) + + +class EventSessionError(BaseModel): + type_: Literal["session.error"] = Field(..., alias="type") + properties: EventSessionErrorProperties + + +class EventFileWatcherUpdatedProperties(BaseModel): + file: str + event: Union[Literal["add"], Literal["change"], Literal["unlink"]] + + +class EventFileWatcherUpdated(BaseModel): + type_: Literal["file.watcher.updated"] = Field(..., alias="type") + properties: EventFileWatcherUpdatedProperties + + +class EventVcsBranchUpdatedProperties(BaseModel): + branch: Optional[str] = Field(default=None) + + +class EventVcsBranchUpdated(BaseModel): + type_: Literal["vcs.branch.updated"] = Field(..., alias="type") + properties: EventVcsBranchUpdatedProperties + + +class PtyStatus(Enum, str): + RUNNING = "running" + EXITED = "exited" + + +class Pty(BaseModel): + id_: str = Field(..., alias="id", pattern="^pty.*") + title: str + command: str + args: list[str] + cwd: str + status: PtyStatus + pid: float + + +class EventPtyCreatedProperties(BaseModel): + info: Pty + + +class EventPtyCreated(BaseModel): + type_: Literal["pty.created"] = Field(..., alias="type") + properties: EventPtyCreatedProperties + + +class EventPtyUpdatedProperties(BaseModel): + info: Pty + + +class EventPtyUpdated(BaseModel): + type_: Literal["pty.updated"] = Field(..., alias="type") + properties: EventPtyUpdatedProperties + + +class EventPtyExitedProperties(BaseModel): + id_: str = Field(..., alias="id", pattern="^pty.*") + exit_code: float = Field(..., alias="exitCode") + + +class EventPtyExited(BaseModel): + type_: Literal["pty.exited"] = Field(..., alias="type") + properties: EventPtyExitedProperties + + +class EventPtyDeletedProperties(BaseModel): + id_: str = Field(..., alias="id", pattern="^pty.*") + + +class EventPtyDeleted(BaseModel): + type_: Literal["pty.deleted"] = Field(..., alias="type") + properties: EventPtyDeletedProperties + + +class EventServerConnected(BaseModel): + type_: Literal["server.connected"] = Field(..., alias="type") + properties: dict[str, Any] + + +class EventGlobalDisposed(BaseModel): + type_: Literal["global.disposed"] = Field(..., alias="type") + properties: dict[str, Any] + + +Event: TypeAlias = Union[EventInstallationUpdated, EventInstallationUpdateAvailable, EventProjectUpdated, EventServerInstanceDisposed, EventLspClientDiagnostics, EventLspUpdated, EventMessageUpdated, EventMessageRemoved, EventMessagePartUpdated, EventMessagePartRemoved, EventPermissionAsked, EventPermissionReplied, EventSessionStatus, EventSessionIdle, EventSessionCompacted, EventFileEdited, EventTodoUpdated, EventTuiPromptAppend, EventTuiCommandExecute, EventTuiToastShow, EventMcpToolsChanged, EventCommandExecuted, EventSessionCreated, EventSessionUpdated, EventSessionDeleted, EventSessionDiff, EventSessionError, EventFileWatcherUpdated, EventVcsBranchUpdated, EventPtyCreated, EventPtyUpdated, EventPtyExited, EventPtyDeleted, EventServerConnected, EventGlobalDisposed] + + +class GlobalEvent(BaseModel): + directory: str + payload: Event + + +class BadRequestError(BaseModel): + data: Any + errors: list[dict[str, Any]] + success: Literal[False] + + +class NotFoundErrorData(BaseModel): + message: str + + +class NotFoundError(BaseModel): + name: Literal["NotFoundError"] + data: NotFoundErrorData + + +class KeybindsConfig(BaseModel): + leader: Optional[str] = Field(default="ctrl+x", description="Leader key for keybind combinations") + app_exit: Optional[str] = Field(default="ctrl+c,ctrl+d,q", description="Exit the application") + editor_open: Optional[str] = Field(default="e", description="Open external editor") + theme_list: Optional[str] = Field(default="t", description="List available themes") + sidebar_toggle: Optional[str] = Field(default="b", description="Toggle sidebar") + scrollbar_toggle: Optional[str] = Field(default="none", description="Toggle session scrollbar") + username_toggle: Optional[str] = Field(default="none", description="Toggle username visibility") + status_view: Optional[str] = Field(default="s", description="View status") + session_export: Optional[str] = Field(default="x", description="Export session to editor") + session_new: Optional[str] = Field(default="n", description="Create a new session") + session_list: Optional[str] = Field(default="l", description="List all sessions") + session_timeline: Optional[str] = Field(default="g", description="Show session timeline") + session_fork: Optional[str] = Field(default="none", description="Fork session from message") + session_rename: Optional[str] = Field(default="none", description="Rename session") + session_share: Optional[str] = Field(default="none", description="Share current session") + session_unshare: Optional[str] = Field(default="none", description="Unshare current session") + session_interrupt: Optional[str] = Field(default="escape", description="Interrupt current session") + session_compact: Optional[str] = Field(default="c", description="Compact the session") + messages_page_up: Optional[str] = Field(default="pageup", description="Scroll messages up by one page") + messages_page_down: Optional[str] = Field(default="pagedown", description="Scroll messages down by one page") + messages_half_page_up: Optional[str] = Field(default="ctrl+alt+u", description="Scroll messages up by half page") + messages_half_page_down: Optional[str] = Field(default="ctrl+alt+d", description="Scroll messages down by half page") + messages_first: Optional[str] = Field(default="ctrl+g,home", description="Navigate to first message") + messages_last: Optional[str] = Field(default="ctrl+alt+g,end", description="Navigate to last message") + messages_next: Optional[str] = Field(default="none", description="Navigate to next message") + messages_previous: Optional[str] = Field(default="none", description="Navigate to previous message") + messages_last_user: Optional[str] = Field(default="none", description="Navigate to last user message") + messages_copy: Optional[str] = Field(default="y", description="Copy message") + messages_undo: Optional[str] = Field(default="u", description="Undo message") + messages_redo: Optional[str] = Field(default="r", description="Redo message") + messages_toggle_conceal: Optional[str] = Field(default="h", description="Toggle code block concealment in messages") + tool_details: Optional[str] = Field(default="none", description="Toggle tool details visibility") + model_list: Optional[str] = Field(default="m", description="List available models") + model_cycle_recent: Optional[str] = Field(default="f2", description="Next recently used model") + model_cycle_recent_reverse: Optional[str] = Field(default="shift+f2", description="Previous recently used model") + model_cycle_favorite: Optional[str] = Field(default="none", description="Next favorite model") + model_cycle_favorite_reverse: Optional[str] = Field(default="none", description="Previous favorite model") + command_list: Optional[str] = Field(default="ctrl+p", description="List available commands") + agent_list: Optional[str] = Field(default="a", description="List agents") + agent_cycle: Optional[str] = Field(default="tab", description="Next agent") + agent_cycle_reverse: Optional[str] = Field(default="shift+tab", description="Previous agent") + variant_cycle: Optional[str] = Field(default="ctrl+t", description="Cycle model variants") + input_clear: Optional[str] = Field(default="ctrl+c", description="Clear input field") + input_paste: Optional[str] = Field(default="ctrl+v", description="Paste from clipboard") + input_submit: Optional[str] = Field(default="return", description="Submit input") + input_newline: Optional[str] = Field(default="shift+return,ctrl+return,alt+return,ctrl+j", description="Insert newline in input") + input_move_left: Optional[str] = Field(default="left,ctrl+b", description="Move cursor left in input") + input_move_right: Optional[str] = Field(default="right,ctrl+f", description="Move cursor right in input") + input_move_up: Optional[str] = Field(default="up", description="Move cursor up in input") + input_move_down: Optional[str] = Field(default="down", description="Move cursor down in input") + input_select_left: Optional[str] = Field(default="shift+left", description="Select left in input") + input_select_right: Optional[str] = Field(default="shift+right", description="Select right in input") + input_select_up: Optional[str] = Field(default="shift+up", description="Select up in input") + input_select_down: Optional[str] = Field(default="shift+down", description="Select down in input") + input_line_home: Optional[str] = Field(default="ctrl+a", description="Move to start of line in input") + input_line_end: Optional[str] = Field(default="ctrl+e", description="Move to end of line in input") + input_select_line_home: Optional[str] = Field(default="ctrl+shift+a", description="Select to start of line in input") + input_select_line_end: Optional[str] = Field(default="ctrl+shift+e", description="Select to end of line in input") + input_visual_line_home: Optional[str] = Field(default="alt+a", description="Move to start of visual line in input") + input_visual_line_end: Optional[str] = Field(default="alt+e", description="Move to end of visual line in input") + input_select_visual_line_home: Optional[str] = Field(default="alt+shift+a", description="Select to start of visual line in input") + input_select_visual_line_end: Optional[str] = Field(default="alt+shift+e", description="Select to end of visual line in input") + input_buffer_home: Optional[str] = Field(default="home", description="Move to start of buffer in input") + input_buffer_end: Optional[str] = Field(default="end", description="Move to end of buffer in input") + input_select_buffer_home: Optional[str] = Field(default="shift+home", description="Select to start of buffer in input") + input_select_buffer_end: Optional[str] = Field(default="shift+end", description="Select to end of buffer in input") + input_delete_line: Optional[str] = Field(default="ctrl+shift+d", description="Delete line in input") + input_delete_to_line_end: Optional[str] = Field(default="ctrl+k", description="Delete to end of line in input") + input_delete_to_line_start: Optional[str] = Field(default="ctrl+u", description="Delete to start of line in input") + input_backspace: Optional[str] = Field(default="backspace,shift+backspace", description="Backspace in input") + input_delete: Optional[str] = Field(default="ctrl+d,delete,shift+delete", description="Delete character in input") + input_undo: Optional[str] = Field(default="ctrl+-,super+z", description="Undo in input") + input_redo: Optional[str] = Field(default="ctrl+.,super+shift+z", description="Redo in input") + input_word_forward: Optional[str] = Field(default="alt+f,alt+right,ctrl+right", description="Move word forward in input") + input_word_backward: Optional[str] = Field(default="alt+b,alt+left,ctrl+left", description="Move word backward in input") + input_select_word_forward: Optional[str] = Field(default="alt+shift+f,alt+shift+right", description="Select word forward in input") + input_select_word_backward: Optional[str] = Field(default="alt+shift+b,alt+shift+left", description="Select word backward in input") + input_delete_word_forward: Optional[str] = Field(default="alt+d,alt+delete,ctrl+delete", description="Delete word forward in input") + input_delete_word_backward: Optional[str] = Field(default="ctrl+w,ctrl+backspace,alt+backspace", description="Delete word backward in input") + history_previous: Optional[str] = Field(default="up", description="Previous history item") + history_next: Optional[str] = Field(default="down", description="Next history item") + session_child_cycle: Optional[str] = Field(default="right", description="Next child session") + session_child_cycle_reverse: Optional[str] = Field(default="left", description="Previous child session") + session_parent: Optional[str] = Field(default="up", description="Go to parent session") + terminal_suspend: Optional[str] = Field(default="ctrl+z", description="Suspend terminal") + terminal_title_toggle: Optional[str] = Field(default="none", description="Toggle terminal title") + tips_toggle: Optional[str] = Field(default="h", description="Toggle tips on home screen") + + +class LogLevel(Enum, str): + DEBUG = "DEBUG" + INFO = "INFO" + WARN = "WARN" + ERROR = "ERROR" + + +class ServerConfig(BaseModel): + port: Optional[int] = Field(default=None, description="Port to listen on", gt=0, le=9007199254740991) + hostname: Optional[str] = Field(default=None, description="Hostname to listen on") + mdns: Optional[bool] = Field(default=None) + cors: Optional[list[str]] = Field(default=None, description="Additional domains to allow for CORS") + + +class PermissionActionConfig(Enum, str): + ASK = "ask" + ALLOW = "allow" + DENY = "deny" + + +PermissionObjectConfig: TypeAlias = dict[str, Any] + + +PermissionRuleConfig: TypeAlias = Union[PermissionActionConfig, PermissionObjectConfig] + + +class PermissionConfig(BaseModel): + read: Optional[PermissionRuleConfig] = Field(default=None) + edit: Optional[PermissionRuleConfig] = Field(default=None) + glob: Optional[PermissionRuleConfig] = Field(default=None) + grep: Optional[PermissionRuleConfig] = Field(default=None) + list_: Optional[PermissionRuleConfig] = Field(default=None, alias="list") + bash: Optional[PermissionRuleConfig] = Field(default=None) + task: Optional[PermissionRuleConfig] = Field(default=None) + external_directory: Optional[PermissionRuleConfig] = Field(default=None) + todowrite: Optional[PermissionActionConfig] = Field(default=None) + todoread: Optional[PermissionActionConfig] = Field(default=None) + webfetch: Optional[PermissionActionConfig] = Field(default=None) + websearch: Optional[PermissionActionConfig] = Field(default=None) + codesearch: Optional[PermissionActionConfig] = Field(default=None) + lsp: Optional[PermissionRuleConfig] = Field(default=None) + doom_loop: Optional[PermissionActionConfig] = Field(default=None) + + +PermissionConfig_2: TypeAlias = Union[PermissionConfig, PermissionActionConfig] + + +AgentConfig: TypeAlias = dict[str, Any] + + +class ProviderConfigModelsValueInterleavedField(Enum, str): + REASONING_CONTENT = "reasoning_content" + REASONING_DETAILS = "reasoning_details" + + +class ProviderConfigModelsValueInterleaved(BaseModel): + field: ProviderConfigModelsValueInterleavedField + + +class ProviderConfigModelsValueCostContextOver200k(BaseModel): + input_: float = Field(..., alias="input") + output: float + cache_read: Optional[float] = Field(default=None) + cache_write: Optional[float] = Field(default=None) + + +class ProviderConfigModelsValueCost(BaseModel): + input_: float = Field(..., alias="input") + output: float + cache_read: Optional[float] = Field(default=None) + cache_write: Optional[float] = Field(default=None) + context_over_200k: Optional[ProviderConfigModelsValueCostContextOver200k] = Field(default=None) + + +class ProviderConfigModelsValueLimit(BaseModel): + context: float + output: float + + +class ProviderConfigModelsValueModalitiesInput(Enum, str): + TEXT = "text" + AUDIO = "audio" + IMAGE = "image" + VIDEO = "video" + PDF = "pdf" + + +class ProviderConfigModelsValueModalitiesOutput(Enum, str): + TEXT = "text" + AUDIO = "audio" + IMAGE = "image" + VIDEO = "video" + PDF = "pdf" + + +class ProviderConfigModelsValueModalities(BaseModel): + input_: list[ProviderConfigModelsValueModalitiesInput] = Field(..., alias="input") + output: list[ProviderConfigModelsValueModalitiesOutput] + + +class ProviderConfigModelsValueStatus(Enum, str): + ALPHA = "alpha" + BETA = "beta" + DEPRECATED = "deprecated" + + +class ProviderConfigModelsValueProvider(BaseModel): + npm: str + + +ProviderConfigModelsValueVariantsValue: TypeAlias = dict[str, Any] + + +class ProviderConfigModelsValue(BaseModel): + id_: Optional[str] = Field(default=None, alias="id") + name: Optional[str] = Field(default=None) + family: Optional[str] = Field(default=None) + release_date: Optional[str] = Field(default=None) + attachment: Optional[bool] = Field(default=None) + reasoning: Optional[bool] = Field(default=None) + temperature: Optional[bool] = Field(default=None) + tool_call: Optional[bool] = Field(default=None) + interleaved: Optional[Union[Literal[True], ProviderConfigModelsValueInterleaved]] = Field(default=None) + cost: Optional[ProviderConfigModelsValueCost] = Field(default=None) + limit: Optional[ProviderConfigModelsValueLimit] = Field(default=None) + modalities: Optional[ProviderConfigModelsValueModalities] = Field(default=None) + experimental: Optional[bool] = Field(default=None) + status: Optional[ProviderConfigModelsValueStatus] = Field(default=None) + options: Optional[dict[str, Any]] = Field(default=None) + headers: Optional[dict[str, Any]] = Field(default=None) + provider: Optional[ProviderConfigModelsValueProvider] = Field(default=None) + variants: Optional[dict[str, Any]] = Field(default=None) + + +ProviderConfigOptions: TypeAlias = dict[str, Any] + + +class ProviderConfig(BaseModel): + api: Optional[str] = Field(default=None) + name: Optional[str] = Field(default=None) + env: Optional[list[str]] = Field(default=None) + id_: Optional[str] = Field(default=None, alias="id") + npm: Optional[str] = Field(default=None) + models: Optional[dict[str, Any]] = Field(default=None) + whitelist: Optional[list[str]] = Field(default=None) + blacklist: Optional[list[str]] = Field(default=None) + options: Optional[ProviderConfigOptions] = Field(default=None) + + +class McpLocalConfig(BaseModel): + type_: Literal["local"] = Field(..., alias="type") + command: list[str] = Field(..., description="Command and arguments to run the MCP server") + environment: Optional[dict[str, Any]] = Field(default=None) + enabled: Optional[bool] = Field(default=None) + timeout: Optional[int] = Field(default=None, description="Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", gt=0, le=9007199254740991) + + +class McpOAuthConfig(BaseModel): + client_id: Optional[str] = Field(default=None, alias="clientId", description="OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.") + client_secret: Optional[str] = Field(default=None, alias="clientSecret", description="OAuth client secret (if required by the authorization server)") + scope: Optional[str] = Field(default=None, description="OAuth scopes to request during authorization") + + +class McpRemoteConfig(BaseModel): + type_: Literal["remote"] = Field(..., alias="type") + url: str = Field(..., description="URL of the remote MCP server") + enabled: Optional[bool] = Field(default=None) + headers: Optional[dict[str, Any]] = Field(default=None) + oauth: Optional[Union[McpOAuthConfig, Literal[False]]] = Field(default=None, description="OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.") + timeout: Optional[int] = Field(default=None, description="Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.", gt=0, le=9007199254740991) + + +class LayoutConfig(Enum, str): + AUTO = "auto" + STRETCH = "stretch" + + +class ConfigTuiScrollAcceleration(BaseModel): + enabled: bool + + +class ConfigTuiDiffStyle(Enum, str): + AUTO = "auto" + STACKED = "stacked" + + +class ConfigTui(BaseModel): + scroll_speed: Optional[float] = Field(default=None, description="TUI scroll speed", ge=0.001) + scroll_acceleration: Optional[ConfigTuiScrollAcceleration] = Field(default=None) + diff_style: Optional[ConfigTuiDiffStyle] = Field(default=None) + + +class ConfigCommandValue(BaseModel): + template: str + description: Optional[str] = Field(default=None) + agent: Optional[str] = Field(default=None) + model: Optional[str] = Field(default=None) + subtask: Optional[bool] = Field(default=None) + + +class ConfigWatcher(BaseModel): + ignore: Optional[list[str]] = Field(default=None) + + +class ConfigShare(Enum, str): + MANUAL = "manual" + AUTO = "auto" + DISABLED = "disabled" + + +class ConfigMode(BaseModel): + build: Optional[AgentConfig] = Field(default=None) + plan: Optional[AgentConfig] = Field(default=None) + + +class ConfigAgent(BaseModel): + plan: Optional[AgentConfig] = Field(default=None) + build: Optional[AgentConfig] = Field(default=None) + general: Optional[AgentConfig] = Field(default=None) + explore: Optional[AgentConfig] = Field(default=None) + title: Optional[AgentConfig] = Field(default=None) + summary: Optional[AgentConfig] = Field(default=None) + compaction: Optional[AgentConfig] = Field(default=None) + + +class ConfigFormatterValue(BaseModel): + disabled: Optional[bool] = Field(default=None) + command: Optional[list[str]] = Field(default=None) + environment: Optional[dict[str, Any]] = Field(default=None) + extensions: Optional[list[str]] = Field(default=None) + + +class ConfigEnterprise(BaseModel): + url: Optional[str] = Field(default=None, description="Enterprise URL") + + +class ConfigCompaction(BaseModel): + auto: Optional[bool] = Field(default=None) + prune: Optional[bool] = Field(default=None) + + +class ConfigExperimentalHookFileEditedValue(BaseModel): + command: list[str] + environment: Optional[dict[str, Any]] = Field(default=None) + + +class ConfigExperimentalHookSessionCompleted(BaseModel): + command: list[str] + environment: Optional[dict[str, Any]] = Field(default=None) + + +class ConfigExperimentalHook(BaseModel): + file_edited: Optional[dict[str, Any]] = Field(default=None) + session_completed: Optional[list[ConfigExperimentalHookSessionCompleted]] = Field(default=None) + + +class ConfigExperimental(BaseModel): + hook: Optional[ConfigExperimentalHook] = Field(default=None) + chat_max_retries: Optional[float] = Field(default=None, alias="chatMaxRetries", description="Number of retries for chat completions on failure") + disable_paste_summary: Optional[bool] = Field(default=None) + batch_tool: Optional[bool] = Field(default=None) + open_telemetry: Optional[bool] = Field(default=None, alias="openTelemetry") + primary_tools: Optional[list[str]] = Field(default=None, description="Tools that should only be available to primary agents.") + continue_loop_on_deny: Optional[bool] = Field(default=None) + mcp_timeout: Optional[int] = Field(default=None, description="Timeout in milliseconds for model context protocol (MCP) requests", gt=0, le=9007199254740991) + + +class Config(BaseModel): + schema: Optional[str] = Field(default=None, alias="$schema", description="JSON schema reference for configuration validation") + theme: Optional[str] = Field(default=None, description="Theme name to use for the interface") + keybinds: Optional[KeybindsConfig] = Field(default=None) + log_level: Optional[LogLevel] = Field(default=None, alias="logLevel") + tui: Optional[ConfigTui] = Field(default=None) + server: Optional[ServerConfig] = Field(default=None) + command: Optional[dict[str, Any]] = Field(default=None) + watcher: Optional[ConfigWatcher] = Field(default=None) + plugin: Optional[list[str]] = Field(default=None) + snapshot: Optional[bool] = Field(default=None) + share: Optional[ConfigShare] = Field(default=None) + autoshare: Optional[bool] = Field(default=None) + autoupdate: Optional[Union[bool, Literal["notify"]]] = Field(default=None, description="Automatically update to the latest version. Set to true to auto-update, false to disable, or 'notify' to show update notifications") + disabled_providers: Optional[list[str]] = Field(default=None, description="Disable providers that are loaded automatically") + enabled_providers: Optional[list[str]] = Field(default=None, description="When set, ONLY these providers will be enabled. All other providers will be ignored") + model: Optional[str] = Field(default=None, description="Model to use in the format of provider/model, eg anthropic/claude-2") + small_model: Optional[str] = Field(default=None, description="Small model to use for tasks like title generation in the format of provider/model") + default_agent: Optional[str] = Field(default=None, description="Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.") + username: Optional[str] = Field(default=None, description="Custom username to display in conversations instead of system username") + mode: Optional[ConfigMode] = Field(default=None) + agent: Optional[ConfigAgent] = Field(default=None) + provider: Optional[dict[str, Any]] = Field(default=None) + mcp: Optional[dict[str, Any]] = Field(default=None) + formatter: Optional[Union[Literal[False], dict[str, Any]]] = Field(default=None) + lsp: Optional[Union[Literal[False], dict[str, Any]]] = Field(default=None) + instructions: Optional[list[str]] = Field(default=None, description="Additional instruction files or patterns to include") + layout: Optional[LayoutConfig] = Field(default=None) + permission: Optional[PermissionConfig_2] = Field(default=None) + tools: Optional[dict[str, Any]] = Field(default=None) + enterprise: Optional[ConfigEnterprise] = Field(default=None) + compaction: Optional[ConfigCompaction] = Field(default=None) + experimental: Optional[ConfigExperimental] = Field(default=None) + + +ToolIds: TypeAlias = list[str] + + +class ToolListItem(BaseModel): + id_: str = Field(..., alias="id") + description: str + parameters: Any + + +ToolList: TypeAlias = list[ToolListItem] + + +class Path(BaseModel): + home: str + state: str + config: str + worktree: str + directory: str + + +class VcsInfo(BaseModel): + branch: str + + +class TextPartInputTime(BaseModel): + start: float + end: Optional[float] = Field(default=None) + + +class TextPartInput(BaseModel): + id_: Optional[str] = Field(default=None, alias="id") + type_: Literal["text"] = Field(..., alias="type") + text: str + synthetic: Optional[bool] = Field(default=None) + ignored: Optional[bool] = Field(default=None) + time: Optional[TextPartInputTime] = Field(default=None) + metadata: Optional[dict[str, Any]] = Field(default=None) + + +class FilePartInput(BaseModel): + id_: Optional[str] = Field(default=None, alias="id") + type_: Literal["file"] = Field(..., alias="type") + mime: str + filename: Optional[str] = Field(default=None) + url: str + source: Optional[FilePartSource] = Field(default=None) + + +class AgentPartInputSource(BaseModel): + value: str + start: int = Field(..., ge=-9007199254740991, le=9007199254740991) + end: int = Field(..., ge=-9007199254740991, le=9007199254740991) + + +class AgentPartInput(BaseModel): + id_: Optional[str] = Field(default=None, alias="id") + type_: Literal["agent"] = Field(..., alias="type") + name: str + source: Optional[AgentPartInputSource] = Field(default=None) + + +class SubtaskPartInput(BaseModel): + id_: Optional[str] = Field(default=None, alias="id") + type_: Literal["subtask"] = Field(..., alias="type") + prompt: str + description: str + agent: str + command: Optional[str] = Field(default=None) + + +class Command(BaseModel): + name: str + description: Optional[str] = Field(default=None) + agent: Optional[str] = Field(default=None) + model: Optional[str] = Field(default=None) + mcp: Optional[bool] = Field(default=None) + template: str + subtask: Optional[bool] = Field(default=None) + hints: list[str] + + +class ModelApi(BaseModel): + id_: str = Field(..., alias="id") + url: str + npm: str + + +class ModelCapabilitiesInput(BaseModel): + text: bool + audio: bool + image: bool + video: bool + pdf: bool + + +class ModelCapabilitiesOutput(BaseModel): + text: bool + audio: bool + image: bool + video: bool + pdf: bool + + +class ModelCapabilitiesInterleavedField(Enum, str): + REASONING_CONTENT = "reasoning_content" + REASONING_DETAILS = "reasoning_details" + + +class ModelCapabilitiesInterleaved(BaseModel): + field: ModelCapabilitiesInterleavedField + + +class ModelCapabilities(BaseModel): + temperature: bool + reasoning: bool + attachment: bool + toolcall: bool + input_: ModelCapabilitiesInput = Field(..., alias="input") + output: ModelCapabilitiesOutput + interleaved: Union[bool, ModelCapabilitiesInterleaved] + + +class ModelCostCache(BaseModel): + read: float + write: float + + +class ModelCostExperimentalOver200kCache(BaseModel): + read: float + write: float + + +class ModelCostExperimentalOver200k(BaseModel): + input_: float = Field(..., alias="input") + output: float + cache: ModelCostExperimentalOver200kCache + + +class ModelCost(BaseModel): + input_: float = Field(..., alias="input") + output: float + cache: ModelCostCache + experimental_over200k: Optional[ModelCostExperimentalOver200k] = Field(default=None, alias="experimentalOver200K") + + +class ModelLimit(BaseModel): + context: float + output: float + + +class ModelStatus(Enum, str): + ALPHA = "alpha" + BETA = "beta" + DEPRECATED = "deprecated" + ACTIVE = "active" + + +class Model(BaseModel): + id_: str = Field(..., alias="id") + provider_id: str = Field(..., alias="providerID") + api: ModelApi + name: str + family: Optional[str] = Field(default=None) + capabilities: ModelCapabilities + cost: ModelCost + limit: ModelLimit + status: ModelStatus + options: dict[str, Any] + headers: dict[str, Any] + release_date: str + variants: Optional[dict[str, Any]] = Field(default=None) + + +class ProviderSource(Enum, str): + ENV = "env" + CONFIG = "config" + CUSTOM = "custom" + API = "api" + + +class Provider(BaseModel): + id_: str = Field(..., alias="id") + name: str + source: ProviderSource + env: list[str] + key: Optional[str] = Field(default=None) + options: dict[str, Any] + models: dict[str, Any] + + +class ProviderAuthMethod(BaseModel): + type_: Union[Literal["oauth"], Literal["api"]] = Field(..., alias="type") + label: str + + +class ProviderAuthAuthorization(BaseModel): + url: str + method: Union[Literal["auto"], Literal["code"]] + instructions: str + + +class SymbolLocation(BaseModel): + uri: str + range_: Range = Field(..., alias="range") + + +class Symbol(BaseModel): + name: str + kind: float + location: SymbolLocation + + +class FileNodeType(Enum, str): + FILE = "file" + DIRECTORY = "directory" + + +class FileNode(BaseModel): + name: str + path: str + absolute: str + type_: FileNodeType = Field(..., alias="type") + ignored: bool + + +class FileContentPatchHunks(BaseModel): + old_start: float = Field(..., alias="oldStart") + old_lines: float = Field(..., alias="oldLines") + new_start: float = Field(..., alias="newStart") + new_lines: float = Field(..., alias="newLines") + lines: list[str] + + +class FileContentPatch(BaseModel): + old_file_name: str = Field(..., alias="oldFileName") + new_file_name: str = Field(..., alias="newFileName") + old_header: Optional[str] = Field(default=None, alias="oldHeader") + new_header: Optional[str] = Field(default=None, alias="newHeader") + hunks: list[FileContentPatchHunks] + index: Optional[str] = Field(default=None) + + +class FileContent(BaseModel): + type_: Literal["text"] = Field(..., alias="type") + content: str + diff: Optional[str] = Field(default=None) + patch: Optional[FileContentPatch] = Field(default=None) + encoding: Optional[Literal["base64"]] = Field(default=None) + mime_type: Optional[str] = Field(default=None, alias="mimeType") + + +class FileStatus(Enum, str): + ADDED = "added" + DELETED = "deleted" + MODIFIED = "modified" + + +class File(BaseModel): + path: str + added: int = Field(..., ge=-9007199254740991, le=9007199254740991) + removed: int = Field(..., ge=-9007199254740991, le=9007199254740991) + status: FileStatus + + +class AgentMode(Enum, str): + SUBAGENT = "subagent" + PRIMARY = "primary" + ALL = "all" + + +class AgentModel(BaseModel): + model_id: str = Field(..., alias="modelID") + provider_id: str = Field(..., alias="providerID") + + +class Agent(BaseModel): + name: str + description: Optional[str] = Field(default=None) + mode: AgentMode + native: Optional[bool] = Field(default=None) + hidden: Optional[bool] = Field(default=None) + top_p: Optional[float] = Field(default=None, alias="topP") + temperature: Optional[float] = Field(default=None) + color: Optional[str] = Field(default=None) + permission: PermissionRuleset + model: Optional[AgentModel] = Field(default=None) + prompt: Optional[str] = Field(default=None) + options: dict[str, Any] + steps: Optional[int] = Field(default=None, gt=0, le=9007199254740991) + + +class McpStatusConnected(BaseModel): + status: Literal["connected"] + + +class McpStatusDisabled(BaseModel): + status: Literal["disabled"] + + +class McpStatusFailed(BaseModel): + status: Literal["failed"] + error: str + + +class McpStatusNeedsAuth(BaseModel): + status: Literal["needs_auth"] + + +class McpStatusNeedsClientRegistration(BaseModel): + status: Literal["needs_client_registration"] + error: str + + +McpStatus: TypeAlias = Union[McpStatusConnected, McpStatusDisabled, McpStatusFailed, McpStatusNeedsAuth, McpStatusNeedsClientRegistration] + + +class LspStatus(BaseModel): + id_: str = Field(..., alias="id") + name: str + root: str + status: Union[Literal["connected"], Literal["error"]] + + +class FormatterStatus(BaseModel): + name: str + extensions: list[str] + enabled: bool + + +class OAuth(BaseModel): + type_: Literal["oauth"] = Field(..., alias="type") + refresh: str + access: str + expires: float + enterprise_url: Optional[str] = Field(default=None, alias="enterpriseUrl") + + +class ApiAuth(BaseModel): + type_: Literal["api"] = Field(..., alias="type") + key: str + + +class WellKnownAuth(BaseModel): + type_: Literal["wellknown"] = Field(..., alias="type") + key: str + token: str + + +Auth: TypeAlias = Union[OAuth, ApiAuth, WellKnownAuth] diff --git a/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/__init__.py b/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/__init__.py index e20c2299e..b96b9eba3 100644 --- a/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/__init__.py +++ b/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/__init__.py @@ -1,3 +1,3 @@ -from .client import Client, create_client +from .client import Client, build_client_params, create_client -__all__ = ["Client", "create_client"] +__all__ = ["Client", "build_client_params", "create_client"] diff --git a/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/client.py b/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/client.py index 9dfbfcd0e..2980b579d 100644 --- a/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/client.py +++ b/packages/openapi-python/src/plugins/@hey-api/client-httpx/bundle/client.py @@ -1,7 +1,73 @@ -from typing import Optional +from typing import Any, Optional import httpx +EXTRA_PREFIXES_MAP = { + "$body_": "json", + "$headers_": "headers", + "$path_": "path", + "$query_": "params", +} + + +def build_client_params(fields: list[dict[str, Any]], **kwargs) -> dict[str, Any]: + """Build client parameters from flat keyword arguments. + + Args: + fields: List of field configurations with 'in', 'key', and optional 'map'. + **kwargs: Flat parameters passed to the SDK method. + + Returns: + Dict suitable for httpx client methods: {params: {...}, headers: {...}, json: Any} + """ + result: dict[str, Any] = {} + + key_map = {} + for field in fields: + key = field.get("key") + if key: + key_map[key] = { + "in": field.get("in"), + "map": field.get("map", key), + } + + for key, value in kwargs.items(): + if value is None: + continue + + field = key_map.get(key) + + if field: + in_slot = field["in"] + map_key = field["map"] + slot = "json" if in_slot == "body" else in_slot + + if in_slot == "body": + result[slot] = value + else: + if slot not in result: + result[slot] = {} + result[slot][map_key] = value + else: + for prefix, slot in EXTRA_PREFIXES_MAP.items(): + if key.startswith(prefix): + actual_key = key[len(prefix) :] + if slot not in result: + result[slot] = {} + result[slot][actual_key] = value + break + else: + if "params" not in result: + result["params"] = {} + result["params"][key] = value + + for slot in list(result.keys()): + if not result[slot]: + del result[slot] + + return result + + class BaseClient: """Base HTTP client using httpx that SDK classes extend.""" diff --git a/packages/openapi-python/src/plugins/@hey-api/sdk/config.ts b/packages/openapi-python/src/plugins/@hey-api/sdk/config.ts index e05aba579..5d1c30987 100644 --- a/packages/openapi-python/src/plugins/@hey-api/sdk/config.ts +++ b/packages/openapi-python/src/plugins/@hey-api/sdk/config.ts @@ -15,6 +15,7 @@ export const defaultConfig: HeyApiSdkPlugin['Config'] = { // transformer: false, // validator: false, }, + dependencies: ['pydantic'], handler, name: '@hey-api/python-sdk', resolveConfig: (plugin, context) => { diff --git a/packages/openapi-python/src/plugins/@hey-api/sdk/shared/operation.ts b/packages/openapi-python/src/plugins/@hey-api/sdk/shared/operation.ts index 5ae7bba24..710b39bca 100644 --- a/packages/openapi-python/src/plugins/@hey-api/sdk/shared/operation.ts +++ b/packages/openapi-python/src/plugins/@hey-api/sdk/shared/operation.ts @@ -1,19 +1,18 @@ +import type { Symbol } from '@hey-api/codegen-core'; import type { IR } from '@hey-api/shared'; -import { toCase } from '@hey-api/shared'; -import type { $ } from '../../../../py-dsl'; -// import { py } from '../../../../ts-python'; +import { $ } from '../../../../py-dsl'; import type { HeyApiSdkPlugin } from '../types'; import { getSignatureParameters } from './signature'; type OperationParameters = { bodyRef?: string; + fields: Array<{ + in: string; + key: string; + map?: string; + }>; parameters: Array>; - // parameters: Array<{ - // annotation?: py.Expression; - // defaultValue?: py.Expression; - // name: string; - // }>; }; const PYTHON_BUILTIN_TYPES: Record = { @@ -25,38 +24,41 @@ const PYTHON_BUILTIN_TYPES: Record = { string: 'str', }; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function schemaToPythonType(schema: IR.SchemaObject, plugin: HeyApiSdkPlugin['Instance']): string { +function schemaToPythonType( + schema: IR.SchemaObject, + plugin: HeyApiSdkPlugin['Instance'], +): ReturnType | Symbol { if (schema.$ref) { - return toCase(schema.$ref.split('/').pop()!, 'PascalCase'); + return plugin.referenceSymbol({ + category: 'schema', + resourceId: schema.$ref, + }); } if (schema.type === 'array') { - const itemsSchema = schema.items as IR.SchemaObject | undefined; + const itemsSchema = schema.items?.[0]; const itemType = itemsSchema ? schemaToPythonType(itemsSchema, plugin) : 'Any'; - return `list[${itemType}]`; + return $('list').slice(itemType); } if (schema.type === 'object' || schema.additionalProperties) { if (schema.additionalProperties && typeof schema.additionalProperties === 'object') { - const valueType = schemaToPythonType(schema.additionalProperties as IR.SchemaObject, plugin); - return `dict[str, ${valueType}]`; + const valueType = schemaToPythonType(schema.additionalProperties, plugin); + return $('dict').slice('str', valueType); } - return 'dict[str, Any]'; + return $('dict').slice('str', plugin.external('typing.Any')); } if (schema.type === 'tuple') { - const itemsSchema = schema.items as IR.SchemaObject | IR.SchemaObject[] | undefined; + const itemsSchema = schema.items; const itemTypes = itemsSchema - ? Array.isArray(itemsSchema) - ? itemsSchema.map((item) => schemaToPythonType(item, plugin)) - : [schemaToPythonType(itemsSchema, plugin)] + ? itemsSchema.map((item) => schemaToPythonType(item, plugin)) : []; - return `tuple[${itemTypes.join(', ')}]`; + return $('tuple').slice(...itemTypes); } - const builtinType = schema.type ? PYTHON_BUILTIN_TYPES[schema.type] : 'Any'; - return builtinType ?? 'Any'; + const builtinType = schema.type ? PYTHON_BUILTIN_TYPES[schema.type] : undefined; + return $(builtinType ?? plugin.external('typing.Any')); } export function operationParameters({ @@ -67,6 +69,7 @@ export function operationParameters({ plugin: HeyApiSdkPlugin['Instance']; }): OperationParameters { const result: OperationParameters = { + fields: [], parameters: [], }; @@ -74,97 +77,26 @@ export function operationParameters({ const signature = getSignatureParameters({ operation }); if (!signature) return result; - // result.bodyRef = signature.bodyRef; - - // for (const param of opParameters.parameters) { - // if (param.name === '*') { - // continue; - // } - // node.param(param.name, (p) => p.type(param.annotation).default(param.defaultValue)); - // } - - // const pathParams: OperationParameters['parameters'] = []; - // const requiredParams: OperationParameters['parameters'] = []; - // const optionalParams: OperationParameters['parameters'] = []; - - // const paramNames = Object.keys(signature.parameters); - - // for (const paramName of paramNames) { - // const param = signature.parameters[paramName]!; - - // if (param.in === 'path') { - // const type = schemaToPythonType(param.schema, plugin); - // pathParams.push({ - // annotation: py.factory.createIdentifier(type), - // name: param.name, - // }); - // continue; - // } - - // if (param.in === 'body' && param.schema.$ref) { - // const refName = toCase(param.schema.$ref.split('/').pop()!, 'PascalCase'); - // if (param.isRequired) { - // requiredParams.push({ - // annotation: py.factory.createIdentifier(refName), - // name: param.name, - // }); - // } else { - // optionalParams.push({ - // annotation: py.factory.createIdentifier(`${refName} | None`), - // defaultValue: py.factory.createLiteral(null), - // name: param.name, - // }); - // } - // continue; - // } - - // const type = schemaToPythonType(param.schema, plugin); - - // if (param.isRequired) { - // requiredParams.push({ - // annotation: py.factory.createIdentifier(type), - // name: param.name, - // }); - // } else { - // let defaultValue: py.Expression = py.factory.createLiteral(null); - // if (param.schema.default !== undefined) { - // const defaultVal = param.schema.default; - // if ( - // typeof defaultVal === 'string' || - // typeof defaultVal === 'number' || - // typeof defaultVal === 'boolean' - // ) { - // defaultValue = py.factory.createLiteral(defaultVal); - // } else { - // defaultValue = py.factory.createLiteral(null); - // } - // } else if (type.startsWith('list') || type.startsWith('dict')) { - // defaultValue = py.factory.createLiteral(null); - // } - - // optionalParams.push({ - // annotation: py.factory.createIdentifier(`${type} | None`), - // defaultValue, - // name: param.name, - // }); - // } - // } - - // if (pathParams.length > 0) { - // result.parameters.push(...pathParams); - // } - - // if (requiredParams.length > 0 || optionalParams.length > 0) { - // result.parameters.push({ name: '*' }); - // result.parameters.push(...requiredParams); - // result.parameters.push(...optionalParams); - // } - - // result.parameters.push({ - // annotation: py.factory.createIdentifier('float | None'), - // defaultValue: py.factory.createLiteral(null), - // name: 'timeout', - // }); + result.bodyRef = signature.bodyRef; + result.fields = signature.fields; + + const paramEntries = Object.entries(signature.parameters).sort(([, valueA], [, valueB]) => + valueA.isRequired === valueB.isRequired ? 0 : valueA.isRequired ? -1 : 1, + ); + + for (const [paramName, param] of paramEntries) { + const type = schemaToPythonType(param.schema, plugin); + + if (param.isRequired) { + result.parameters.push($.param(paramName).type(type)); + } else { + result.parameters.push( + $.param(paramName) + .type($(plugin.external('typing.Union')).slice(type, 'None')) + .default('None'), + ); + } + } } return result; diff --git a/packages/openapi-python/src/plugins/@hey-api/sdk/shared/signature.ts b/packages/openapi-python/src/plugins/@hey-api/sdk/shared/signature.ts index eb99b6834..326f8cce6 100644 --- a/packages/openapi-python/src/plugins/@hey-api/sdk/shared/signature.ts +++ b/packages/openapi-python/src/plugins/@hey-api/sdk/shared/signature.ts @@ -13,8 +13,15 @@ type SignatureParameter = { type SignatureParameters = Record; +type Field = { + in: Location | 'headers'; + key: string; + map?: string; +}; + type Signature = { bodyRef?: string; + fields: Field[]; parameters: SignatureParameters; }; @@ -70,6 +77,7 @@ export function getSignatureParameters({ } const signatureParameters: SignatureParameters = {}; + const fields: Field[] = []; for (const location of locations) { const parameters = operation.parameters?.[location]; @@ -88,6 +96,11 @@ export function getSignatureParameters({ signatureParameter.originalName = originalName; } signatureParameters[name] = signatureParameter; + fields.push({ + in: location === 'header' ? 'headers' : location, + key: name, + ...(name !== originalName ? { map: originalName } : {}), + }); } } } @@ -115,6 +128,11 @@ export function getSignatureParameters({ signatureParameter.originalName = originalName; } signatureParameters[name] = signatureParameter; + fields.push({ + in: location, + key: name, + ...(name !== originalName ? { map: originalName } : {}), + }); } } else if (operation.body.schema.$ref) { const value = refToName(operation.body.schema.$ref); @@ -131,6 +149,11 @@ export function getSignatureParameters({ signatureParameter.originalName = originalName; } signatureParameters[name] = signatureParameter; + fields.push({ + in: location, + key: name, + map: 'body', + }); } else { signatureParameters.body = { in: location, @@ -138,6 +161,11 @@ export function getSignatureParameters({ name: 'body', schema: operation.body.schema, }; + fields.push({ + in: location, + key: 'body', + map: 'body', + }); } } @@ -145,5 +173,5 @@ export function getSignatureParameters({ return; } - return { bodyRef, parameters: signatureParameters }; + return { bodyRef, fields, parameters: signatureParameters }; } diff --git a/packages/openapi-python/src/plugins/@hey-api/sdk/v1/node.ts b/packages/openapi-python/src/plugins/@hey-api/sdk/v1/node.ts index de0de6bb8..8205d5af8 100644 --- a/packages/openapi-python/src/plugins/@hey-api/sdk/v1/node.ts +++ b/packages/openapi-python/src/plugins/@hey-api/sdk/v1/node.ts @@ -118,12 +118,48 @@ function implementFn>(args: { const { node, operation, plugin } = args; const method = operation.method.toLowerCase(); const opParameters = operationParameters({ operation, plugin }); - return ( - node - .params(...opParameters.parameters) - // TODO: extract operation statements into a separate function - .do($('self').attr('client').attr(method).call($.literal(operation.path)).return()) as T - ); + + if (plugin.config.paramsStructure === 'flat' && opParameters.fields.length > 0) { + const paramNames = opParameters.parameters.map((parameter) => parameter.name.toString()); + + const fieldsList = $.list(); + for (const field of opParameters.fields) { + const fieldDict = $.dict(); + fieldDict.entry($.literal('in'), $.literal(field.in)); + fieldDict.entry($.literal('key'), $.literal(field.key)); + if (field.map) { + fieldDict.entry($.literal('map'), $.literal(field.map)); + } + fieldsList.element(fieldDict); + } + + const kwargs: Array> = []; + for (const name of paramNames) { + kwargs.push($.kwarg(name, name)); + } + + return ( + node + .params(...opParameters.parameters) + // TODO: extract operation statements into a separate function + .do( + $.var('params').assign( + $(plugin.external('client.build_client_params')).call(fieldsList, ...kwargs), + ), + ) + .do( + $('self') + .attr('client') + .attr(method) + .call($.literal(operation.path), $.kwarg('params', $('params') as never)) + .return(), + ) as T + ); + } + + return node + .params(...opParameters.parameters) + .do($('self').attr('client').attr(method).call($.literal(operation.path)).return()) as T; } export function toNode( diff --git a/packages/openapi-python/src/plugins/@hey-api/sdk/v1/plugin.ts b/packages/openapi-python/src/plugins/@hey-api/sdk/v1/plugin.ts index 4d065673b..2dbbd76e3 100644 --- a/packages/openapi-python/src/plugins/@hey-api/sdk/v1/plugin.ts +++ b/packages/openapi-python/src/plugins/@hey-api/sdk/v1/plugin.ts @@ -11,6 +11,16 @@ import { createShell, type OperationItem, source, toNode } from './node'; export const handlerV1: HeyApiSdkPlugin['Handler'] = ({ plugin }) => { const clientModule = clientFolderAbsolutePath(getTypedConfig(plugin)); const client = getClientPlugin(getTypedConfig(plugin)); + + // client + plugin.symbol('build_client_params', { + external: clientModule, + meta: { + category: 'external', + resource: 'client.build_client_params', + tool: client.name, + }, + }); plugin.symbol('Client', { external: clientModule, meta: { @@ -20,6 +30,7 @@ export const handlerV1: HeyApiSdkPlugin['Handler'] = ({ plugin }) => { }, }); + // functools plugin.symbol('cached_property', { external: 'functools', meta: { @@ -28,6 +39,22 @@ export const handlerV1: HeyApiSdkPlugin['Handler'] = ({ plugin }) => { }, }); + // typing + plugin.symbol('Any', { + external: 'typing', + meta: { + category: 'external', + resource: 'typing.Any', + }, + }); + plugin.symbol('Union', { + external: 'typing', + meta: { + category: 'external', + resource: 'typing.Union', + }, + }); + const structure = new StructureModel(); const shell = createShell(plugin); const strategy = resolveStrategy(plugin); diff --git a/packages/openapi-python/src/plugins/pydantic/v2/plugin.ts b/packages/openapi-python/src/plugins/pydantic/v2/plugin.ts index cae12dbcf..f9552a6ec 100644 --- a/packages/openapi-python/src/plugins/pydantic/v2/plugin.ts +++ b/packages/openapi-python/src/plugins/pydantic/v2/plugin.ts @@ -21,13 +21,6 @@ export const handlerV2: PydanticPlugin['Handler'] = ({ plugin }) => { resource: 'typing.Any', }, }); - plugin.symbol('List', { - external: 'typing', - meta: { - category: 'external', - resource: 'typing.List', - }, - }); plugin.symbol('Literal', { external: 'typing', meta: { diff --git a/packages/openapi-python/src/plugins/pydantic/v2/toAst/array.ts b/packages/openapi-python/src/plugins/pydantic/v2/toAst/array.ts index 1d279738d..9d77e7fca 100644 --- a/packages/openapi-python/src/plugins/pydantic/v2/toAst/array.ts +++ b/packages/openapi-python/src/plugins/pydantic/v2/toAst/array.ts @@ -9,19 +9,18 @@ import type { FieldConstraints } from '../constants'; function baseNode(ctx: ArrayResolverContext): PydanticType { const { applyModifiers, childResults, plugin } = ctx; - const list = plugin.external('typing.List'); const any = plugin.external('typing.Any'); if (childResults.length === 0) { return { - type: $(list).slice(any), + type: $('list').slice(any), }; } if (childResults.length === 1) { const itemResult = applyModifiers(childResults[0]!); return { - type: $(list).slice(itemResult.type ?? any), + type: $('list').slice(itemResult.type ?? any), }; } @@ -29,12 +28,12 @@ function baseNode(ctx: ArrayResolverContext): PydanticType { const union = plugin.external('typing.Union'); const itemTypes = childResults.map((r) => applyModifiers(r).type ?? any); return { - type: $(list).slice($(union).slice(...itemTypes)), + type: $('list').slice($(union).slice(...itemTypes)), }; } return { - type: $(list).slice(any), + type: $('list').slice(any), }; } @@ -88,7 +87,6 @@ export function arrayToType(ctx: { }): ArrayToTypeResult { const { applyModifiers, plugin, schema, walk, walkerCtx } = ctx; const any = plugin.external('typing.Any'); - const list = plugin.external('typing.List'); const childResults: Array = []; @@ -120,7 +118,7 @@ export function arrayToType(ctx: { const resolved = resolver?.(resolverCtx) ?? arrayResolver(resolverCtx); if (!resolved.type) { - resolved.type = $(list).slice(any); + resolved.type = $('list').slice(any); } return { diff --git a/packages/openapi-python/src/py-dsl/decl/param.ts b/packages/openapi-python/src/py-dsl/decl/param.ts index a976ac4ff..6cd61f200 100644 --- a/packages/openapi-python/src/py-dsl/decl/param.ts +++ b/packages/openapi-python/src/py-dsl/decl/param.ts @@ -6,7 +6,7 @@ import { PyDsl } from '../base'; export type ParamDefaultValue = NodeName | py.Expression | undefined; export type ParamFn = (p: ParamPyDsl) => void; export type ParamName = NodeName | ParamFn; -export type ParamType = NodeName | py.Expression | undefined; +export type ParamType = NodeName | PyDsl | undefined; export type ParamCtor = (name: ParamName, fn?: ParamFn) => ParamPyDsl; diff --git a/packages/openapi-python/src/py-dsl/expr/kwarg.ts b/packages/openapi-python/src/py-dsl/expr/kwarg.ts index 1950d5088..ca5e1bdfb 100644 --- a/packages/openapi-python/src/py-dsl/expr/kwarg.ts +++ b/packages/openapi-python/src/py-dsl/expr/kwarg.ts @@ -1,3 +1,5 @@ +import type { NodeName } from '@hey-api/codegen-core'; + import { py } from '../../ts-python'; import type { MaybePyDsl } from '../base'; import { PyDsl } from '../base'; @@ -7,22 +9,26 @@ export type KwargValue = string | number | boolean | null | MaybePyDsl { readonly '~dsl' = 'KwargPyDsl'; - constructor( - private readonly argName: string, - private readonly argValue: KwargValue, - ) { + protected _value: KwargValue; + + constructor(name: NodeName, value: KwargValue) { super(); + this.name.set(name); + this._value = value; } override toAst() { - return py.factory.createKeywordArgument(this.argName, this.$valueToNode(this.argValue)); + const name = this.name.toString(); + return py.factory.createKeywordArgument(name, this.$valueToNode(this._value)); } private $valueToNode(value: KwargValue) { - if (value === null) { - return py.factory.createIdentifier('None'); - } - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + if ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + value === null + ) { return py.factory.createLiteral(value); } return this.$node(value); diff --git a/packages/openapi-python/src/py-dsl/expr/subscript.ts b/packages/openapi-python/src/py-dsl/expr/subscript.ts index 93339c724..0ae500e1f 100644 --- a/packages/openapi-python/src/py-dsl/expr/subscript.ts +++ b/packages/openapi-python/src/py-dsl/expr/subscript.ts @@ -1,12 +1,11 @@ import type { AnalysisContext, NodeName } from '@hey-api/codegen-core'; import { py } from '../../ts-python'; -import type { MaybePyDsl } from '../base'; import { PyDsl } from '../base'; import { LayoutMixin } from '../mixins/layout'; import { f } from '../utils/factories'; -export type SubscriptExpr = NodeName | MaybePyDsl; +export type SubscriptExpr = NodeName | PyDsl; export type SubscriptCtor = ( value: SubscriptExpr, ...slices: Array diff --git a/packages/openapi-python/src/py-dsl/mixins/expr.ts b/packages/openapi-python/src/py-dsl/mixins/expr.ts index 64830fb3b..3fc53f28d 100644 --- a/packages/openapi-python/src/py-dsl/mixins/expr.ts +++ b/packages/openapi-python/src/py-dsl/mixins/expr.ts @@ -37,7 +37,6 @@ export function ExprMixin>(Ba } protected slice(...args: DropFirst>): ReturnType { - // @ts-expect-error - fix this type return f.slice(this, ...args); } } diff --git a/packages/openapi-python/src/py-dsl/mixins/value.ts b/packages/openapi-python/src/py-dsl/mixins/value.ts index 06243bb36..e7fcf5e36 100644 --- a/packages/openapi-python/src/py-dsl/mixins/value.ts +++ b/packages/openapi-python/src/py-dsl/mixins/value.ts @@ -1,10 +1,10 @@ import type { AnalysisContext, Node, NodeName } from '@hey-api/codegen-core'; import type { py } from '../../ts-python'; -import type { MaybePyDsl } from '../base'; +import type { PyDsl } from '../base'; import type { BaseCtor, MixinCtor } from './types'; -export type ValueExpr = NodeName | MaybePyDsl; +export type ValueExpr = NodeName | PyDsl; export interface ValueMethods extends Node { $value(): py.Expression | undefined; diff --git a/packages/openapi-python/src/py-dsl/stmt/var.ts b/packages/openapi-python/src/py-dsl/stmt/var.ts index b2ea23228..cde6c06e4 100644 --- a/packages/openapi-python/src/py-dsl/stmt/var.ts +++ b/packages/openapi-python/src/py-dsl/stmt/var.ts @@ -2,14 +2,13 @@ import type { AnalysisContext, NodeName } from '@hey-api/codegen-core'; import { isSymbol } from '@hey-api/codegen-core'; import { py } from '../../ts-python'; -import type { MaybePyDsl } from '../base'; import { PyDsl } from '../base'; import { ValueMixin } from '../mixins/value'; import { safeRuntimeName } from '../utils/name'; const Mixed = ValueMixin(PyDsl); -export type VarType = NodeName | MaybePyDsl; +export type VarType = NodeName | PyDsl; export class VarPyDsl extends Mixed { readonly '~dsl' = 'VarPyDsl';