diff --git a/.gitattributes b/.gitattributes index 2a92ef017..6e04e9800 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,7 +6,7 @@ java/**/*.java text eol=lf # Generated files — keep LF line endings so codegen output is deterministic across platforms. nodejs/src/generated/* eol=lf linguist-generated=true dotnet/src/Generated/* eol=lf linguist-generated=true -python/copilot/generated/* eol=lf linguist-generated=true +python/copilot/_generated/* eol=lf linguist-generated=true go/zsession_events.go eol=lf linguist-generated=true go/zsession_encoding.go eol=lf linguist-generated=true go/rpc/zrpc.go eol=lf linguist-generated=true diff --git a/.github/workflows/codegen-check.yml b/.github/workflows/codegen-check.yml index 78927f160..7aab4a671 100644 --- a/.github/workflows/codegen-check.yml +++ b/.github/workflows/codegen-check.yml @@ -10,7 +10,7 @@ on: - 'scripts/codegen/**' - 'nodejs/src/generated/**' - 'dotnet/src/Generated/**' - - 'python/copilot/generated/**' + - 'python/copilot/_generated/**' - 'go/generated_*.go' - 'go/rpc/**' - 'rust/src/generated/**' diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 8b0100df2..dd7d4dc8e 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -11,6 +11,19 @@ # Register the public ``copilot.session_events`` namespace. from . import session_events as session_events # noqa: F401 +from ._generated.rpc import ( + CurrentToolMetadata, + GitHubTelemetryClientInfo, + GitHubTelemetryEvent, + GitHubTelemetryNotification, + ModelBillingTokenPrices, + ModelBillingTokenPricesLongContext, +) +from ._generated.session_events import ( + PermissionRequest, + SessionEvent, + SessionEventType, +) from ._mode import ( BUILTIN_TOOLS_ISOLATED, CopilotClientMode, @@ -78,19 +91,6 @@ CopilotWebSocketHandler, LlmInferenceHeaders, ) -from .generated.rpc import ( - CurrentToolMetadata, - GitHubTelemetryClientInfo, - GitHubTelemetryEvent, - GitHubTelemetryNotification, - ModelBillingTokenPrices, - ModelBillingTokenPricesLongContext, -) -from .generated.session_events import ( - PermissionRequest, - SessionEvent, - SessionEventType, -) from .session import ( AgentStopHandler, AgentStopHookInput, diff --git a/python/copilot/generated/__init__.py b/python/copilot/_generated/__init__.py similarity index 100% rename from python/copilot/generated/__init__.py rename to python/copilot/_generated/__init__.py diff --git a/python/copilot/generated/rpc.py b/python/copilot/_generated/rpc.py similarity index 100% rename from python/copilot/generated/rpc.py rename to python/copilot/_generated/rpc.py diff --git a/python/copilot/generated/session_events.py b/python/copilot/_generated/session_events.py similarity index 100% rename from python/copilot/generated/session_events.py rename to python/copilot/_generated/session_events.py diff --git a/python/copilot/canvas.py b/python/copilot/canvas.py index 9b8dec525..3a6028f37 100644 --- a/python/copilot/canvas.py +++ b/python/copilot/canvas.py @@ -19,7 +19,7 @@ from dataclasses import dataclass from typing import Any -from .generated.rpc import ( +from ._generated.rpc import ( CanvasAction, CanvasHostContext, CanvasHostContextCapabilities, diff --git a/python/copilot/client.py b/python/copilot/client.py index f7f0a4eb2..dec0582c6 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -33,6 +33,26 @@ from ._diagnostics import log_timing from ._ffi_runtime_host import FfiRuntimeHost +from ._generated.rpc import ( + ClientGlobalApiHandlers, + ClientSessionApiHandlers, + GitHubTelemetryNotification, + ModelBillingTokenPrices, + ModelBillingTokenPricesLongContext, # noqa: F401 + OpenCanvasInstance, + RemoteSessionMode, + ServerRpc, + _ConnectResult, + _HookInvokeRequest, + _HookInvokeResponse, + from_datetime, + register_client_global_api_handlers, + register_client_session_api_handlers, +) +from ._generated.session_events import ( + SessionEvent, + session_event_from_dict, +) from ._jsonrpc import JsonRpcClient, JsonRpcError, ProcessExitedError from ._mode import ( CopilotClientMode, @@ -64,26 +84,6 @@ ExtensionInfo, ) from .copilot_request_handler import CopilotRequestHandler, create_copilot_request_adapter -from .generated.rpc import ( - ClientGlobalApiHandlers, - ClientSessionApiHandlers, - GitHubTelemetryNotification, - ModelBillingTokenPrices, - ModelBillingTokenPricesLongContext, # noqa: F401 - OpenCanvasInstance, - RemoteSessionMode, - ServerRpc, - _ConnectResult, - _HookInvokeRequest, - _HookInvokeResponse, - from_datetime, - register_client_global_api_handlers, - register_client_session_api_handlers, -) -from .generated.session_events import ( - SessionEvent, - session_event_from_dict, -) from .session import ( AutoModeSwitchHandler, BearerTokenProvider, @@ -4356,7 +4356,7 @@ async def _apply_post_create_options_patch( If the patch is rejected, tear the session down so empty-mode callers never end up with a permissive session. """ - from .generated.rpc import SessionInstalledPlugin, SessionUpdateOptionsParams + from ._generated.rpc import SessionInstalledPlugin, SessionUpdateOptionsParams patch = _post_create_options_patch( mode, diff --git a/python/copilot/copilot_request_handler.py b/python/copilot/copilot_request_handler.py index e6465b7bb..f3333b421 100644 --- a/python/copilot/copilot_request_handler.py +++ b/python/copilot/copilot_request_handler.py @@ -15,7 +15,7 @@ transparent forwarding connection via the ``websockets`` library. :func:`create_copilot_request_adapter` converts a handler into the generated -:class:`~copilot.generated.rpc.LlmInferenceHandler` shape so the RPC dispatcher +:class:`~copilot.rpc.LlmInferenceHandler` shape so the RPC dispatcher can route inbound ``httpRequestStart`` / ``httpRequestChunk`` frames through it. """ @@ -27,7 +27,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any -from .generated.rpc import ( +from ._generated.rpc import ( LlmInferenceHTTPRequestChunkRequest, LlmInferenceHTTPRequestChunkResult, LlmInferenceHTTPRequestStartRequest, diff --git a/python/copilot/rpc.py b/python/copilot/rpc.py index 73c3d976d..0f0902347 100644 --- a/python/copilot/rpc.py +++ b/python/copilot/rpc.py @@ -3,11 +3,11 @@ These types are auto-generated from the Copilot CLI protocol schemas. This module is the stable public access point so callers can write ``copilot.rpc.SessionUpdateOptionsParams`` without depending on the internal -``copilot.generated`` package layout. +``copilot._generated`` package layout. """ -from .generated.rpc import * # noqa: F401, F403 -from .generated.rpc import ( +from ._generated.rpc import * # noqa: F401, F403 +from ._generated.rpc import ( SessionFsReaddirWithTypesEntryType as SessionFSReaddirWithTypesEntryType, # noqa: F401 ) -from .generated.rpc import __all__ # noqa: F401 +from ._generated.rpc import __all__ # noqa: F401 diff --git a/python/copilot/session.py b/python/copilot/session.py index 4474ce562..f0bce1872 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -23,13 +23,10 @@ from typing import TYPE_CHECKING, Any, Literal, NotRequired, Required, TypedDict, cast from ._diagnostics import log_timing -from ._jsonrpc import JsonRpcError, ProcessExitedError -from ._telemetry import get_trace_context, trace_context -from .canvas import CanvasError, CanvasHandler, OpenCanvasInstance -from .generated.rpc import ( +from ._generated.rpc import ( CanvasHandler as RpcCanvasHandler, ) -from .generated.rpc import ( +from ._generated.rpc import ( CanvasProviderCloseRequest, CanvasProviderInvokeActionRequest, CanvasProviderOpenRequest, @@ -59,11 +56,11 @@ UIElicitationSchemaType, UIHandlePendingElicitationRequest, ) -from .generated.rpc import ( +from ._generated.rpc import ( ContextTier as _RpcContextTier, ) -from .generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride -from .generated.session_events import ( +from ._generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride +from ._generated.session_events import ( AssistantMessageData, CapabilitiesChangedData, CommandExecuteData, @@ -79,9 +76,12 @@ SessionIdleData, session_event_from_dict, ) -from .generated.session_events import ( +from ._generated.session_events import ( ReasoningSummary as _RpcReasoningSummary, ) +from ._jsonrpc import JsonRpcError, ProcessExitedError +from ._telemetry import get_trace_context, trace_context +from .canvas import CanvasError, CanvasHandler, OpenCanvasInstance from .tools import ( Tool, ToolHandler, diff --git a/python/copilot/session_events.py b/python/copilot/session_events.py index 584ab47f8..d182b5720 100644 --- a/python/copilot/session_events.py +++ b/python/copilot/session_events.py @@ -3,8 +3,8 @@ These types are auto-generated from the Copilot CLI session-events schema. This module is the stable public access point so callers can write ``copilot.session_events.AssistantMessageData`` without depending on the -internal ``copilot.generated`` package layout. +internal ``copilot._generated`` package layout. """ -from .generated.session_events import * # noqa: F401, F403 -from .generated.session_events import __all__ # noqa: F401 +from ._generated.session_events import * # noqa: F401, F403 +from ._generated.session_events import __all__ # noqa: F401 diff --git a/python/copilot/session_fs_provider.py b/python/copilot/session_fs_provider.py index c9e90a644..c73955555 100644 --- a/python/copilot/session_fs_provider.py +++ b/python/copilot/session_fs_provider.py @@ -23,7 +23,7 @@ from datetime import UTC, datetime from typing import Any -from .generated.rpc import ( +from ._generated.rpc import ( SessionFSError, SessionFSErrorCode, SessionFSExistsResult, @@ -38,13 +38,13 @@ SessionFSSqliteTransactionStatement, SessionFSStatResult, ) -from .generated.rpc import ( +from ._generated.rpc import ( SessionFSSqliteQueryResult as _GeneratedSqliteQueryResult, ) -from .generated.rpc import ( +from ._generated.rpc import ( SessionFSSqliteTransactionError as _GeneratedSqliteTransactionError, ) -from .generated.rpc import ( +from ._generated.rpc import ( SessionFSSqliteTransactionResult as _GeneratedSqliteTransactionResult, ) diff --git a/python/copilot/tools.py b/python/copilot/tools.py index 762b79c45..2ab551ceb 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -16,9 +16,9 @@ from pydantic import BaseModel, ValidationError if TYPE_CHECKING: - from .generated.rpc import CurrentToolMetadata + from ._generated.rpc import CurrentToolMetadata -from .generated.rpc import ( +from ._generated.rpc import ( ExternalToolTextResultForLlm, ExternalToolTextResultForLlmBinaryResultsForLlm, ExternalToolTextResultForLlmBinaryResultsForLlmType, diff --git a/python/e2e/_copilot_request_helpers.py b/python/e2e/_copilot_request_helpers.py index 2d91bc9bc..78b1f9480 100644 --- a/python/e2e/_copilot_request_helpers.py +++ b/python/e2e/_copilot_request_helpers.py @@ -24,7 +24,7 @@ import pytest_asyncio from copilot import CopilotClient, CopilotRequestHandler, RuntimeConnection -from copilot.generated.session_events import AssistantMessageData +from copilot.session_events import AssistantMessageData from .testharness import E2ETestContext diff --git a/python/e2e/test_mcp_oauth_e2e.py b/python/e2e/test_mcp_oauth_e2e.py index 9d70597c3..f422c9e43 100644 --- a/python/e2e/test_mcp_oauth_e2e.py +++ b/python/e2e/test_mcp_oauth_e2e.py @@ -7,7 +7,7 @@ import httpx import pytest -from copilot.generated.rpc import ( +from copilot.rpc import ( MCPAppsCallToolRequest, MCPListToolsRequest, MCPOauthHandlePendingRequest, diff --git a/python/e2e/test_provider_endpoint_e2e.py b/python/e2e/test_provider_endpoint_e2e.py index 875a95b91..6a047cd03 100644 --- a/python/e2e/test_provider_endpoint_e2e.py +++ b/python/e2e/test_provider_endpoint_e2e.py @@ -8,7 +8,7 @@ import pytest from copilot.client import CopilotClient, RuntimeConnection -from copilot.generated.rpc import ProviderEndpointType, ProviderEndpointWireApi +from copilot.rpc import ProviderEndpointType, ProviderEndpointWireApi from copilot.session import PermissionHandler from .testharness import E2ETestContext diff --git a/python/pyproject.toml b/python/pyproject.toml index 7e6274d9c..8c2f7ebcc 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -59,7 +59,7 @@ line-length = 100 target-version = "py311" exclude = [ "generated", - "copilot/generated", + "copilot/_generated", ] [tool.ruff.lint] @@ -82,7 +82,7 @@ invalid-argument-type = "warn" [tool.ty.src] exclude = [ "generated", - "copilot/generated", + "copilot/_generated", "copilot/test_*.py", ] diff --git a/python/test_client.py b/python/test_client.py index ba353bde3..a036b9894 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -2630,7 +2630,7 @@ async def request(self, method, params, **kwargs): @pytest.mark.asyncio async def test_event_routes_to_handler(self): - from copilot.generated.rpc import GitHubTelemetryNotification + from copilot.rpc import GitHubTelemetryNotification received: list = [] @@ -2689,7 +2689,7 @@ def on_telemetry(notification): @pytest.mark.asyncio async def test_event_routes_to_async_handler(self): - from copilot.generated.rpc import GitHubTelemetryNotification + from copilot.rpc import GitHubTelemetryNotification received: list = [] delivered = asyncio.Event() diff --git a/python/test_codegen_type_names.py b/python/test_codegen_type_names.py index 5242f1c78..b1b5bb25f 100644 --- a/python/test_codegen_type_names.py +++ b/python/test_codegen_type_names.py @@ -1,7 +1,7 @@ import re import types -from copilot.generated import rpc +from copilot._generated import rpc def test_permission_approval_exports_are_union_aliases(): diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py index 1ffbd59c5..34d81f1a5 100644 --- a/python/test_event_forward_compatibility.py +++ b/python/test_event_forward_compatibility.py @@ -177,7 +177,7 @@ def test_missing_optional_fields_remain_none_after_parsing(self): the schema default instead of ``None`` and broke ``from_dict(to_dict(x))`` round-trips for instances where the field was ``None``. """ - from copilot.generated.session_events import ( + from copilot._generated.session_events import ( _load_PermissionPromptRequest, _load_PermissionRequest, ) @@ -224,13 +224,13 @@ def test_optional_fields_round_trip_none(self): # PermissionRequest is now a discriminated union; the dispatch loader # should round-trip via the correct variant class. - from copilot.generated.session_events import _load_PermissionRequest + from copilot._generated.session_events import _load_PermissionRequest round_tripped = _load_PermissionRequest(permission.to_dict()) assert isinstance(round_tripped, PermissionRequestMemory) assert round_tripped == permission # PermissionPromptRequest likewise. - from copilot.generated.session_events import _load_PermissionPromptRequest + from copilot._generated.session_events import _load_PermissionPromptRequest round_tripped_prompt = _load_PermissionPromptRequest(prompt.to_dict()) assert isinstance(round_tripped_prompt, PermissionPromptRequestMemory) diff --git a/python/test_public_api.py b/python/test_public_api.py new file mode 100644 index 000000000..c8df45db8 --- /dev/null +++ b/python/test_public_api.py @@ -0,0 +1,17 @@ +"""Tests for the Python SDK's public module boundary.""" + +from importlib.util import find_spec + +from copilot import rpc, session_events + + +def test_generated_implementation_package_is_private(): + """The implementation package follows Python's private-name convention.""" + assert find_spec("copilot._generated") is not None + assert find_spec("copilot.generated") is None + + +def test_generated_types_remain_available_from_public_modules(): + """Renaming the implementation package preserves the documented exports.""" + assert "SessionUpdateOptionsParams" in rpc.__all__ + assert "AssistantMessageData" in session_events.__all__ diff --git a/python/test_tools.py b/python/test_tools.py index 646f17c03..a17720e53 100644 --- a/python/test_tools.py +++ b/python/test_tools.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator from copilot import define_tool -from copilot.generated.rpc import ExternalToolTextResultForLlm +from copilot.rpc import ExternalToolTextResultForLlm from copilot.tools import ( ToolBinaryResult, ToolInvocation, diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 978021a98..23e229a43 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -2950,7 +2950,7 @@ async function generateSessionEvents(schemaPath?: string): Promise { code = renameInternalPythonSymbols(code, typeNames); code = appendPythonSessionEventsAllList(code, processed, typeNames); - const outPath = await writeGeneratedFile("python/copilot/generated/session_events.py", code); + const outPath = await writeGeneratedFile("python/copilot/_generated/session_events.py", code); console.log(` ✓ ${outPath}`); } @@ -3408,13 +3408,13 @@ def _patch_model_capabilities(data: dict) -> dict: finalCode = appendPythonRpcAllList(finalCode, rpcDefinitions); - const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", finalCode); + const outPath = await writeGeneratedFile("python/copilot/_generated/rpc.py", finalCode); console.log(` ✓ ${outPath}`); } /** * Appends an `__all__` list to the generated session-events module so that - * the public ``copilot.session_events`` shim can ``from .generated.session_events + * the public ``copilot.session_events`` shim can ``from ._generated.session_events * import *`` without leaking helper functions (``from_str``, ``from_int``, …) * or TypeVars (``T``, ``EnumT``). Internal-marked types are omitted so they * remain hidden from the SDK's public surface even though their renamed @@ -3464,7 +3464,7 @@ function appendPythonSessionEventsAllList(code: string, _schema: JSONSchema7, in /** * Appends an `__all__` list to the generated RPC module so that the public - * ``copilot.rpc`` shim can ``from .generated.rpc import *`` without leaking + * ``copilot.rpc`` shim can ``from ._generated.rpc import *`` without leaking * helper functions (``from_str``, ``from_int``, …) or TypeVars * (``T``, ``EnumT``). *