diff --git a/.github/workflows/python-sdk-release.yml b/.github/workflows/python-sdk-release.yml new file mode 100644 index 0000000000..b831727e0a --- /dev/null +++ b/.github/workflows/python-sdk-release.yml @@ -0,0 +1,91 @@ +name: python-sdk-release + +on: + push: + tags: + - "python-v*" + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + build-python-sdk: + if: github.repository == 'openai/codex' + name: build-python-sdk + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - name: Install packaging tools + run: python -m pip install build uv==0.11.3 + + - name: Validate tag and build Python SDK package + shell: bash + run: | + set -euo pipefail + + sdk_version="${GITHUB_REF_NAME#python-v}" + if [[ ! "${sdk_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$ ]]; then + echo "Python SDK release tags must identify a beta release, for example python-v0.1.0b1." + exit 1 + fi + + configured_version="$( + python -c 'import tomllib; print(tomllib.load(open("sdk/python/pyproject.toml", "rb"))["project"]["version"])' + )" + if [[ "${configured_version}" != "${sdk_version}" ]]; then + echo "Tag version ${sdk_version} does not match sdk/python/pyproject.toml version ${configured_version}." + exit 1 + fi + + cd sdk/python + uv sync --extra dev --frozen + uv run --extra dev --frozen python scripts/update_sdk_artifacts.py \ + stage-sdk "${RUNNER_TEMP}/openai-codex" \ + --sdk-version "${sdk_version}" + + python -m build \ + --wheel \ + --sdist \ + --outdir "${GITHUB_WORKSPACE}/dist/python-sdk" \ + "${RUNNER_TEMP}/openai-codex" + + - name: Upload Python SDK package + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: python-sdk-package + path: dist/python-sdk/* + if-no-files-found: error + + publish-python-sdk: + name: publish-python-sdk + needs: build-python-sdk + runs-on: ubuntu-latest + environment: pypi + permissions: + contents: read + id-token: write # Required for PyPI trusted publishing. + + steps: + - name: Download Python SDK package + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: python-sdk-package + path: dist/python-sdk + + - name: Publish Python SDK to PyPI + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + with: + packages-dir: dist/python-sdk diff --git a/sdk/python-runtime/pyproject.toml b/sdk/python-runtime/pyproject.toml index e01a517037..18967bab51 100644 --- a/sdk/python-runtime/pyproject.toml +++ b/sdk/python-runtime/pyproject.toml @@ -11,7 +11,6 @@ requires-python = ">=3.10" license = { text = "Apache-2.0" } authors = [{ name = "OpenAI" }] classifiers = [ - "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", diff --git a/sdk/python/README.md b/sdk/python/README.md index d3b74b02cf..457a20066a 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -16,8 +16,8 @@ uv sync source .venv/bin/activate ``` -Published SDK builds pin an exact `openai-codex-cli-bin` runtime dependency -with the same version as the SDK. Pass `CodexConfig(codex_bin=...)` only +Published SDK builds pin an exact compatible `openai-codex-cli-bin` runtime +dependency. Pass `CodexConfig(codex_bin=...)` only when you intentionally want to run against a specific local app-server binary. ## Quickstart @@ -111,7 +111,7 @@ python examples/01_quickstart_constructor/async.py Published SDK builds are pinned to an exact `openai-codex-cli-bin` package version, and that runtime package carries the platform-specific binary for the -target wheel. The SDK package version and runtime package version must match. +target wheel. SDK beta releases are versioned independently of runtime releases. ## Compatibility and versioning @@ -119,7 +119,7 @@ target wheel. The SDK package version and runtime package version must match. - Runtime package: `openai-codex-cli-bin` - Python: `>=3.10` - Target protocol: Codex `app-server` JSON-RPC v2 -- Versioning rule: the SDK package version is the underlying Codex runtime version +- Versioning rule: SDK releases pin one exact compatible Codex runtime version ## Notes diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index b7c890d06e..3b76e45526 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "openai-codex" -version = "0.131.0a4" +version = "0.1.0b1" description = "Python SDK for Codex app-server v2" readme = "README.md" requires-python = ">=3.10" @@ -22,7 +22,7 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", ] -dependencies = ["pydantic>=2.12", "openai-codex-cli-bin==0.131.0a4"] +dependencies = ["pydantic>=2.12", "openai-codex-cli-bin==0.132.0"] [project.urls] Homepage = "https://github.com/openai/codex" @@ -86,10 +86,10 @@ combine-as-imports = true [tool.uv] exclude-newer = "7 days" -exclude-newer-package = { openai-codex-cli-bin = "2026-05-10T00:00:00Z" } +exclude-newer-package = { openai-codex-cli-bin = "2026-05-20T21:00:00Z" } index-strategy = "first-index" [tool.uv.pip] exclude-newer = "7 days" -exclude-newer-package = { openai-codex-cli-bin = "2026-05-10T00:00:00Z" } +exclude-newer-package = { openai-codex-cli-bin = "2026-05-20T21:00:00Z" } index-strategy = "first-index" diff --git a/sdk/python/scripts/update_sdk_artifacts.py b/sdk/python/scripts/update_sdk_artifacts.py index b20f814f66..077e44c46e 100755 --- a/sdk/python/scripts/update_sdk_artifacts.py +++ b/sdk/python/scripts/update_sdk_artifacts.py @@ -216,25 +216,8 @@ def _rewrite_project_name(pyproject_text: str, name: str) -> str: return updated -def _rewrite_sdk_runtime_dependency(pyproject_text: str, runtime_version: str) -> str: - match = re.search(r"^dependencies = \[(.*?)\]$", pyproject_text, flags=re.MULTILINE) - if match is None: - raise RuntimeError("Could not find dependencies array in sdk/python/pyproject.toml") - - raw_items = [item.strip() for item in match.group(1).split(",") if item.strip()] - raw_items = [ - item - for item in raw_items - if RUNTIME_DISTRIBUTION_NAME.removeprefix("openai-") not in item - and RUNTIME_DISTRIBUTION_NAME not in item - ] - raw_items.append(f'"{RUNTIME_DISTRIBUTION_NAME}=={runtime_version}"') - replacement = "dependencies = [\n " + ",\n ".join(raw_items) + ",\n]" - return pyproject_text[: match.start()] + replacement + pyproject_text[match.end() :] - - -def stage_python_sdk_package(staging_dir: Path, codex_version: str) -> Path: - package_version = normalize_codex_version(codex_version) +def stage_python_sdk_package(staging_dir: Path, sdk_version: str) -> Path: + package_version = normalize_codex_version(sdk_version) _copy_package_tree(sdk_root(), staging_dir) sdk_bin_dir = staging_dir / "src" / "openai_codex" / "bin" if sdk_bin_dir.exists(): @@ -244,7 +227,6 @@ def stage_python_sdk_package(staging_dir: Path, codex_version: str) -> Path: pyproject_text = pyproject_path.read_text() pyproject_text = _rewrite_project_name(pyproject_text, SDK_DISTRIBUTION_NAME) pyproject_text = _rewrite_project_version(pyproject_text, package_version) - pyproject_text = _rewrite_sdk_runtime_dependency(pyproject_text, package_version) pyproject_path.write_text(pyproject_text) return staging_dir @@ -1229,28 +1211,20 @@ def build_parser() -> argparse.ArgumentParser: stage_sdk_parser = subparsers.add_parser( "stage-sdk", - help="Stage a releasable SDK package pinned to a runtime version", + help="Stage a releasable SDK package while preserving its reviewed runtime pin", ) stage_sdk_parser.add_argument( "staging_dir", type=Path, help="Output directory for the staged SDK package", ) - stage_sdk_parser.add_argument( - "--codex-version", - help=( - "Codex release version to write into the staged SDK package and exact " - f"{RUNTIME_DISTRIBUTION_NAME} dependency. Accepts PEP 440 versions " - "or release tags such as rust-v0.116.0-alpha.1." - ), - ) - stage_sdk_parser.add_argument( - "--runtime-version", - help=argparse.SUPPRESS, - ) stage_sdk_parser.add_argument( "--sdk-version", - help=argparse.SUPPRESS, + required=True, + help=( + "Python SDK release version to write into the staged package. " + "Accepts PEP 440 versions such as 0.1.0b1." + ), ) stage_runtime_parser = subparsers.add_parser( @@ -1269,15 +1243,12 @@ def build_parser() -> argparse.ArgumentParser: ) stage_runtime_parser.add_argument( "--codex-version", + required=True, help=( "Codex release version to write into the staged runtime package. " "Accepts PEP 440 versions or release tags such as rust-v0.116.0-alpha.1." ), ) - stage_runtime_parser.add_argument( - "--runtime-version", - help=argparse.SUPPRESS, - ) stage_runtime_parser.add_argument( "--platform-tag", help=( @@ -1301,40 +1272,19 @@ def default_cli_ops() -> CliOps: ) -def _resolve_codex_version(args: argparse.Namespace) -> str: - versions = [ - value - for value in ( - getattr(args, "codex_version", None), - getattr(args, "runtime_version", None), - getattr(args, "sdk_version", None), - ) - if value is not None - ] - if not versions: - raise RuntimeError("Pass --codex-version to stage Python release artifacts") - - normalized_versions = [normalize_codex_version(version) for version in versions] - if len(set(normalized_versions)) != 1: - raise RuntimeError("SDK and runtime package versions must match; pass one --codex-version") - return normalized_versions[0] - - def run_command(args: argparse.Namespace, ops: CliOps) -> None: if args.command == "generate-types": ops.generate_types() elif args.command == "stage-sdk": - codex_version = _resolve_codex_version(args) ops.generate_types() ops.stage_python_sdk_package( args.staging_dir, - codex_version, + normalize_codex_version(args.sdk_version), ) elif args.command == "stage-runtime": - codex_version = _resolve_codex_version(args) ops.stage_python_runtime_package( args.staging_dir, - codex_version, + normalize_codex_version(args.codex_version), args.package_archive.resolve(), args.platform_tag, ) diff --git a/sdk/python/src/openai_codex/generated/v2_all.py b/sdk/python/src/openai_codex/generated/v2_all.py index 363d87495c..85120b8293 100644 --- a/sdk/python/src/openai_codex/generated/v2_all.py +++ b/sdk/python/src/openai_codex/generated/v2_all.py @@ -49,26 +49,24 @@ class AccountLoginCompletedNotification(BaseModel): success: bool -class AdditionalWritableRootActivePermissionProfileModification(BaseModel): +class ActivePermissionProfile(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - path: AbsolutePathBuf - type: Annotated[ - Literal["additionalWritableRoot"], - Field(title="AdditionalWritableRootActivePermissionProfileModificationType"), + extends: Annotated[ + str | None, + Field( + description="Parent profile identifier once permissions profiles support inheritance. This is currently always `null`." + ), + ] = None + id: Annotated[ + str, + Field( + description="Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile." + ), ] -class ActivePermissionProfileModification( - RootModel[AdditionalWritableRootActivePermissionProfileModification] -): - model_config = ConfigDict( - populate_by_name=True, - ) - root: AdditionalWritableRootActivePermissionProfileModification - - class AddCreditsNudgeCreditType(Enum): credits = "credits" usage_limit = "usage_limit" @@ -597,6 +595,12 @@ class UserConfigLayerSource(BaseModel): description="This is the path to the user's config.toml file, though it is not guaranteed to exist." ), ] + profile: Annotated[ + str | None, + Field( + description="Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one." + ), + ] = None type: Annotated[Literal["user"], Field(title="UserConfigLayerSourceType")] @@ -680,6 +684,7 @@ class CommandConfiguredHookHandler(BaseModel): ) async_: Annotated[bool, Field(alias="async")] command: str + command_windows: Annotated[str | None, Field(alias="commandWindows")] = None status_message: Annotated[str | None, Field(alias="statusMessage")] = None timeout_sec: Annotated[int | None, Field(alias="timeoutSec", ge=0)] = None type: Annotated[Literal["command"], Field(title="CommandConfiguredHookHandlerType")] @@ -842,6 +847,13 @@ class ExperimentalFeatureListParams(BaseModel): int | None, Field(description="Optional page size; defaults to a reasonable server-side value.", ge=0), ] = None + thread_id: Annotated[ + str | None, + Field( + alias="threadId", + description="Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.", + ), + ] = None class ExperimentalFeatureStage(Enum): @@ -1014,6 +1026,18 @@ class FileSystemSpecialPath( ) +class ForcedChatgptWorkspaceIds(RootModel[str | list[str]]): + model_config = ConfigDict( + populate_by_name=True, + ) + root: Annotated[ + str | list[str], + Field( + description="Backward-compatible API shape for ChatGPT workspace login restrictions." + ), + ] + + class ForcedLoginMethod(Enum): chatgpt = "chatgpt" api = "api" @@ -1482,8 +1506,6 @@ class HooksListParams(BaseModel): class ImageDetail(Enum): - auto = "auto" - low = "low" high = "high" original = "original" @@ -2166,66 +2188,6 @@ class PatchChangeKind( root: AddPatchChangeKind | DeletePatchChangeKind | UpdatePatchChangeKind -class DisabledPermissionProfile(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - type: Annotated[Literal["disabled"], Field(title="DisabledPermissionProfileType")] - - -class UnrestrictedPermissionProfileFileSystemPermissions(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - type: Annotated[ - Literal["unrestricted"], - Field(title="UnrestrictedPermissionProfileFileSystemPermissionsType"), - ] - - -class AdditionalWritableRootPermissionProfileModificationParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - path: AbsolutePathBuf - type: Annotated[ - Literal["additionalWritableRoot"], - Field(title="AdditionalWritableRootPermissionProfileModificationParamsType"), - ] - - -class PermissionProfileModificationParams( - RootModel[AdditionalWritableRootPermissionProfileModificationParams] -): - model_config = ConfigDict( - populate_by_name=True, - ) - root: AdditionalWritableRootPermissionProfileModificationParams - - -class PermissionProfileNetworkPermissions(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - enabled: bool - - -class ProfilePermissionProfileSelectionParams(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - id: str - modifications: list[PermissionProfileModificationParams] | None = None - type: Annotated[Literal["profile"], Field(title="ProfilePermissionProfileSelectionParamsType")] - - -class PermissionProfileSelectionParams(RootModel[ProfilePermissionProfileSelectionParams]): - model_config = ConfigDict( - populate_by_name=True, - ) - root: ProfilePermissionProfileSelectionParams - - class Personality(Enum): none = "none" friendly = "friendly" @@ -2298,6 +2260,23 @@ class PluginInstallResponse(BaseModel): auth_policy: Annotated[PluginAuthPolicy, Field(alias="authPolicy")] +class PluginInstalledParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + cwds: Annotated[ + list[AbsolutePathBuf] | None, + Field(description="Optional working directories used to discover repo marketplaces."), + ] = None + install_suggestion_plugin_names: Annotated[ + list[str] | None, + Field( + alias="installSuggestionPluginNames", + description="Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.", + ), + ] = None + + class PluginInterface(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2385,6 +2364,26 @@ class PluginReadParams(BaseModel): remote_marketplace_name: Annotated[str | None, Field(alias="remoteMarketplaceName")] = None +class PluginShareCheckoutParams(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + remote_plugin_id: Annotated[str, Field(alias="remotePluginId")] + + +class PluginShareCheckoutResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + marketplace_name: Annotated[str, Field(alias="marketplaceName")] + marketplace_path: Annotated[AbsolutePathBuf, Field(alias="marketplacePath")] + plugin_id: Annotated[str, Field(alias="pluginId")] + plugin_name: Annotated[str, Field(alias="pluginName")] + plugin_path: Annotated[AbsolutePathBuf, Field(alias="pluginPath")] + remote_plugin_id: Annotated[str, Field(alias="remotePluginId")] + remote_version: Annotated[str | None, Field(alias="remoteVersion")] = None + + class PluginShareDeleteParams(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -2745,6 +2744,7 @@ class RemoteControlStatusChangedNotification(BaseModel): ) environment_id: Annotated[str | None, Field(alias="environmentId")] = None installation_id: Annotated[str, Field(alias="installationId")] + server_name: Annotated[str, Field(alias="serverName")] status: RemoteControlConnectionStatus @@ -2911,6 +2911,13 @@ class CompactionResponseItem(BaseModel): type: Annotated[Literal["compaction"], Field(title="CompactionResponseItemType")] +class CompactionTriggerResponseItem(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + type: Annotated[Literal["compaction_trigger"], Field(title="CompactionTriggerResponseItemType")] + + class ContextCompactionResponseItem(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -3627,6 +3634,8 @@ class ThreadGoalClearedNotification(BaseModel): class ThreadGoalStatus(Enum): active = "active" paused = "paused" + blocked = "blocked" + usage_limited = "usageLimited" budget_limited = "budgetLimited" complete = "complete" @@ -4308,6 +4317,7 @@ class ImageUserInput(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + detail: ImageDetail | None = None type: Annotated[Literal["image"], Field(title="ImageUserInputType")] url: str @@ -4316,6 +4326,7 @@ class LocalImageUserInput(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + detail: ImageDetail | None = None path: str type: Annotated[Literal["localImage"], Field(title="LocalImageUserInputType")] @@ -4525,30 +4536,6 @@ class AccountUpdatedNotification(BaseModel): plan_type: Annotated[PlanType | None, Field(alias="planType")] = None -class ActivePermissionProfile(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - extends: Annotated[ - str | None, - Field( - description="Parent profile identifier once permissions profiles support inheritance. This is currently always `null`." - ), - ] = None - id: Annotated[ - str, - Field( - description="Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile." - ), - ] - modifications: Annotated[ - list[ActivePermissionProfileModification] | None, - Field( - description="Bounded user-requested modifications applied on top of the named profile, if any." - ), - ] = [] - - class AppConfig(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4790,6 +4777,15 @@ class PluginListRequest(BaseModel): params: PluginListParams +class PluginInstalledRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[Literal["plugin/installed"], Field(title="Plugin/installedRequestMethod")] + params: PluginInstalledParams + + class PluginReadRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -4817,6 +4813,17 @@ class PluginShareListRequest(BaseModel): params: PluginShareListParams +class PluginShareCheckoutRequest(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + id: RequestId + method: Annotated[ + Literal["plugin/share/checkout"], Field(title="Plugin/share/checkoutRequestMethod") + ] + params: PluginShareCheckoutParams + + class PluginShareDeleteRequest(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -5424,6 +5431,7 @@ class ConfigRequirements(BaseModel): model_config = ConfigDict( populate_by_name=True, ) + allow_managed_hooks_only: Annotated[bool | None, Field(alias="allowManagedHooksOnly")] = None allowed_approval_policies: Annotated[ list[AskForApproval] | None, Field(alias="allowedApprovalPolicies") ] = None @@ -5851,40 +5859,6 @@ class OverriddenMetadata(BaseModel): overriding_layer: Annotated[ConfigLayerMetadata, Field(alias="overridingLayer")] -class ExternalPermissionProfile(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - network: PermissionProfileNetworkPermissions - type: Annotated[Literal["external"], Field(title="ExternalPermissionProfileType")] - - -class RestrictedPermissionProfileFileSystemPermissions(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - entries: list[FileSystemSandboxEntry] - glob_scan_max_depth: Annotated[int | None, Field(alias="globScanMaxDepth", ge=1)] = None - type: Annotated[ - Literal["restricted"], Field(title="RestrictedPermissionProfileFileSystemPermissionsType") - ] - - -class PermissionProfileFileSystemPermissions( - RootModel[ - RestrictedPermissionProfileFileSystemPermissions - | UnrestrictedPermissionProfileFileSystemPermissions - ] -): - model_config = ConfigDict( - populate_by_name=True, - ) - root: ( - RestrictedPermissionProfileFileSystemPermissions - | UnrestrictedPermissionProfileFileSystemPermissions - ) - - class PluginSharePrincipal(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -6626,7 +6600,6 @@ class ToolsV2(BaseModel): model_config = ConfigDict( populate_by_name=True, ) - view_image: bool | None = None web_search: WebSearchToolConfig | None = None @@ -7072,24 +7045,6 @@ class ListMcpServerStatusResponse(BaseModel): ] = None -class ManagedPermissionProfile(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - file_system: Annotated[PermissionProfileFileSystemPermissions, Field(alias="fileSystem")] - network: PermissionProfileNetworkPermissions - type: Annotated[Literal["managed"], Field(title="ManagedPermissionProfileType")] - - -class PermissionProfile( - RootModel[ManagedPermissionProfile | DisabledPermissionProfile | ExternalPermissionProfile] -): - model_config = ConfigDict( - populate_by_name=True, - ) - root: ManagedPermissionProfile | DisabledPermissionProfile | ExternalPermissionProfile - - class PluginShareContext(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -7098,6 +7053,13 @@ class PluginShareContext(BaseModel): creator_name: Annotated[str | None, Field(alias="creatorName")] = None discoverability: PluginShareDiscoverability | None = None remote_plugin_id: Annotated[str, Field(alias="remotePluginId")] + remote_version: Annotated[ + str | None, + Field( + alias="remoteVersion", + description="Version of the remote shared plugin release when available.", + ), + ] = None share_principals: Annotated[ list[PluginSharePrincipal] | None, Field(alias="sharePrincipals") ] = None @@ -7129,7 +7091,20 @@ class PluginSummary(BaseModel): installed: bool interface: PluginInterface | None = None keywords: list[str] | None = [] + local_version: Annotated[ + str | None, + Field( + alias="localVersion", + description="Version of the locally materialized plugin package when available.", + ), + ] = None name: str + remote_plugin_id: Annotated[ + str | None, + Field( + alias="remotePluginId", description="Backend remote plugin identifier when available." + ), + ] = None share_context: Annotated[ PluginShareContext | None, Field( @@ -7209,6 +7184,7 @@ class ResponseItem( | WebSearchCallResponseItem | ImageGenerationCallResponseItem | CompactionResponseItem + | CompactionTriggerResponseItem | ContextCompactionResponseItem | OtherResponseItem ] @@ -7229,6 +7205,7 @@ class ResponseItem( | WebSearchCallResponseItem | ImageGenerationCallResponseItem | CompactionResponseItem + | CompactionTriggerResponseItem | ContextCompactionResponseItem | OtherResponseItem ) @@ -7448,8 +7425,9 @@ class Config(BaseModel): ), ] = None compact_prompt: str | None = None + desktop: dict[str, Any] | None = None developer_instructions: str | None = None - forced_chatgpt_workspace_id: str | None = None + forced_chatgpt_workspace_id: ForcedChatgptWorkspaceIds | None = None forced_login_method: ForcedLoginMethod | None = None instructions: str | None = None model: str | None = None @@ -7827,7 +7805,7 @@ class ThreadForkResponse(BaseModel): sandbox: Annotated[ SandboxPolicy, Field( - description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance." ), ] service_tier: Annotated[str | None, Field(alias="serviceTier")] = None @@ -7895,7 +7873,7 @@ class ThreadResumeResponse(BaseModel): sandbox: Annotated[ SandboxPolicy, Field( - description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance." ), ] service_tier: Annotated[str | None, Field(alias="serviceTier")] = None @@ -7940,7 +7918,7 @@ class ThreadStartResponse(BaseModel): sandbox: Annotated[ SandboxPolicy, Field( - description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + description="Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance." ), ] service_tier: Annotated[str | None, Field(alias="serviceTier")] = None @@ -7998,11 +7976,13 @@ class ClientRequest( | MarketplaceRemoveRequest | MarketplaceUpgradeRequest | PluginListRequest + | PluginInstalledRequest | PluginReadRequest | PluginSkillReadRequest | PluginShareSaveRequest | PluginShareUpdateTargetsRequest | PluginShareListRequest + | PluginShareCheckoutRequest | PluginShareDeleteRequest | AppListRequest | FsReadFileRequest @@ -8079,11 +8059,13 @@ class ClientRequest( | MarketplaceRemoveRequest | MarketplaceUpgradeRequest | PluginListRequest + | PluginInstalledRequest | PluginReadRequest | PluginSkillReadRequest | PluginShareSaveRequest | PluginShareUpdateTargetsRequest | PluginShareListRequest + | PluginShareCheckoutRequest | PluginShareDeleteRequest | AppListRequest | FsReadFileRequest @@ -8135,6 +8117,16 @@ class ClientRequest( ] +class PluginInstalledResponse(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + marketplace_load_errors: Annotated[ + list[MarketplaceLoadErrorInfo] | None, Field(alias="marketplaceLoadErrors") + ] = [] + marketplaces: list[PluginMarketplaceEntry] + + class PluginListResponse(BaseModel): model_config = ConfigDict( populate_by_name=True, diff --git a/sdk/python/tests/test_artifact_workflow_and_binaries.py b/sdk/python/tests/test_artifact_workflow_and_binaries.py index c7b5713ba1..0fb97b8e38 100644 --- a/sdk/python/tests/test_artifact_workflow_and_binaries.py +++ b/sdk/python/tests/test_artifact_workflow_and_binaries.py @@ -158,8 +158,8 @@ def test_schema_normalization_only_flattens_string_literal_oneofs( "AuthMode", "InputModality", "ExperimentalFeatureStage", - "CommandExecOutputStream", "ProcessOutputStream", + "CommandExecOutputStream", ] @@ -249,11 +249,11 @@ def test_source_sdk_package_pins_published_runtime() -> None: "runtime_pin": script.pinned_runtime_version(), "dependencies": pyproject["project"]["dependencies"], } == { - "sdk_version": "0.131.0a4", - "runtime_pin": "0.131.0a4", + "sdk_version": "0.1.0b1", + "runtime_pin": "0.132.0", "dependencies": [ "pydantic>=2.12", - "openai-codex-cli-bin==0.131.0a4", + "openai-codex-cli-bin==0.132.0", ], } @@ -284,19 +284,26 @@ def test_release_metadata_retries_without_invalid_auth( assert authorizations == ["Bearer invalid-token", None] -def test_runtime_setup_uses_pep440_package_version_and_codex_release_tags() -> None: - """The SDK uses PEP 440 package pins and converts only when fetching releases.""" +def test_runtime_setup_reads_independent_runtime_pin_and_release_tags() -> None: + """Runtime package pins remain independent of the SDK beta version.""" runtime_setup = _load_runtime_setup_module() pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text()) - assert runtime_setup.PACKAGE_NAME == "openai-codex-cli-bin" - assert runtime_setup.pinned_runtime_version() == pyproject["project"]["version"] - assert ( - f"{runtime_setup.PACKAGE_NAME}=={pyproject['project']['version']}" - in pyproject["project"]["dependencies"] - ) - assert runtime_setup._normalized_package_version("rust-v0.116.0-alpha.1") == "0.116.0a1" - assert runtime_setup._release_tag("0.116.0a1") == "rust-v0.116.0-alpha.1" + assert { + "package_name": runtime_setup.PACKAGE_NAME, + "sdk_version": pyproject["project"]["version"], + "runtime_pin": runtime_setup.pinned_runtime_version(), + "normalized_release_version": runtime_setup._normalized_package_version( + "rust-v0.116.0-alpha.1" + ), + "release_tag": runtime_setup._release_tag("0.116.0a1"), + } == { + "package_name": "openai-codex-cli-bin", + "sdk_version": "0.1.0b1", + "runtime_pin": "0.132.0", + "normalized_release_version": "0.116.0a1", + "release_tag": "rust-v0.116.0-alpha.1", + } @pytest.mark.parametrize( @@ -491,23 +498,32 @@ def test_runtime_package_layout_is_included_by_wheel_config( ] -def test_stage_sdk_release_injects_exact_runtime_pin(tmp_path: Path) -> None: +def test_stage_sdk_release_preserves_reviewed_runtime_pin(tmp_path: Path) -> None: script = _load_update_script_module() staged = script.stage_python_sdk_package( tmp_path / "sdk-stage", - "rust-v0.116.0-alpha.1", + "0.1.0b1", ) - pyproject = (staged / "pyproject.toml").read_text() - assert 'name = "openai-codex"' in pyproject - assert 'version = "0.116.0a1"' in pyproject - assert '"openai-codex-cli-bin==0.116.0a1"' in pyproject + pyproject = tomllib.loads((staged / "pyproject.toml").read_text()) + assert { + "name": pyproject["project"]["name"], + "version": pyproject["project"]["version"], + "dependencies": pyproject["project"]["dependencies"], + } == { + "name": "openai-codex", + "version": "0.1.0b1", + "dependencies": [ + "pydantic>=2.12", + "openai-codex-cli-bin==0.132.0", + ], + } assert ( - '__version__ = "0.116.0a1"' + '__version__ = "0.1.0b1"' not in (staged / "src" / "openai_codex" / "__init__.py").read_text() ) assert ( - 'client_version: str = "0.116.0a1"' + 'client_version: str = "0.1.0b1"' not in (staged / "src" / "openai_codex" / "client.py").read_text() ) assert not any((staged / "src" / "openai_codex").glob("bin/**")) @@ -520,34 +536,41 @@ def test_stage_sdk_release_replaces_existing_staging_dir(tmp_path: Path) -> None old_file.parent.mkdir(parents=True) old_file.write_text("stale") - staged = script.stage_python_sdk_package(staging_dir, "0.116.0a1") + staged = script.stage_python_sdk_package(staging_dir, "0.1.0b1") assert staged == staging_dir assert not old_file.exists() -def test_staged_sdk_and_runtime_versions_match(tmp_path: Path) -> None: +def test_sdk_beta_release_can_pin_stable_runtime(tmp_path: Path) -> None: script = _load_update_script_module() package_archive = _write_fake_codex_package_archive(tmp_path, script) sdk_stage = script.stage_python_sdk_package( tmp_path / "sdk-stage", - "rust-v0.116.0-alpha.1", + "0.1.0b1", ) runtime_stage = script.stage_python_runtime_package( tmp_path / "runtime-stage", - "rust-v0.116.0-alpha.1", + "0.132.0", package_archive, ) sdk_pyproject = tomllib.loads((sdk_stage / "pyproject.toml").read_text()) runtime_pyproject = tomllib.loads((runtime_stage / "pyproject.toml").read_text()) - assert sdk_pyproject["project"]["version"] == runtime_pyproject["project"]["version"] - assert sdk_pyproject["project"]["dependencies"] == [ - "pydantic>=2.12", - "openai-codex-cli-bin==0.116.0a1", - ] + assert { + "sdk_version": sdk_pyproject["project"]["version"], + "runtime_version": runtime_pyproject["project"]["version"], + "sdk_dependencies": sdk_pyproject["project"]["dependencies"], + } == { + "sdk_version": "0.1.0b1", + "runtime_version": "0.132.0", + "sdk_dependencies": [ + "pydantic>=2.12", + "openai-codex-cli-bin==0.132.0", + ], + } def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None: @@ -557,16 +580,16 @@ def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None: [ "stage-sdk", str(tmp_path / "sdk-stage"), - "--codex-version", - "rust-v0.116.0-alpha.1", + "--sdk-version", + "0.1.0b1", ] ) def fake_generate_types() -> None: calls.append("generate_types") - def fake_stage_sdk_package(_staging_dir: Path, codex_version: str) -> Path: - calls.append(f"stage_sdk:{codex_version}") + def fake_stage_sdk_package(_staging_dir: Path, sdk_version: str) -> Path: + calls.append(f"stage_sdk:{sdk_version}") return tmp_path / "sdk-stage" def fake_stage_runtime_package( @@ -589,26 +612,7 @@ def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None: script.run_command(args, ops) - assert calls == ["generate_types", "stage_sdk:0.116.0a1"] - - -def test_stage_sdk_rejects_mismatched_legacy_versions(tmp_path: Path) -> None: - script = _load_update_script_module() - args = script.parse_args( - [ - "stage-sdk", - str(tmp_path / "sdk-stage"), - "--codex-version", - "0.116.0a1", - "--runtime-version", - "0.116.0a1", - "--sdk-version", - "0.115.0", - ] - ) - - with pytest.raises(RuntimeError, match="versions must match"): - script.run_command(args, script.default_cli_ops()) + assert calls == ["generate_types", "stage_sdk:0.1.0b1"] def test_stage_runtime_stages_package_without_type_generation(tmp_path: Path) -> None: diff --git a/sdk/python/tests/test_contract_generation.py b/sdk/python/tests/test_contract_generation.py index f809d05294..2c95d06a3e 100644 --- a/sdk/python/tests/test_contract_generation.py +++ b/sdk/python/tests/test_contract_generation.py @@ -40,7 +40,7 @@ def test_generated_files_are_up_to_date(): # Regenerate contract artifacts via the pinned runtime package, not a local # app-server binary from the checkout or CI environment. - assert importlib.metadata.version("openai-codex-cli-bin") == "0.131.0a4" + assert importlib.metadata.version("openai-codex-cli-bin") == "0.132.0" env = os.environ.copy() env.pop("CODEX_EXEC_PATH", None) python_bin = str(Path(sys.executable).parent) diff --git a/sdk/python/uv.lock b/sdk/python/uv.lock index d8d079e722..8807f4f591 100644 --- a/sdk/python/uv.lock +++ b/sdk/python/uv.lock @@ -2,6 +2,13 @@ version = 1 revision = 3 requires-python = ">=3.10" +[options] +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer-span = "P7D" + +[options.exclude-newer-package] +openai-codex-cli-bin = "2026-05-20T21:00:00Z" + [[package]] name = "annotated-types" version = "0.7.0" @@ -275,7 +282,7 @@ wheels = [ [[package]] name = "openai-codex" -version = "0.131.0a4" +version = "0.1.0b1" source = { editable = "." } dependencies = [ { name = "openai-codex-cli-bin" }, @@ -292,7 +299,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "datamodel-code-generator", marker = "extra == 'dev'", specifier = "==0.31.2" }, - { name = "openai-codex-cli-bin", specifier = "==0.131.0a4" }, + { name = "openai-codex-cli-bin", specifier = "==0.132.0" }, { name = "pydantic", specifier = ">=2.12" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.8" }, @@ -301,15 +308,15 @@ provides-extras = ["dev"] [[package]] name = "openai-codex-cli-bin" -version = "0.131.0a4" +version = "0.132.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/9f/f9fc4bb1b2b7a20d4d65143ebb4c4dcd2301a718183b539ecb5b1c0ac3ec/openai_codex_cli_bin-0.131.0a4-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:db0f3cb7dda310641ac04fbaf3f128693a3817ab83ae59b67a3c9c74bd53f8b8", size = 88367585, upload-time = "2026-05-09T06:14:09.453Z" }, - { url = "https://files.pythonhosted.org/packages/dc/39/eb95ed0e8156669e895a192dec760be07dabe891c3c6340f7c6487b9a976/openai_codex_cli_bin-0.131.0a4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6cae5af6edca7f6d3f0bcbbd93cfc8a6dc3e33fb5955af21ae492b6d5d0dcb72", size = 79245567, upload-time = "2026-05-09T06:14:13.581Z" }, - { url = "https://files.pythonhosted.org/packages/0c/92/ade176fa78d746d5ff7a6e371d64740c0d95ab299b0dd58a5404b89b3915/openai_codex_cli_bin-0.131.0a4-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5728f9887baf62d7e72f4f242093b3ff81e26c81d80d346fe1eef7eda6838aa8", size = 77758628, upload-time = "2026-05-09T06:14:18.374Z" }, - { url = "https://files.pythonhosted.org/packages/28/e6/bfe6c65f8e3e5499f71b24c3b6e8d07e4d426543d25e429b9b141b544e5f/openai_codex_cli_bin-0.131.0a4-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d7a47fd3667fbcc216593839c202deffa056e9b3d46c6933e72594d461f4fea0", size = 84535509, upload-time = "2026-05-09T06:14:22.851Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b7/53dc094a691ab6f2ca079e8e865b122843809ac4fad51cac4d59021e599d/openai_codex_cli_bin-0.131.0a4-py3-none-win_amd64.whl", hash = "sha256:c61bcf029672494c4c7fdc8567dbaa659a48bb75641d91c2ade27c1e46803434", size = 88185543, upload-time = "2026-05-09T06:14:27.282Z" }, - { url = "https://files.pythonhosted.org/packages/82/99/e0852ffcf9b4d2794fef83e0c3a267b3c773a776f136e9f7ce19f0c8df42/openai_codex_cli_bin-0.131.0a4-py3-none-win_arm64.whl", hash = "sha256:bbde750186861f102e346ac066f4e9608f515f7b71b16a6e8b7ef1ddc02a97a5", size = 81196380, upload-time = "2026-05-09T06:14:32.103Z" }, + { url = "https://files.pythonhosted.org/packages/be/a1/b92b7a1b73a83785d2e1dcd0faecd1b7f886a38cf02a30abe1c35f42f0f7/openai_codex_cli_bin-0.132.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:1c22b51dbd679413f84f00b9d8fd4e5cf8a1c0d1c7cc8c42bcb3f9f1b33e2334", size = 89403211, upload-time = "2026-05-20T02:37:22.311Z" }, + { url = "https://files.pythonhosted.org/packages/5f/68/163272e582de55a7f460e2329281267908d75d0fbcbbbb2c6749a6329e6b/openai_codex_cli_bin-0.132.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:56217495e6635c8a5d96df820cc0da5f46cd9b6ec6f3a5f67f1607d69ef74256", size = 79058685, upload-time = "2026-05-20T02:37:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/0b/18/a60c6b137e7cd3959cae16ba757f57ca5702979b0ea107a21f516ba15d98/openai_codex_cli_bin-0.132.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:09642e7578a3078bfccc82af4077b085d42b0022b529e4b5c645e0a0af3397a4", size = 78689038, upload-time = "2026-05-20T02:37:31.548Z" }, + { url = "https://files.pythonhosted.org/packages/f8/eb/1b184307a67c1006d59b61636bcfcea73a89aa95271f6516ed28dce554ca/openai_codex_cli_bin-0.132.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:85aec095f9d144d7a2d1aff39fce77b7240f42014580c35801ba74b9317aa5f7", size = 85528820, upload-time = "2026-05-20T02:37:36.559Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e8/1b823a8bf7b96d1513905ad79b16a146d797f81a19a6bc350a2f95a16661/openai_codex_cli_bin-0.132.0-py3-none-win_amd64.whl", hash = "sha256:3cb5c90c55baa39bd5ddc890d2068d3e1322a57a54d1d0e623819009a205c7f5", size = 86916218, upload-time = "2026-05-20T02:37:41.886Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e6/bb8634bd4f3adaea299c95d7b03105ac417e32dd6d8bc2af5dda141d6f28/openai_codex_cli_bin-0.132.0-py3-none-win_arm64.whl", hash = "sha256:74ef93d3deef7cb83c71d19fc667defe749cdab337ec331f59a23511561b6f6a", size = 79892931, upload-time = "2026-05-20T02:37:46.828Z" }, ] [[package]]