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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
python-version: ['3.11', '3.12']
uses: ./.github/workflows/reusable-python.yml
uses: ./.github/workflows/reusable-python.yaml
with:
python-version: ${{ matrix.python-version }}
install-extras: test,typing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ on:
- '.pre-commit-config.yaml'
- 'ruff.toml'
- '.ruff.toml'
- '.github/workflows/py-code-style.yml'
- '.github/workflows/reusable-python.yml'
- '.github/workflows/py-code-style.yaml'
- '.github/workflows/reusable-python.yaml'
pull_request:
branches:
- main
Expand All @@ -27,8 +27,8 @@ on:
- '.pre-commit-config.yaml'
- 'ruff.toml'
- '.ruff.toml'
- '.github/workflows/py-code-style.yml'
- '.github/workflows/reusable-python.yml'
- '.github/workflows/py-code-style.yaml'
- '.github/workflows/reusable-python.yaml'
workflow_dispatch:

concurrency:
Expand All @@ -37,7 +37,7 @@ concurrency:

jobs:
check-code-style:
uses: ./.github/workflows/reusable-python.yml
uses: ./.github/workflows/reusable-python.yaml
with:
python-version: '3.11'
extra-packages: pre-commit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ on:
- 'setup.py'
- 'requirements*.txt'
- '**/*.py'
- '.github/workflows/py-tests.yml'
- '.github/workflows/reusable-python.yml'
- '.github/workflows/py-tests.yaml'
- '.github/workflows/reusable-python.yaml'
pull_request:
branches:
- main
Expand All @@ -23,8 +23,8 @@ on:
- 'setup.py'
- 'requirements*.txt'
- '**/*.py'
- '.github/workflows/py-tests.yml'
- '.github/workflows/reusable-python.yml'
- '.github/workflows/py-tests.yaml'
- '.github/workflows/reusable-python.yaml'

workflow_dispatch:

Expand All @@ -34,7 +34,7 @@ jobs:
max-parallel: 1
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']
uses: ./.github/workflows/reusable-python.yml
uses: ./.github/workflows/reusable-python.yaml
with:
python-version: ${{ matrix.python-version }}
install-extras: test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ on:
- 'setup.py'
- 'mypy.ini'
- '.mypy.ini'
- '.github/workflows/py-typing.yml'
- '.github/workflows/reusable-python.yml'
- '.github/workflows/py-typing.yaml'
- '.github/workflows/reusable-python.yaml'
pull_request:
branches:
- main
Expand All @@ -25,8 +25,8 @@ on:
- 'setup.py'
- 'mypy.ini'
- '.mypy.ini'
- '.github/workflows/py-typing.yml'
- '.github/workflows/reusable-python.yml'
- '.github/workflows/py-typing.yaml'
- '.github/workflows/reusable-python.yaml'
workflow_dispatch:

concurrency:
Expand All @@ -35,7 +35,7 @@ concurrency:

jobs:
type-check:
uses: ./.github/workflows/reusable-python.yml
uses: ./.github/workflows/reusable-python.yaml
with:
python-version: '3.12'
install-extras: typing
Expand Down
105 changes: 105 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright (c) 2023-2026 Datalayer, Inc.
# Distributed under the terms of the Modified BSD License.

# Publishes the release a tag names to PyPI, with no stored token: PyPI
# trusts this workflow file (OIDC trusted publishing, through the `pypi`
# environment). The tag must name the version in agent_codemode/__version__.py; a
# version already published is skipped. See RELEASE.md.

name: Release

on:
push:
tags:
- 'v*'

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
outputs:
python: ${{ steps.plan.outputs.python }}
steps:
- uses: actions/checkout@v4

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

- name: Check the tag names the version the package carries
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME#v}"
version="$(python -c "import re;print(re.search(r'__version__\s*=\s*[\"\']([^\"\']+)', open('agent_codemode/__version__.py').read())[1])")"
echo "tag=$tag version=$version"
test "$tag" = "$version"

- name: Find whether it is published yet
id: plan
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
status="$(curl -sL -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/agent-codemode/$version/json")"
if [ "$status" = "200" ]; then
echo "PyPI: agent-codemode $version is already published, skipping"
echo "python=" >> "$GITHUB_OUTPUT"
else
echo "PyPI: agent-codemode $version will be published"
echo "python=yes" >> "$GITHUB_OUTPUT"
fi

- name: Build
if: steps.plan.outputs.python != ''
run: |
set -euo pipefail
python -m pip install --upgrade pip build
python -m build --sdist --wheel --outdir dist .
ls -la dist

- uses: actions/upload-artifact@v4
if: steps.plan.outputs.python != ''
with:
name: python
path: dist/*

pypi:
needs: build
if: needs.build.outputs.python != ''
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: python
path: dist
- uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true

release:
name: GitHub release
needs: [build, pypi]
if: always() && needs.build.result == 'success' && needs.pypi.result != 'failure'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create the release with generated notes
run: |
version="${GITHUB_REF_NAME#v}"
gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 && exit 0
gh release create "$GITHUB_REF_NAME" --title "Release $version" --generate-notes \
--notes "- [PyPI agent-codemode](https://pypi.org/project/agent-codemode/$version/)"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ agent = Agent(
Expose the programmatic tools as an MCP server for any MCP-compatible client:

```python
from agent_codemode import codemode_server, configure_server
from agent_codemode import configure_server, run_server
from agent_codemode import ToolRegistry, MCPServerConfig, CodeModeConfig

# Create and configure registry with MCP servers to compose
Expand All @@ -331,7 +331,7 @@ config = CodeModeConfig(
)

configure_server(config=config, registry=registry)
codemode_server.run()
run_server() # stdio; run_server(transport="streamable-http", host=..., port=...) for HTTP
```

Or start with command line:
Expand Down Expand Up @@ -469,14 +469,14 @@ When running in a sandbox, state can persist between `execute_code` calls within

## CI Workflows

This repository uses a reusable GitHub Actions workflow at `.github/workflows/reusable-python.yml`.
This repository uses a reusable GitHub Actions workflow at `.github/workflows/reusable-python.yaml`.

The following workflows call it:

- `.github/workflows/build.yml`
- `.github/workflows/py-tests.yml`
- `.github/workflows/py-code-style.yml`
- `.github/workflows/py-typing.yml`
- `.github/workflows/build.yaml`
- `.github/workflows/py-tests.yaml`
- `.github/workflows/py-code-style.yaml`
- `.github/workflows/py-typing.yaml`

Reusable workflow inputs:

Expand Down
35 changes: 35 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!--
~ Copyright (c) 2023-2026 Datalayer, Inc.
~
~ BSD 3-Clause License
-->

# Making a release

A tag releases `agent-codemode` to PyPI, with no stored token: PyPI trusts
`.github/workflows/release.yaml` through OIDC (trusted publishing, GitHub
environment `pypi`). The version lives in `agent_codemode/__version__.py`, which
hatch reads too.

## Steps

1. Bump `__version__` on a branch and open a pull request.

1. Merge, then tag the merge commit and push the tag:

```bash
git checkout main && git pull
git tag vX.Y.Z
git push origin vX.Y.Z
```

1. The `Release` workflow checks that the tag names the version, builds the
wheel and the sdist, publishes them unless PyPI already has that version,
and creates a GitHub release with generated notes.

## Trusted publishing

PyPI project `agent-codemode`: owner `datalayer`, repository `agent-codemode`, workflow
`release.yaml`, environment `pypi`. The registry matches the repository,
the workflow filename and the environment exactly; renaming any of them
means re-registering.
2 changes: 2 additions & 0 deletions agent_codemode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from .proxy.meta_tools import MetaToolProvider
from .server import configure as configure_server
from .server import mcp as codemode_server
from .server import run as run_server
from .toolset import PYDANTIC_AI_AVAILABLE, CodemodeToolset
from .types import (
CodeModeConfig,
Expand Down Expand Up @@ -90,6 +91,7 @@
"configure_server",
"parallel",
"retry",
"run_server",
"run_with_timeout",
"setup_skills_directory",
# Helpers (from agent_skills)
Expand Down
2 changes: 1 addition & 1 deletion agent_codemode/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

"""Agent Codemode."""

__version__ = "1.0.1"
__version__ = "1.1.1"
5 changes: 4 additions & 1 deletion agent_codemode/composition/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,10 @@ async def call_tool(tool_name: str, arguments: dict[str, Any]) -> Any:

is_error = False
if isinstance(result, dict):
is_error = result.get("isError", False)
is_error = result.get("isError", result.get("is_error", False))
elif hasattr(result, "is_error"):
# A ``CallToolResult`` of mcp 2, whose fields are snake_case.
is_error = result.is_error
elif hasattr(result, "isError"):
is_error = result.isError

Expand Down
5 changes: 4 additions & 1 deletion agent_codemode/discovery/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ async def call_tool(tool_name: str, arguments: dict[str, Any]) -> Any:
# Check for error response
is_error = False
if isinstance(result, dict):
is_error = result.get("isError", False)
is_error = result.get("isError", result.get("is_error", False))
elif hasattr(result, "is_error"):
# A ``CallToolResult`` of mcp 2, whose fields are snake_case.
is_error = result.is_error
elif hasattr(result, "isError"):
is_error = result.isError

Expand Down
9 changes: 6 additions & 3 deletions agent_codemode/proxy/mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,14 @@ async def _get_http_session(self):
return self._http_session

from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from mcp.client.streamable_http import streamable_http_client

http_ctx = streamablehttp_client(self.url)
# mcp 2: the transport yields the two streams only; the session id
# callback of mcp 1 is gone, and headers or timeouts would be set on
# an ``httpx2.AsyncClient`` passed as ``http_client``.
http_ctx = streamable_http_client(self.url)
self._http_ctx = http_ctx
read_stream, write_stream, _get_session_id = await http_ctx.__aenter__()
read_stream, write_stream = await http_ctx.__aenter__()
http_session = ClientSession(read_stream, write_stream)
self._http_session = http_session
await http_session.__aenter__()
Expand Down
Loading
Loading