From f4485a28b5f2850f5be0a5fb0722456bb1bb986b Mon Sep 17 00:00:00 2001 From: Eloi Date: Sun, 6 Sep 2026 23:39:30 +0200 Subject: [PATCH] Upgrade SDK integrations and complete the PR workflow --- .claude/commands/agent-runtime-kit/upgrade.md | 13 +- .../skills/agent-runtime-kit-upgrade/SKILL.md | 280 ++++++++++-------- CHANGELOG.md | 18 ++ docs/providers.md | 20 +- docs/sdk-evolution-agent.md | 9 + examples/sdk_evolution_agent/release_notes.py | 25 +- src/agent_runtime_kit/adapters/antigravity.py | 57 +++- src/agent_runtime_kit/compatibility.py | 4 +- tests/test_antigravity_adapter.py | 92 +++++- tests/test_sdk_contract.py | 86 +++++- tests/test_sdk_evolution_agent.py | 43 +++ uv.lock | 26 +- 12 files changed, 501 insertions(+), 172 deletions(-) diff --git a/.claude/commands/agent-runtime-kit/upgrade.md b/.claude/commands/agent-runtime-kit/upgrade.md index e5ea7b2..472bea5 100644 --- a/.claude/commands/agent-runtime-kit/upgrade.md +++ b/.claude/commands/agent-runtime-kit/upgrade.md @@ -1,6 +1,6 @@ --- name: "Agent Runtime Kit: Upgrade" -description: Inspect and safely upgrade agent-runtime-kit vendor SDK dependencies +description: Upgrade and adapt agent-runtime-kit vendor SDK integrations through a verified pull request category: Workflow tags: [agent-runtime-kit, sdk-evolution, upgrade, workflow] --- @@ -9,12 +9,11 @@ tags: [agent-runtime-kit, sdk-evolution, upgrade, workflow] Read and follow the canonical repository workflow in `.codex/skills/agent-runtime-kit-upgrade/SKILL.md`. That file owns candidate -discovery, no-cooloff resolution, report-first gates, local upgrade -authorization, verification, and PR publication rules; do not duplicate or +discovery, no-cooloff resolution, report-first gates, upstream adaptation, +verification, and PR publication rules; do not duplicate or weaken them here. For this Claude command, use `claude-agent-sdk` with `--extra claude` unless the -user selected another runtime. Preserve every other authorization boundary from -the canonical workflow: an explicit SDK upgrade request permits the gated local -dependency update, while branch, commit, push, PR, merge, and release actions -remain separately authorized. +user selected another runtime. Complete the canonical workflow, including the +necessary source adaptations and regular PR, for an upgrade invocation. Honor +explicit report-only or local-only limits. Never auto-merge or publish a release. diff --git a/.codex/skills/agent-runtime-kit-upgrade/SKILL.md b/.codex/skills/agent-runtime-kit-upgrade/SKILL.md index bd803a7..7e47e3f 100644 --- a/.codex/skills/agent-runtime-kit-upgrade/SKILL.md +++ b/.codex/skills/agent-runtime-kit-upgrade/SKILL.md @@ -1,76 +1,45 @@ --- name: agent-runtime-kit-upgrade -description: Inspect and safely upgrade agent-runtime-kit's Claude Agent SDK, OpenAI Codex SDK and coupled CLI binary, and Google Antigravity SDK dependencies. Use for SDK freshness checks, upgrades, compatibility repairs, or upgrade PRs in agent-runtime-kit. +description: Upgrade agent-runtime-kit's Claude, Codex and coupled CLI, and Antigravity SDK integrations, implement the adaptations required by upstream evolution, verify the complete change, and create a pull request. Also supports explicitly requested report-only freshness and compatibility checks. --- # Agent Runtime Kit SDK Upgrade -Use the repository-owned evolution runner for evidence collection, candidate -inspection, compatibility gates, dependency updates, and verification. - -## Authorization - -- Always run a report-only pass before changing dependencies. -- An explicit request to upgrade, refresh, or update the SDKs authorizes the - gated local `pyproject.toml`, `uv.lock`, and compatibility-manifest update - after the report passes. -- A bare skill invocation or freshness question is report-only. -- Creating a branch, commit, push, or pull request requires an explicit request - for that publication action. Never auto-merge or publish a release. - -## Invariants - -- Discover each monitored package's latest upstream release independently of - the repository's current constraints. A constraint that excludes a newer - release is a result to investigate, not proof that the repository is current. -- Keep these facts separate in the report: upstream latest, compatible - candidate, current resolver result, prospective resolver result, and applied - lock version. -- `openai-codex-cli-bin` is coupled to the exact version required by the - latest published `openai-codex`. Record a newer standalone CLI release as a - staged artifact, not a blocked candidate. It may be fingerprinted in a - credential-scrubbed disposable environment for implementation-history - analysis, but never select it into the project unless the SDK requires it. -- Direction-of-travel means trends in actual upstream implementations. Inspect - recent release files and Python definitions even when no dependency update is - pending. Never turn the direction report into upgrade, hold, resolver, or - lockfile advice; that belongs to the architecture and implementation stages. -- Run UV without freshness delays. The runner removes cutoff environment - variables and uses `uv lock --exclude-newer false`; do not reintroduce the - retired repository cooloff or package-specific exemptions. -- Candidate API snapshots and behavior probes must cover every exact - `(package, baseline, candidate)` transition. Field presence alone is not a - semantic probe: construct the provider configuration and exercise the - adapter-owned contract. -- Never promote or create a PR unless the update produced a non-empty diff, the - lock contains every inspected compatible candidate exactly, and all - verification commands passed. - -## Checkout Preflight - -Fetch and compare the current checkout with the requested base. Work directly -only when it is the intended clean checkout. Preserve dirty or unrelated work. -When isolation is needed, use a unique path rather than the old shared `/tmp` -path: - -```bash -git fetch origin --prune -scratch="$(mktemp -d "${TMPDIR:-/tmp}/ark-sdk-evolution.XXXXXX")" -worktree="$scratch/worktree" -branch="sdk-evolution-upgrade-$(date +%Y%m%d-%H%M%S)-$$" -git worktree add -b "$branch" "$worktree" origin/main -``` - -Announce the selected checkout and base. Do not delete a worktree that contains -uncommitted work. If repository instructions mention a GSD command that is not -actually installed, record the investigation and plan in the available task -plan, then continue; an unavailable wrapper is not a reason to abandon an -explicitly requested repair. - -## Runtime Preflight - -Use `codex-agent-sdk` unless the user selected another runtime. Match runtime -and optional extra: +Complete the SDK upgrade through a verified pull request. Use the repository's +evolution runner for evidence collection, direction analysis, architecture and +review gates, and atomic dependency updates. Implement the necessary runtime, +adapter, public API, test, and documentation changes as part of the same work. + +## Scope and Completion + +- An explicit invocation of this upgrade skill, or a request to upgrade, update, + or refresh these SDK integrations, requests the complete workflow: investigate, + adapt, update dependencies, verify, create a branch, commit, push, and open a + regular pull request. Do not stop after producing a report or ask for the same + authorization again at each stage. +- Honor narrower user instructions. A freshness/status question or an explicit + report-only request performs inspection without changes or publication. A + local-only upgrade performs the adaptation and verification without a push + or PR. Never infer upgrade authority merely from a question about versions. +- Always collect and review evidence before implementation. Report-first is a + sequencing requirement; the requested upgrade is complete when the necessary + adaptations and dependency changes are in a verified PR. +- Never auto-merge or publish a release. If there is no justified change, report + the evidence and do not manufacture a diff or an empty PR. + +## Checkout and Runtime Preflight + +Fetch `origin` and compare the checkout with the requested base, normally +`origin/main`. Preserve unrelated dirty work. Use an intended clean checkout or +a fresh worktree with a unique path. For an upgrade, create one unique branch; +for report-only isolation, use a detached worktree. Keep using that worktree +when later reruns contain this workflow's own reviewed edits. + +Announce the selected checkout and base. If repository instructions mention a +GSD command that is not installed, record the plan with an available planning +mechanism and continue the authorized work; do not wait for a missing wrapper. + +Use `codex-agent-sdk` unless the user selected another runtime. Match the extra: - `claude-agent-sdk` -> `--extra claude` - `codex-agent-sdk` -> `--extra codex` @@ -79,19 +48,25 @@ and optional extra: Use supported authentication only. For Codex, prepare the dedicated SDK home: ```bash -env -u UV_EXCLUDE_NEWER \ +env -u UV_EXCLUDE_NEWER -u UV_EXCLUDE_NEWER_PACKAGE \ uv run --locked --extra codex python -m examples.sdk_evolution_agent.auth ensure-codex ``` -If authentication fails, stop and report that concrete blocker. Do not scrape -or reconstruct credentials. +If authentication fails, report the concrete blocker. Do not scrape or +reconstruct credentials. Honor an explicitly requested model and reasoning +effort rather than silently using the runner's defaults. Verify that selection +with a small structured runtime call before expensive collection. The installed +SDK's supported `CodexConfig.codex_bin` and the adapter's `config_cls` injection +can select an already-installed executable when the bundled CLI cannot run the +requested model. Record that analysis-driver override separately from the +inspected and locked SDK/CLI versions; it does not establish package compatibility. -## Report-Only Pass +## Evidence Pass -Run all four monitored packages unless the user explicitly narrowed scope: +Run all four monitored packages unless the user narrowed scope: ```bash -env -u UV_EXCLUDE_NEWER \ +env -u UV_EXCLUDE_NEWER -u UV_EXCLUDE_NEWER_PACKAGE \ uv run --locked --extra codex python -m examples.sdk_evolution_agent \ --runtime codex-agent-sdk \ --refresh-preview \ @@ -102,37 +77,79 @@ env -u UV_EXCLUDE_NEWER \ --package google-antigravity ``` -`--inspect-candidates` is explicit consent to install and import exact locked baselines and candidate -versions in credential-scrubbed temporary environments. The runner reuses each -package/version environment for its API snapshot and behavior probe, retries -transient metadata/install failures, and prints progress while it works. - -Read the newest `reports/sdk-evolution//report.md` plus its raw JSON -artifacts, especially `implementation_diffs.json` and `behavior_summary.json`. -The implementation-trend section must describe observed source/artifact changes -and explicit binary limitations, never dependency action. Treat any of these as a hard -implementation blocker: - -- missing or failed no-cooloff prospective resolver preview; -- upstream candidate absent from the exact API-diff inventory; -- missing required release evidence; -- behavior status `fail` or `incomplete`, malformed/contradictory evidence, or - a missing exact-version comparison; -- `manual_design_required`, reviewer rejection, or an unsafe architecture - decision; -- recursive runtime impact without an explicit adaptation and rerun plan. +`--inspect-candidates` is explicit consent to install and import exact locked +baselines and candidates in credential-scrubbed temporary environments. The +runner reuses each package/version environment for API snapshots and behavior +probes, retries transient collection failures, and prints progress. + +Read `reports/sdk-evolution//report.md` and the raw artifacts, +especially `evidence.json`, `api_diffs.json`, `implementation_diffs.json`, +`release_notes.json`, `behavior_probes.json`, `behavior_diffs.json`, +`behavior_summary.json`, `direction_analysis.json`, `architecture_decision.json`, +and `review.json`. + +Preserve these evidence invariants: + +- Discover latest upstream releases independently of current constraints. Keep + upstream latest, compatible candidate, current resolver, prospective resolver, + and applied lock versions separate. An excluding bound is an investigation + finding, not proof that the integration is current. +- Run UV without freshness delays: remove cutoff variables and use + `uv lock --exclude-newer false`. Do not reintroduce cooloffs or exemptions. +- The Codex CLI candidate is the exact dependency required by the published + Codex SDK. A newer standalone CLI is a staged artifact for implementation + history; never select it into the package unless the SDK requires it. +- Inspect recent upstream release files and Python definitions even when no + version bump is pending. Describe observed implementation trends and binary + visibility limits in the direction report; put adaptation decisions in the + architecture plan. +- Cover every exact `(package, baseline, candidate)` transition with API and + behavior evidence. Construct real provider configurations using values accepted + by the public API and exercise the adapter-owned contract. Field presence alone + does not establish semantic compatibility. + +## Adapt to Upstream Evolution + +For every substantive implementation trend, trace its impact through the current +runtime and adapter. Record the evidence, the existing behavior, and the decision: +adapt now, already supported, or outside this package's scope with a concrete +reason. Consider changed options and defaults, permissions, tools/MCP, sessions, +events, results, structured output, lifecycle, and capabilities where the evidence +shows an impact. Passing the current probes does not by itself prove that the +integration exposes the vendor capabilities it should support. + +Implement the necessary changes in the owning layer, including regression +coverage for the observed contract and relevant docs. Preserve the small typed +runtime API, vendor capabilities, optional extras, and Python 3.10 support. +Avoid speculative features or importing upstream orchestration/model policy. +When the change affects the evolution runner's own use of `AgentTask`, +`AgentResult`, `RuntimeRegistry`, adapters, output schemas, event sinks, +permissions, or typed errors, adapt those usages and rerun the workflow too. + +The runner's deterministic implementation lane updates dependencies and the +compatibility manifest; it does not implement adapter source changes. Perform +those changes in the repository coding workflow, then rerun the evidence and +review stages. Do not treat that runner limitation as a reason to abandon an +authorized adaptation or reduce the task to version bumps. + +These findings block promotion until resolved: missing/failed resolver or +release evidence; an absent exact candidate API diff; snapshot import errors; +behavior status `fail` or `incomplete`; contradictory evidence; +`manual_design_required`; reviewer rejection; or unsafe recursive adaptation. +Investigate, repair the owned contract or evidence collector, and rerun the +gates. Do not override a rejection, weaken a probe, or relabel missing evidence +to obtain a pass. Ask for user input only for an unresolved product decision, +missing authorization, or external blocker that prevents further progress. `pass` means complete unchanged behavior evidence. `changed` means complete, -non-breaking behavior evidence. Neither status permits ignoring snapshot import -errors or a failed resolver preview. +non-breaking evidence. Neither permits ignoring other failed gates. -## Apply a Requested Local Upgrade +## Apply and Verify the Upgrade -When the user explicitly requested an upgrade and the report-only pass is green, -rerun with `--implementation-enabled` and without PR flags: +Once the adaptation and evidence support promotion, run the implementation pass: ```bash -env -u UV_EXCLUDE_NEWER \ +env -u UV_EXCLUDE_NEWER -u UV_EXCLUDE_NEWER_PACKAGE \ uv run --locked --extra codex python -m examples.sdk_evolution_agent \ --runtime codex-agent-sdk \ --refresh-preview \ @@ -144,40 +161,43 @@ env -u UV_EXCLUDE_NEWER \ --package google-antigravity ``` -The deterministic implementation lane widens only excluding upper bounds, -updates the lock with `--exclude-newer false`, verifies exact target versions, -updates `src/agent_runtime_kit/compatibility.py` to the exact tested SDK and -coupled-runtime versions, and rolls all three artifacts back on resolver -mismatch or test failure. If a candidate requires adapter source changes, the -runner must block; repair the evidenced contract in the scoped coding workflow, -add a regression test, and rerun the report before applying dependencies. - -## Pull Request Mode - -Only when the user requested a PR, add a unique branch and the PR flags to the -green implementation pass: - -```bash ---create-branch --branch-name "$branch" --draft-pr --pr-base main -``` - -The runner may stage only its reported `changed_paths`. Verify the exact pushed -head, PR URL, changed files, and CI state. Do not claim a PR exists merely -because `--draft-pr` was requested; the runner skips publication when the -change is empty, unapplied, or unverified. - -## Final Verification +The runner widens only excluding upper bounds, resolves without cooloffs, +verifies exact targets, and updates `src/agent_runtime_kit/compatibility.py`. +Project declarations, `uv.lock`, and the compatibility manifest form one atomic +update and roll back together on mismatch or verification failure. Preserve +the adaptation edits and fix the evidenced failure before retrying. -Verify the deciding live surface again: +Verify the complete diff, including source adaptations, against the final lock: ```bash -env -u UV_EXCLUDE_NEWER uv lock --check -env -u UV_EXCLUDE_NEWER uv run --locked ruff check . -env -u UV_EXCLUDE_NEWER uv run --locked mypy -env -u UV_EXCLUDE_NEWER uv run --locked pytest +env -u UV_EXCLUDE_NEWER -u UV_EXCLUDE_NEWER_PACKAGE uv lock --check +env -u UV_EXCLUDE_NEWER -u UV_EXCLUDE_NEWER_PACKAGE uv run --locked ruff check . +env -u UV_EXCLUDE_NEWER -u UV_EXCLUDE_NEWER_PACKAGE uv run --locked mypy +env -u UV_EXCLUDE_NEWER -u UV_EXCLUDE_NEWER_PACKAGE uv run --locked pytest ``` -Report the baseline and applied versions, candidate classifications, behavior -status, exact verification results, changed files, report path, and any -remaining blocker or uncertainty. For PR work, also report the verified URL, -head SHA, and current checks. +Run targeted contract/regression checks for the changed behavior. Distinguish +local semantic probes from live provider checks and disclose skips. Review the +actual cumulative source and dependency diff; a green pre-change design review +does not replace review of the implementation. + +## Create the Pull Request + +Complete this step for the default upgrade workflow without another permission +question. Stage only the reviewed SDK upgrade and adaptation paths, including +their tests and docs; the runner's dependency-only `changed_paths` is not the +whole scope when source changes were necessary. Keep reports and unrelated work +out of the commit. + +Commit, push the unique branch, and use `gh pr create` to open a regular PR +against the requested base. Do not use the runner's `--draft-pr` flag. Write the +PR body to a temporary file and pass `--body-file`; describe the upstream changes, +resulting integration behavior, exact versions, and validation. Update an +existing PR for this branch instead of creating a duplicate. + +Verify the pushed head, PR URL, base/head, changed files, and CI. Resolve relevant +failures and wait for checks to finish, or identify a concrete external blocker. +The handoff must include the PR URL, exact versions, implemented adaptations and +justified scope decisions, review/verification results, report paths, and any +remaining uncertainty. For an explicit report-only or local-only request, stop +at its requested boundary instead. diff --git a/CHANGELOG.md b/CHANGELOG.md index a0fd38e..6ca02a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Updated tested Claude Agent SDK to `0.2.152` and Google Antigravity SDK to + `0.1.16`; the Codex SDK and its coupled CLI remain at `0.147.0`. +- Upgrade skill invocations now include necessary runtime adaptations, + verification, and a regular pull request. Explicit report-only and local-only + requests retain their narrower scope. + +### Fixed + +- Antigravity preserves explicit `vertex=True` with an API key for Vertex + Express authentication, rejects mixed regional credentials, and refuses older + SDKs or configurations that would silently drop the requested endpoint. +- Release-note collection recognizes versions present only in a GitHub + discussion title and rejects unrelated or longer version-prefix matches. + ## 0.5.1 - 2026-08-29 ### Added diff --git a/docs/providers.md b/docs/providers.md index 00634ec..9e28b39 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -188,11 +188,25 @@ constructor, `GEMINI_API_KEY`, or `GOOGLE_API_KEY`. Without an API key, the adapter uses Vertex AI config with Google ADC when a project can be discovered from ADC, `GOOGLE_CLOUD_PROJECT`, or `GCLOUD_PROJECT`; location defaults to `global` unless `GOOGLE_CLOUD_LOCATION`, `GOOGLE_CLOUD_REGION`, or -`CLOUD_ML_REGION` is set. Precedence: an explicit constructor `api_key` is the -most specific request and wins; otherwise an explicit +`CLOUD_ML_REGION` is set. An explicit constructor `api_key` takes precedence over +ambient credentials. With `vertex=True`, that key selects Vertex Express mode +(supported by released `google-antigravity>=0.1.16` versions): no ADC, project, +or location is required. Older or unverifiable SDK releases raise +`UnsupportedTaskInputError` for Express mode; their existing authentication +modes remain available. Explicit project/location arguments cannot be combined +with Express mode. For example: + +```python +runtime = AntigravityAgentRuntime(vertex=True, api_key=vertex_express_key) +``` + +Without an explicit key, an explicit `AntigravityAgentRuntime(vertex=True, ...)` takes precedence over an ambient `GEMINI_API_KEY`/`GOOGLE_API_KEY`, so a Vertex-configured runtime is never -silently redirected to the Gemini API by an exported environment key. Under +silently redirected to the Gemini API by an exported environment key. When no +model is supplied, the adapter preserves the vendor default (`gemini-3.8-flash` +in Antigravity 0.1.16); supply `default_model` or `AgentTask.model` when a stable +model selection is required. Under `PERMISSIVE`, `disallowed_tools` maps to `CapabilitiesConfig.disabled_tools` (the baseline is every tool, so "enable everything else" is exact); under any other posture the deny-list is folded into an allow-list of the mode's baseline diff --git a/docs/sdk-evolution-agent.md b/docs/sdk-evolution-agent.md index 15789a4..43d61b7 100644 --- a/docs/sdk-evolution-agent.md +++ b/docs/sdk-evolution-agent.md @@ -4,6 +4,15 @@ The SDK evolution agent is a local dogfood workflow for keeping agent-runtime-kit aligned with Claude Agent SDK, OpenAI Codex SDK, and Google Antigravity SDK as those upstream packages evolve. +For a complete upgrade, invoke the repository's +[`agent-runtime-kit-upgrade` skill](../.codex/skills/agent-runtime-kit-upgrade/SKILL.md). +It collects evidence first, maps upstream implementation trends to the current +integration, implements the necessary adapter/runtime changes with tests and +docs, applies the compatible dependency update, and creates a verified regular +pull request. Explicit report-only and local-only requests retain those limits. +The CLI examples below describe the underlying runner; its report-only default +and dependency-only implementation lane are stages within that broader skill. + For the intended architecture, evidence contract, behavior probe strategy, changelog strategy, caveats, and alternatives, see [`docs/sdk-evolution-agent-design.md`](sdk-evolution-agent-design.md). diff --git a/examples/sdk_evolution_agent/release_notes.py b/examples/sdk_evolution_agent/release_notes.py index 1567313..7e2daee 100644 --- a/examples/sdk_evolution_agent/release_notes.py +++ b/examples/sdk_evolution_agent/release_notes.py @@ -349,14 +349,21 @@ def _summaries_for_interval( ) -> list[str]: text = _prefer_github_discussion_body(text) lines = [line.strip() for line in text.splitlines()] - version_patterns = [to_version] + versions = [to_version] if from_version: - version_patterns.append(from_version) + versions.append(from_version) + version_patterns = [ + re.compile(rf"(?= 0: + version_match = version_patterns[0].search(compact) + if version_match: + version_index = version_match.start() start = max(0, version_index - 160) end = min(len(compact), version_index + 320) matches.append(_clean_summary(compact[start:end])) @@ -380,7 +388,12 @@ def _prefer_github_discussion_body(text: str) -> str: ) if not match: return text - return match.group("body") + # GitHub release discussions may identify the version only in the page + # title, while the comment body describes the changes without repeating it. + # Keep that title attached to its body, excluding navigation and replies. + title = re.search(r"]*>(.*?)", text, flags=re.DOTALL | re.IGNORECASE) + body = match.group("body") + return f"{title.group(1)}\n{body}" if title else body def _release_note_links_for_version(source_url: str, text: str, to_version: str) -> tuple[str, ...]: diff --git a/src/agent_runtime_kit/adapters/antigravity.py b/src/agent_runtime_kit/adapters/antigravity.py index ea42c06..2e098d7 100644 --- a/src/agent_runtime_kit/adapters/antigravity.py +++ b/src/agent_runtime_kit/adapters/antigravity.py @@ -45,6 +45,7 @@ optional_str, output_schema_from, package_availability, + package_version, resolve_structured_output, select_model, validate_model_configuration, @@ -98,6 +99,10 @@ def __init__( policy_module: Any | None = None, reuse_process: bool = False, ) -> None: + if api_key and vertex is True and (project or location): + raise ValueError( + "Vertex Express API-key authentication cannot be combined with project/location" + ) self._default_model = default_model self._supported_models = validate_model_configuration(default_model, supported_models) self._api_key = api_key @@ -149,7 +154,7 @@ async def check_readiness(self) -> RuntimeReadiness: message="An Antigravity API-key credential signal is configured.", package=availability.package, version=availability.version, - metadata={"auth_source": "api-key"}, + metadata={"auth_source": self._auth_config().source}, ) if self._vertex is False: return self._missing_credentials_readiness(availability) @@ -319,7 +324,13 @@ def _load_sdk(self) -> _AntigravitySDK: self.kind, "google-antigravity is not installed. Install agent-runtime-kit[antigravity].", ) from exc - return _AntigravitySDK(Agent, LocalAgentConfig, types, policy) # pragma: no cover + return _AntigravitySDK( + Agent, + LocalAgentConfig, + types, + policy, + supports_vertex_express=_supports_vertex_express(package_version("google-antigravity")), + ) def _build_config( self, @@ -329,6 +340,12 @@ def _build_config( auth: _AntigravityAuthConfig, sdk: _AntigravitySDK, ) -> tuple[Any, list[str]]: + if auth.api_key is not None and auth.vertex is True and not sdk.supports_vertex_express: + raise UnsupportedTaskInputError( + self.kind, + "vertex", + "Vertex Express requires a released google-antigravity>=0.1.16 SDK", + ) for server in task.mcp_servers: if server.env: raise UnsupportedTaskInputError( @@ -366,9 +383,11 @@ def _build_config( # LocalAgentConfig no longer accepts (instead of a TypeError) and record # them — except the tool posture (and workspace scoping when requested), # which must fail closed rather than run with the SDK's default access. - required = ["capabilities", "policies"] + required = {"capabilities": "permissions", "policies": "permissions"} + if auth.api_key is not None and auth.vertex is True: + required.update(api_key="api_key", vertex="vertex") if config_kwargs["workspaces"]: - required.append("workspaces") + required["workspaces"] = "permissions" supported, dropped = filter_supported_kwargs( sdk.config_cls, config_kwargs, required=required, kind=self.kind ) @@ -670,9 +689,14 @@ def _vertex_auth_config(self) -> _AntigravityAuthConfig: return _AntigravityAuthConfig(source="none") def _auth_config(self) -> _AntigravityAuthConfig: - # An explicit constructor api_key is the most specific request and wins. + # An explicit key selects credentials without erasing an explicit + # endpoint: vertex=True plus a key is Vertex Express, without ADC. if self._api_key: - return _AntigravityAuthConfig(api_key=self._api_key, source="api-key") + return _AntigravityAuthConfig( + api_key=self._api_key, + vertex=self._vertex, + source="vertex-express-api-key" if self._vertex is True else "api-key", + ) # Explicit vertex=True takes precedence over an ambient env API key, so # AntigravityAgentRuntime(vertex=True, project=...) is not silently # redirected to the Gemini API just because GEMINI_API_KEY is exported. @@ -703,11 +727,30 @@ def _runtime_dir(self, name: str) -> Path: class _AntigravitySDK: - def __init__(self, agent_cls: Any, config_cls: Any, types: Any, policy: Any) -> None: + def __init__( + self, + agent_cls: Any, + config_cls: Any, + types: Any, + policy: Any, + *, + supports_vertex_express: bool = True, + ) -> None: self.agent_cls = agent_cls self.config_cls = config_cls self.types = types self.policy = policy + self.supports_vertex_express = supports_vertex_express + + +def _supports_vertex_express(version: str | None) -> bool: + # 0.1.15 exposes the same kwargs, so field presence cannot establish native + # Express support. Enable it only for a verifiable released SDK; injected + # SDK implementations own their capability contract independently. + released = re.fullmatch( + r"(\d+)\.(\d+)\.(\d+)(?:\.post\d+)?(?:\+[a-zA-Z0-9._-]+)?", version or "" + ) + return released is not None and tuple(int(part) for part in released.groups()) >= (0, 1, 16) @dataclass(frozen=True) diff --git a/src/agent_runtime_kit/compatibility.py b/src/agent_runtime_kit/compatibility.py index b3b097e..861c582 100644 --- a/src/agent_runtime_kit/compatibility.py +++ b/src/agent_runtime_kit/compatibility.py @@ -66,7 +66,7 @@ def __post_init__(self) -> None: package="claude-agent-sdk", module="claude_agent_sdk", version_specifier=">=0.2.87,<0.3", - tested_version="0.2.148", + tested_version="0.2.152", ), RuntimeCompatibility( kind=AgentRuntimeKind.CODEX_AGENT_SDK, @@ -85,7 +85,7 @@ def __post_init__(self) -> None: package="google-antigravity", module="google.antigravity", version_specifier=">=0.1.2,<0.2", - tested_version="0.1.15", + tested_version="0.1.16", ), ) diff --git a/tests/test_antigravity_adapter.py b/tests/test_antigravity_adapter.py index 43515e5..3d5838a 100644 --- a/tests/test_antigravity_adapter.py +++ b/tests/test_antigravity_adapter.py @@ -19,7 +19,10 @@ ) from agent_runtime_kit._errors import UnsupportedTaskInputError from agent_runtime_kit.adapters import AntigravityAgentRuntime -from agent_runtime_kit.adapters.antigravity import _provider_conversation_id +from agent_runtime_kit.adapters.antigravity import ( + _provider_conversation_id, + _supports_vertex_express, +) from agent_runtime_kit.testing import RecordingEventSink @@ -98,12 +101,20 @@ class FakeConfig: def __init__( self, *, + api_key: str | None = None, + vertex: bool | None = None, capabilities: Any = None, policies: Any = None, workspaces: Any = None, **kwargs: Any, ) -> None: - kwargs.update(capabilities=capabilities, policies=policies, workspaces=workspaces) + kwargs.update( + api_key=api_key, + vertex=vertex, + capabilities=capabilities, + policies=policies, + workspaces=workspaces, + ) self.kwargs = kwargs @@ -1233,6 +1244,83 @@ async def test_antigravity_explicit_vertex_beats_ambient_api_key( assert config.kwargs["api_key"] is None +@pytest.mark.asyncio +async def test_antigravity_explicit_key_preserves_vertex_express_endpoint( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("GEMINI_API_KEY", "ambient-should-not-win") + monkeypatch.setenv("GOOGLE_CLOUD_PROJECT", "ambient-standard-project") + monkeypatch.setenv("GOOGLE_CLOUD_LOCATION", "us-central1") + + def unexpected_adc() -> None: + raise AssertionError("Vertex Express must not require ADC") + + monkeypatch.setattr("agent_runtime_kit.adapters.antigravity._probe_google_adc", unexpected_adc) + monkeypatch.setattr( + "agent_runtime_kit.adapters.antigravity._google_adc_project", unexpected_adc + ) + runtime = make_runtime(api_key="explicit-express-key", vertex=True, data_dir=tmp_path) + + readiness = await runtime.check_readiness() + result = await runtime.run(AgentTask(goal="task")) + + assert readiness.metadata["auth_source"] == "vertex-express-api-key" + assert result.output == "done: task" + config = FakeAgent.last_config + assert config is not None + assert config.kwargs["api_key"] == "explicit-express-key" + assert config.kwargs["vertex"] is True + assert config.kwargs["project"] is None + assert config.kwargs["location"] is None + + +@pytest.mark.parametrize( + ("version", "supported"), + ( + (None, False), + ("0.1.15", False), + ("0.1.16rc1", False), + ("0.1.16", True), + ("0.1.16.post1", True), + ("0.1.17+local", True), + ), +) +def test_antigravity_express_requires_a_verifiable_released_sdk( + version: str | None, supported: bool +) -> None: + assert _supports_vertex_express(version) is supported + + +@pytest.mark.parametrize("regional", ({"project": "project"}, {"location": "us-central1"})) +def test_antigravity_rejects_mixed_vertex_express_and_regional_auth( + regional: dict[str, str], +) -> None: + with pytest.raises(ValueError, match="Vertex Express"): + AntigravityAgentRuntime(api_key="express-key", vertex=True, **regional) + + +@pytest.mark.asyncio +async def test_antigravity_express_endpoint_cannot_be_dropped_by_sdk_drift(tmp_path: Path) -> None: + class ConfigWithoutVertex: + def __init__(self, *, api_key: str, capabilities: Any, policies: Any) -> None: + raise AssertionError("missing endpoint selection must fail before construction") + + runtime = AntigravityAgentRuntime( + api_key="express-key", + vertex=True, + data_dir=tmp_path, + agent_cls=FakeAgent, + config_cls=ConfigWithoutVertex, + types_module=FakeTypes, + policy_module=FakePolicy, + ) + + with pytest.raises(UnsupportedTaskInputError) as exc_info: + await runtime.run(AgentTask(goal="task")) + + assert exc_info.value.field == "vertex" + + @pytest.mark.asyncio async def test_antigravity_rejects_reasoning_effort(tmp_path: Path) -> None: # No LocalAgentConfig field maps onto reasoning effort, so the first-class diff --git a/tests/test_sdk_contract.py b/tests/test_sdk_contract.py index 990a83d..d963529 100644 --- a/tests/test_sdk_contract.py +++ b/tests/test_sdk_contract.py @@ -1,16 +1,19 @@ """SDK surface contract tests. -These verify the real vendor SDK surfaces this package depends on. They are pure -introspection: no network, no credentials, no agent construction. Each SDK is gated +These verify the real vendor SDK surfaces and configurations this package depends +on: no network, no credentials, no agent construction. Each SDK is gated with ``importorskip`` so the suite still passes in the core-only lane. """ from __future__ import annotations import dataclasses +import importlib.metadata import inspect +from pathlib import Path import pytest +from packaging.version import Version def _fields(cls: object) -> set[str]: @@ -42,6 +45,37 @@ def test_claude_options_has_every_kwarg_adapter_builds() -> None: assert not missing, f"ClaudeAgentOptions missing: {sorted(missing)}" +def test_claude_adapter_constructs_real_options(tmp_path: Path) -> None: + claude = pytest.importorskip("claude_agent_sdk") + from agent_runtime_kit import AgentTask, McpServerConfig, PermissionMode, PermissionProfile + from agent_runtime_kit.adapters import ClaudeAgentRuntime + + schema = {"type": "object", "properties": {"ok": {"type": "boolean"}}} + task = AgentTask( + goal="Inspect configuration only", + system="Return the requested structured result", + working_directory=tmp_path, + session_id="test-session", + output_schema=schema, + budget_usd=1.0, + permissions=PermissionProfile( + mode=PermissionMode.STRICT, allowed_tools=("Read",), disallowed_tools=("Bash",) + ), + mcp_servers=(McpServerConfig(name="files", command="test-mcp", args=("--read-only",)),), + ) + options, dropped = ClaudeAgentRuntime()._build_options(task, None, claude.ClaudeAgentOptions) + + assert not dropped + assert options.permission_mode == "plan" + assert options.allowed_tools == ["Read"] + assert options.disallowed_tools == ["Bash"] + assert options.cwd == tmp_path + assert options.resume == "test-session" + assert options.max_budget_usd == 1.0 + assert options.output_format == {"type": "json_schema", "schema": schema} + assert options.mcp_servers["files"]["args"] == ["--read-only"] + + def test_claude_message_and_block_types_exist() -> None: claude = pytest.importorskip("claude_agent_sdk") @@ -166,3 +200,51 @@ def test_antigravity_policy_and_config() -> None: } missing = expected - fields assert not missing, f"LocalAgentConfig missing: {sorted(missing)}" + + +@pytest.mark.parametrize("vertex", [False, True]) +def test_antigravity_adapter_constructs_explicit_api_key_endpoint( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path, vertex: bool +) -> None: + pytest.importorskip("google.antigravity") + version = Version(importlib.metadata.version("google-antigravity")) + if not vertex and version < Version("0.1.15"): + pytest.skip("Endpoint model inspection uses Antigravity 0.1.15+") + from google.antigravity import types + + from agent_runtime_kit import AgentTask + from agent_runtime_kit._errors import UnsupportedTaskInputError + from agent_runtime_kit.adapters import AntigravityAgentRuntime + + monkeypatch.setenv("GOOGLE_GENAI_USE_VERTEXAI", "true") + monkeypatch.setenv("GOOGLE_CLOUD_PROJECT", "ambient-project") + monkeypatch.setenv("GOOGLE_CLOUD_LOCATION", "ambient-location") + monkeypatch.setenv("GEMINI_API_KEY", "ambient-key") + runtime = AntigravityAgentRuntime( + vertex=vertex, api_key="test-api-key-no-network", data_dir=tmp_path + ) + def build_config(): + return runtime._build_config( + AgentTask(goal="Inspect configuration only"), + model=None, + auth=runtime._auth_config(), + sdk=runtime._load_sdk(), + ) + + if vertex and version < Version("0.1.16"): + with pytest.raises(UnsupportedTaskInputError, match=r">=0\.1\.16"): + build_config() + return + config, dropped = build_config() + + assert not dropped + assert config.models + for target in config.models: + endpoint = target.endpoint + expected_type = types.VertexEndpoint if vertex else types.GeminiAPIEndpoint + assert isinstance(endpoint, expected_type) + assert endpoint.api_key == "test-api-key-no-network" + endpoint.validate_endpoint() + if vertex: + assert endpoint.project is None + assert endpoint.location is None diff --git a/tests/test_sdk_evolution_agent.py b/tests/test_sdk_evolution_agent.py index 4e3a30b..40ca739 100644 --- a/tests/test_sdk_evolution_agent.py +++ b/tests/test_sdk_evolution_agent.py @@ -569,6 +569,49 @@ def fetcher(url: str) -> str: assert any("Python 3.14" in summary for summary in notes[0].summaries) +@pytest.mark.parametrize( + ("title_version", "expected_status"), + ( + ("0.1.16", "found"), + ("0.1.160", "no-matching-version"), + ("0.1.15", "no-matching-version"), + ("0.1.16-rc.1", "no-matching-version"), + ("0.1.16+local", "no-matching-version"), + ), +) +def test_release_notes_associate_discussion_title_with_its_body( + title_version: str, expected_status: str +) -> None: + def fetcher(url: str) -> str: + if url.endswith("/discussions/categories/announcements"): + return ( + 'v0.1.16 Release Notes' + ) + if url.endswith("/discussions/199"): + # The real release puts the version only in the page title. Page + # navigation and other releases must not supply matching evidence. + return ( + f"Google Antigravity Python SDK — v{title_version} Release Notes" + "" + '' + "

Vertex Express supports API-key authentication.

" + "" + ) + return "General package documentation" + + notes = collect_release_notes( + [{"name": "google-antigravity", "locked_version": "0.1.15"}], + {"google-antigravity": "0.1.16"}, + fetcher=fetcher, + ) + + assert notes[0].status == expected_status + if expected_status == "found": + assert any("Vertex Express" in summary for summary in notes[0].summaries) + assert not any("Unrelated navigation" in summary for summary in notes[0].summaries) + + def test_release_note_links_only_follow_github_https_urls() -> None: # The discussion-index markup can carry user-generated hrefs. A # protocol-relative path would urljoin into an off-site host and the blind diff --git a/uv.lock b/uv.lock index 9b5ed09..ab3301e 100644 --- a/uv.lock +++ b/uv.lock @@ -382,7 +382,7 @@ wheels = [ [[package]] name = "claude-agent-sdk" -version = "0.2.148" +version = "0.2.152" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -391,13 +391,13 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/f4/fb7f81b31e44f69af9f53b93c32e2047f3ca15cd6311cfe72bfdf420f1f5/claude_agent_sdk-0.2.148.tar.gz", hash = "sha256:45c9972fa72dc6006745239c6838cc3cea7a9b42d6927c89f7bdb6996a1983b0", size = 344717, upload-time = "2026-08-28T18:33:51.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/61/de2194d266fcede2a737996e800e7726079730f81ef03d41117e7888be04/claude_agent_sdk-0.2.152.tar.gz", hash = "sha256:0822bbdf700ccdd1acecf8365345a56bde07833afe7f8bd1bee68cba0e2fcf9a", size = 344710, upload-time = "2026-09-02T22:47:58.633Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/f5/f4affb9f882bcf61422ea48491effb6ee2de28bfbdafd0ec90d1b959dad2/claude_agent_sdk-0.2.148-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026e2b54bb3f3909712ff462b8469e2b1fc5471cfde796abc23f32d30b2c8ba1", size = 83882833, upload-time = "2026-08-28T18:33:56.238Z" }, - { url = "https://files.pythonhosted.org/packages/af/cf/e1b11dd9fac877c8b44f022cf895f54f23854b916e09b32360e71209904c/claude_agent_sdk-0.2.148-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:c8cca1f4e4eb4e0c9f44ccfca6f651cf35e8f3914e483632ae8867c5619818a0", size = 88314490, upload-time = "2026-08-28T18:34:01.284Z" }, - { url = "https://files.pythonhosted.org/packages/34/d4/53de4727576fa6b5a261414d420895f4eb5f0369e232d403f1096205709e/claude_agent_sdk-0.2.148-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:3fbbea7cc30099d14f894e12fef68b0ee42fa9982567df94e59499b6aff55959", size = 94014630, upload-time = "2026-08-28T18:34:06.238Z" }, - { url = "https://files.pythonhosted.org/packages/d8/e9/2ea469751ed4df75a911c4bf4dcad1ff5b71ccc9aa1a4aa108583a2a66b5/claude_agent_sdk-0.2.148-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:221efbc0f583c80258189ab93e4f0d6b6a61ad61335de1fdf3e2331ad423dbeb", size = 94210928, upload-time = "2026-08-28T18:34:11.592Z" }, - { url = "https://files.pythonhosted.org/packages/c3/ea/86ccd7a34446c37174e53f03c389b25f168c7f4fd4f00f3e130baae9c902/claude_agent_sdk-0.2.148-py3-none-win_amd64.whl", hash = "sha256:f155252940fd77f1bcc707836924ca3a9691d19bbc464914ae2b7ccf17f0410f", size = 96875177, upload-time = "2026-08-28T18:34:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d3/ee2b674958e105cf26a2eb4d512c46b02fcfdc8fadbb14dde750bbfaf9b1/claude_agent_sdk-0.2.152-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6cc1e949743c7634274ae526bfd1c0a216ff9d71c6b36d94cab80f32b0d32b4c", size = 85989333, upload-time = "2026-09-02T22:48:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/6564671e6d303012b5c0bd6650b9977317c3b5c83914cf5ac62068fa85e6/claude_agent_sdk-0.2.152-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:9c79c4ef91b15e5fae6aba62a82afb983dc360bc159902c541046227e2852c86", size = 90503485, upload-time = "2026-09-02T22:48:06.357Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/1efd9e3332d2909e015c6571a4b5ed937e9126b9c702217813247c1c8052/claude_agent_sdk-0.2.152-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:1e5b619e6fa3c98f06ddc107652f7820c4573fb73a9a24263568d9974078bbc2", size = 95850705, upload-time = "2026-09-02T22:48:10.493Z" }, + { url = "https://files.pythonhosted.org/packages/95/a0/f921e52fdbfd400e46fbc1e707e6004a3ef99ace6cb4555429a3a8d3f371/claude_agent_sdk-0.2.152-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:f885bd935054082a599dcf92bbdaf3c0b853117a79375cb3aae74674a8124326", size = 96039825, upload-time = "2026-09-02T22:48:14.587Z" }, + { url = "https://files.pythonhosted.org/packages/e6/be/dd113754761c6eef6eec1f2b148c825f925d4bfaa3f946656925239c54f9/claude_agent_sdk-0.2.152-py3-none-win_amd64.whl", hash = "sha256:f422c256358997ffa3a740f9fdc8127aa8404a9d57493502dc7d2ac05ecb5486", size = 98656506, upload-time = "2026-09-02T22:48:19.109Z" }, ] [[package]] @@ -604,7 +604,7 @@ wheels = [ [[package]] name = "google-antigravity" -version = "0.1.15" +version = "0.1.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "absl-py" }, @@ -616,11 +616,11 @@ dependencies = [ { name = "websockets" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/95/142eee8db8be5a553f9fd18f762336f585138f5fe8ce85cb5892c7ec8ee0/google_antigravity-0.1.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3e281ab5ee2017e6c57ffd33053ed51300fa9bbc08f81e728d5d53a1243217ac", size = 32916793, upload-time = "2026-08-26T19:08:08.899Z" }, - { url = "https://files.pythonhosted.org/packages/7c/8d/f844f97b5f0cd4b432491032be93f9a90a6a4c14387df03c107d119eee6a/google_antigravity-0.1.15-py3-none-manylinux_2_17_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:e7c3c39df2ab3bbe5a2fdfa7d2ee7c8958bd6bb40a6b97aa43ab18c5d36e717c", size = 35736612, upload-time = "2026-08-26T19:08:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/29/2c/3837d346fb5caa454db32eb785934d37ce7313cc319b90edca9866a279d3/google_antigravity-0.1.15-py3-none-manylinux_2_17_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:ee28bc56c895f21623b8bb7b147f91ae791fc5c2b03b76bcea62a998f8ed068d", size = 39623445, upload-time = "2026-08-26T19:08:21.005Z" }, - { url = "https://files.pythonhosted.org/packages/40/4e/b998ebd8b565a15b7d9193b33f385191ae72f372272fc835cb9875a0813e/google_antigravity-0.1.15-py3-none-win_amd64.whl", hash = "sha256:a51106034129825f87a1eb0d7ef78a2efa9605ba289fa4850f242aa037767b20", size = 38008309, upload-time = "2026-08-26T19:08:25.706Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5d/135860fc10ea92f612889f10ab13b6e1c563573442edb678651150f5447c/google_antigravity-0.1.15-py3-none-win_arm64.whl", hash = "sha256:47dcf6282d4cf2b4f0a36575598adb6e9ee50fd7ca6ed40f7034d371152e8732", size = 34293602, upload-time = "2026-08-26T19:08:30.278Z" }, + { url = "https://files.pythonhosted.org/packages/69/f9/c996b0a8900ed083483aeeda16ff75ef010212e6cb25acbc774448f0c30a/google_antigravity-0.1.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1034bbd6738bc53cccdacae057d64b23e40a228ad44ebc62cc783583708fd178", size = 37127921, upload-time = "2026-09-02T15:34:17.238Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/abeb62d2c2653561baadc1a602778ccab1305be7c1ada41442ebab5e1d96/google_antigravity-0.1.16-py3-none-manylinux_2_17_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:bf1eea7b4d89dcbdb664d69f5e1afc1a6ea68aba1048cca67beabbfd1434f037", size = 38866110, upload-time = "2026-09-02T15:34:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1d/55ccedbc1a78fe0407cad7a2d612dd428f6b34cd97e0be95f67fb34bd32c/google_antigravity-0.1.16-py3-none-manylinux_2_17_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:7c998c28bae47912b1c28fe06b07b05163bf037fcb40f6df06aa5e17b3de7eca", size = 42823031, upload-time = "2026-09-02T15:34:23.689Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d6/6acb462d8dcaae6f50586978321a7f5036f868b36e171a9fe183b026d476/google_antigravity-0.1.16-py3-none-win_amd64.whl", hash = "sha256:45dce51f3b241c1b0248d91c16f7969c5566dd3d38eadde1a462e1af9f00749c", size = 40308562, upload-time = "2026-09-02T15:34:27.133Z" }, + { url = "https://files.pythonhosted.org/packages/05/3c/f26dba7e28c476ad56c3889682b8b4c54028bfc59ff0c43e16ec5a5e5883/google_antigravity-0.1.16-py3-none-win_arm64.whl", hash = "sha256:f37618978fd18cc660f3e1e9f7a68a0dc26168d80b256003de87131fecb45e73", size = 36448840, upload-time = "2026-09-02T15:34:30.09Z" }, ] [[package]]