Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/publish-python-bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Publish Python Bootstrap

# Builds and publishes the pure-Python `a3s-code` bootstrap shim to PyPI.
# The shim is tiny (no native code) so it sails under PyPI's per-project
# size cap. On first `import a3s_code` it downloads the matching native
# wheel for the user's platform from this repo's GitHub Releases.

on:
workflow_call:
workflow_dispatch:

jobs:
publish:
name: Publish Python Bootstrap to PyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install build dependencies
run: python -m pip install --upgrade pip build twine

- name: Run bootstrap unit tests
working-directory: sdk/python-bootstrap
run: python -m unittest tests.test_bootstrap -v

- name: Build wheel + sdist
run: python -m build --wheel --sdist sdk/python-bootstrap

- name: Twine check
run: python -m twine check sdk/python-bootstrap/dist/*

- name: Publish to PyPI
if: startsWith(github.ref, 'refs/tags/')
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_NATIVE_TOKEN }}
run: python -m twine upload --skip-existing sdk/python-bootstrap/dist/*
18 changes: 15 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,20 +119,32 @@ jobs:
secrets: inherit

# ───────────────────────────────────────────────
# Build and publish Python native SDK (reusable)
# Build native Python wheels and attach to GitHub Release (reusable).
# No longer pushes to PyPI — the per-project size cap made that
# impractical. GH Release is the canonical wheel host.
# ───────────────────────────────────────────────
publish-python:
name: Python SDK
name: Python SDK (native wheels → GH Release)
needs: [ci, ci-windows]
uses: ./.github/workflows/publish-python.yml
secrets: inherit

# ───────────────────────────────────────────────
# Publish the pure-Python bootstrap to PyPI so `pip install a3s-code`
# still works. Needs the native wheels to be on GH Release first.
# ───────────────────────────────────────────────
publish-python-bootstrap:
name: Python SDK (bootstrap → PyPI)
needs: [publish-python]
uses: ./.github/workflows/publish-python-bootstrap.yml
secrets: inherit

# ───────────────────────────────────────────────
# Create GitHub Release
# ───────────────────────────────────────────────
github-release:
name: GitHub Release
needs: [publish-crate, publish-node, publish-python]
needs: [publish-crate, publish-node, publish-python, publish-python-bootstrap]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.2.1] - 2026-05-24

### Added

- Python SDK: small pure-Python **bootstrap** shim published to PyPI as
`a3s-code`. On first `import a3s_code` it downloads the matching
native wheel for the current interpreter/platform from this repo's
GitHub Releases, verifies the wheel's sha256 against the release
manifest, extracts the compiled `_native` extension into
`~/.cache/a3s-code/<version>/`, and registers it as
`sys.modules["a3s_code._native"]`. Subsequent imports use the cache.
Source under `sdk/python-bootstrap/`.
- Environment knobs: `A3S_CODE_CACHE_DIR`, `A3S_CODE_RELEASES_BASE_URL`,
`A3S_CODE_SKIP_HASH_CHECK`.
- 15 unit tests + 1 live download test gated on
`A3S_CODE_BOOTSTRAP_LIVE=1`.
- New workflow `publish-python-bootstrap.yml`, wired after
`publish-python` in `release.yml`.
- `scripts/check_release_versions.sh` now also validates the bootstrap
package version and the runtime `__version__` literal.
- `release.sh` now bumps the bootstrap version in lockstep with the
core release.

### Fixed

- `pip install a3s-code` works again from v3.2.1, restored after v3.2.0
could only push a single wheel to PyPI under the quota cap.

## [3.2.0] - 2026-05-24

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,22 @@ Full migration notes are in [CHANGELOG.md](./CHANGELOG.md).
## Install

```bash
# Python
pip install a3s-code

# Node.js
npm install @a3s-lab/code

# Python (from v3.2.0; pick the wheel for your interpreter/platform)
pip install \
https://github.com/AI45Lab/Code/releases/download/v3.2.0/a3s_code-3.2.0-cp312-cp312-manylinux_2_28_x86_64.whl
```

Rust users can depend on `a3s-code-core`.

Python wheels are hosted on [GitHub Releases](https://github.com/AI45Lab/Code/releases)
from v3.2.0 onwards — the project grew past PyPI's per-project storage
quota for binary distributions. Each release ships a
`python-native-manifest.json` with sha256 hashes for every wheel.
Versions up to 3.1.0 remain installable via `pip install a3s-code==3.1.0`.
From v3.2.1 onwards the PyPI `a3s-code` package is a small pure-Python
bootstrap. On first `import a3s_code` it downloads the matching native
wheel from [GitHub Releases](https://github.com/AI45Lab/Code/releases),
verifies the wheel's sha256 against the release manifest, and caches the
compiled extension under `~/.cache/a3s-code/<version>/`. Subsequent
imports use the cache. The split exists because the full native-wheel
matrix grew past PyPI's per-project storage cap.

---

Expand Down
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "a3s-code-core"
version = "3.2.0"
version = "3.2.1"
edition = "2021"
authors = ["A3S Lab Team"]
license = "MIT"
Expand Down
11 changes: 11 additions & 0 deletions release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ echo " Updating sdk/python/pyproject.toml..."
sed -i.bak "s/^version = \".*\"/version = \"${VERSION}\"/" sdk/python/pyproject.toml
rm -f sdk/python/pyproject.toml.bak

# Update Python bootstrap shim (pyproject.toml + runtime __version__).
# Must stay in lockstep with core so the bootstrap fetches the matching
# native wheel from GH Releases on first import.
echo " Updating sdk/python-bootstrap/pyproject.toml..."
sed -i.bak "s/^version = \".*\"/version = \"${VERSION}\"/" sdk/python-bootstrap/pyproject.toml
rm -f sdk/python-bootstrap/pyproject.toml.bak

echo " Updating sdk/python-bootstrap/src/a3s_code/_bootstrap.py..."
sed -i.bak "s/^__version__ = \".*\"/__version__ = \"${VERSION}\"/" sdk/python-bootstrap/src/a3s_code/_bootstrap.py
rm -f sdk/python-bootstrap/src/a3s_code/_bootstrap.py.bak

echo " Updating Node package lockfiles..."
update_node_lockfile sdk/node/package-lock.json
update_node_lockfile sdk/node/examples/package-lock.json
Expand Down
10 changes: 10 additions & 0 deletions scripts/check_release_versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ def check_node_lockfile(path):
check_equal(f"{path} package {key or '<root>'} optionalDependency {name}", value)


def check_bootstrap_runtime_version(path):
match = re.search(r'^__version__\s*=\s*"([^"]+)"', read(path), re.MULTILINE)
if not match:
fail(f"{path}: missing __version__ literal")
return
check_equal(f"{path} __version__", match.group(1))


if not expected:
expected = first_manifest_version("core/Cargo.toml") or ""

Expand All @@ -110,6 +118,8 @@ check_core_dependency("sdk/node/Cargo.toml")
check_core_dependency("sdk/python/Cargo.toml")
check_package_json("sdk/node/package.json")
check_pyproject("sdk/python/pyproject.toml")
check_pyproject("sdk/python-bootstrap/pyproject.toml")
check_bootstrap_runtime_version("sdk/python-bootstrap/src/a3s_code/_bootstrap.py")
check_cargo_lock("Cargo.lock")
check_node_lockfile("sdk/node/package-lock.json")
check_node_lockfile("sdk/node/examples/package-lock.json")
Expand Down
4 changes: 2 additions & 2 deletions sdk/node/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "a3s-code-node"
version = "3.2.0"
version = "3.2.1"
edition = "2021"
authors = ["A3S Lab Team"]
license = "MIT"
Expand All @@ -11,7 +11,7 @@ description = "A3S Code Node.js bindings - Native addon via napi-rs"
crate-type = ["cdylib"]

[dependencies]
a3s-code-core = { version = "3.2.0", path = "../../core", features = ["ahp", "s3"] }
a3s-code-core = { version = "3.2.1", path = "../../core", features = ["ahp", "s3"] }
napi = { version = "2", features = ["async", "napi6", "serde-json"] }
napi-derive = "2"
tokio = { version = "1.35", features = ["full"] }
Expand Down
14 changes: 7 additions & 7 deletions sdk/node/examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions sdk/node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions sdk/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@a3s-lab/code",
"version": "3.2.0",
"version": "3.2.1",
"description": "A3S Code - Native Node.js bindings for the coding-agent runtime",
"main": "index.js",
"types": "index.d.ts",
Expand Down Expand Up @@ -43,11 +43,11 @@
"test:helpers": "node test-helpers.mjs"
},
"optionalDependencies": {
"@a3s-lab/code-darwin-arm64": "3.2.0",
"@a3s-lab/code-linux-x64-gnu": "3.2.0",
"@a3s-lab/code-linux-x64-musl": "3.2.0",
"@a3s-lab/code-linux-arm64-gnu": "3.2.0",
"@a3s-lab/code-linux-arm64-musl": "3.2.0",
"@a3s-lab/code-win32-x64-msvc": "3.2.0"
"@a3s-lab/code-darwin-arm64": "3.2.1",
"@a3s-lab/code-linux-x64-gnu": "3.2.1",
"@a3s-lab/code-linux-x64-musl": "3.2.1",
"@a3s-lab/code-linux-arm64-gnu": "3.2.1",
"@a3s-lab/code-linux-arm64-musl": "3.2.1",
"@a3s-lab/code-win32-x64-msvc": "3.2.1"
}
}
6 changes: 6 additions & 0 deletions sdk/python-bootstrap/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build/
dist/
*.egg-info/
__pycache__/
*.pyc
*.pyo
45 changes: 45 additions & 0 deletions sdk/python-bootstrap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# a3s-code (Python bootstrap)

`pip install a3s-code` ships this small pure-Python package. On first
`import a3s_code` it downloads the native extension matching your
interpreter and platform from the project's
[GitHub Releases](https://github.com/AI45Lab/Code/releases), verifies
the wheel's sha256 against the release manifest, extracts the compiled
extension into a per-user cache, and exposes the normal `a3s_code` API.

Subsequent imports use the cached extension. Cache lives under
`~/.cache/a3s-code/<version>/` (or `$XDG_CACHE_HOME/a3s-code/<version>/`).

## Why

PyPI imposes a default 10 GB per-project storage cap. A Rust SDK with
~17 MB native wheels per Python × platform tripped that limit. GitHub
Releases is the canonical wheel host; this bootstrap keeps
`pip install a3s-code` working without dragging the native wheels back
through PyPI.

## Supported platforms

- macOS arm64 (Apple Silicon)
- Linux x86_64 (glibc 2.28+)
- Windows x86_64

CPython 3.10, 3.11, 3.12, 3.13.

## Environment overrides

| Variable | Effect |
|---|---|
| `A3S_CODE_CACHE_DIR` | Cache root (defaults to `$XDG_CACHE_HOME/a3s-code` or `~/.cache/a3s-code`) |
| `A3S_CODE_RELEASES_BASE_URL` | Override the release base URL — useful for air-gapped mirrors |
| `A3S_CODE_SKIP_HASH_CHECK` | `1` skips sha256 verification (do not use in production) |

## Manual install

If you do not want the bootstrap to phone home, install the native
wheel directly:

```bash
pip install \
https://github.com/AI45Lab/Code/releases/download/v3.2.1/a3s_code-3.2.1-cp312-cp312-manylinux_2_28_x86_64.whl
```
18 changes: 18 additions & 0 deletions sdk/python-bootstrap/build/lib/a3s_code/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""A3S Code Python SDK.

This is the pure-Python bootstrap distributed on PyPI. The actual native
extension is fetched from GitHub Releases on first import — see
`a3s_code._bootstrap` for the loader logic and the environment variables
that customize cache location / source URL.
"""

from . import _bootstrap as _bootstrap

_bootstrap.ensure_native_loaded()

# Re-import after the cache dir is on sys.path. The bootstrap extracted
# `_native.<abi>.so` into a per-version cache; Python's import machinery
# picks it up from there.
from ._native import * # noqa: E402,F401,F403

__version__ = _bootstrap.__version__
Loading
Loading