Skip to content

Commit 49a8bd0

Browse files
authored
Merge pull request #4 from chatbotkit/next
Release python-sdk
2 parents a23035b + 725a9af commit 49a8bd0

11 files changed

Lines changed: 7828 additions & 4921 deletions

File tree

.github/workflows/backmerge.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Backmerge releases from main to next
2+
#
3+
# This workflow ensures that any commits on `main` (releases, version bumps)
4+
# are automatically merged back into `next` to keep branches in sync.
5+
#
6+
# This prevents divergence where main has commits that next doesn't have.
7+
8+
name: Backmerge to next
9+
10+
on:
11+
push:
12+
branches: [main]
13+
14+
permissions:
15+
contents: write
16+
17+
jobs:
18+
backmerge:
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
with:
24+
ref: next
25+
fetch-depth: 0
26+
token: ${{ secrets.GITHUB_TOKEN }}
27+
28+
- name: Merge main into next
29+
run: |
30+
git config user.name "github-actions[bot]"
31+
git config user.email "github-actions[bot]@users.noreply.github.com"
32+
33+
git fetch origin main
34+
35+
if git merge-base --is-ancestor origin/main HEAD; then
36+
echo "next already contains all commits from main. Nothing to merge."
37+
exit 0
38+
fi
39+
40+
git merge origin/main -m "chore: backmerge from main"
41+
git push origin next

.github/workflows/ci.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Continuous Integration for the python-sdk.
2+
#
3+
# Runs the pytest suite across every supported Python version and verifies the
4+
# package builds and passes twine's metadata checks. Mirrors the gating the
5+
# other ChatBotKit SDKs have (go/terraform `ci.yml`, node `publish.yml`).
6+
name: CI
7+
8+
on:
9+
push:
10+
branches:
11+
- main
12+
- next
13+
paths-ignore:
14+
- '**.md'
15+
- 'LICENSE'
16+
pull_request:
17+
branches:
18+
- main
19+
- next
20+
paths-ignore:
21+
- '**.md'
22+
- 'LICENSE'
23+
24+
permissions:
25+
contents: read
26+
27+
jobs:
28+
test:
29+
name: Test (Python ${{ matrix.python-version }})
30+
runs-on: ubuntu-latest
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
python-version: ['3.10', '3.11', '3.12', '3.13']
35+
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v4
39+
40+
- name: Set up Python ${{ matrix.python-version }}
41+
uses: actions/setup-python@v5
42+
with:
43+
python-version: ${{ matrix.python-version }}
44+
cache: pip
45+
46+
- name: Install
47+
run: |
48+
python -m pip install --upgrade pip
49+
pip install -e '.[dev,agent,examples]'
50+
51+
- name: Test
52+
run: pytest
53+
54+
build:
55+
name: Build & metadata check
56+
runs-on: ubuntu-latest
57+
needs: test
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v4
61+
62+
- name: Set up Python
63+
uses: actions/setup-python@v5
64+
with:
65+
python-version: '3.12'
66+
cache: pip
67+
68+
- name: Install build tooling
69+
run: |
70+
python -m pip install --upgrade pip
71+
pip install build twine
72+
73+
- name: Build sdist and wheel
74+
run: python -m build
75+
76+
- name: Check metadata
77+
run: twine check dist/*

.github/workflows/release.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Release workflow for the python-sdk.
2+
#
3+
# Triggered by tag-release.yml (workflow_dispatch at the new tag) or by a
4+
# manually pushed `v*` tag. Builds the sdist/wheel, publishes to PyPI (only if
5+
# a PYPI_API_TOKEN secret is configured), and creates a GitHub Release whose
6+
# body is the matching section from CHANGELOG.md with auto-generated commit
7+
# notes appended.
8+
#
9+
# To enable PyPI publishing, add a `PYPI_API_TOKEN` secret (a PyPI API token)
10+
# to this repository. Without it, the build still runs and the GitHub Release is
11+
# still created; only the upload step is skipped.
12+
name: Release
13+
14+
on:
15+
push:
16+
tags:
17+
- 'v*'
18+
workflow_dispatch:
19+
20+
permissions:
21+
contents: write
22+
23+
jobs:
24+
release:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
with:
30+
fetch-depth: 0
31+
32+
- name: Set up Python
33+
uses: actions/setup-python@v5
34+
with:
35+
python-version: '3.12'
36+
37+
- name: Build sdist and wheel
38+
run: |
39+
python -m pip install --upgrade pip
40+
pip install build twine
41+
python -m build
42+
twine check dist/*
43+
44+
- name: Detect PyPI token
45+
id: token
46+
env:
47+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
48+
run: |
49+
if [ -n "$PYPI_API_TOKEN" ]; then
50+
echo "present=true" >> "$GITHUB_OUTPUT"
51+
else
52+
echo "present=false" >> "$GITHUB_OUTPUT"
53+
echo "PYPI_API_TOKEN not set - skipping PyPI upload." >&2
54+
fi
55+
56+
- name: Publish to PyPI
57+
if: steps.token.outputs.present == 'true'
58+
env:
59+
TWINE_USERNAME: __token__
60+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
61+
run: twine upload --non-interactive dist/*
62+
63+
- name: Extract changelog section for this version
64+
id: changelog
65+
run: |
66+
VERSION="${GITHUB_REF_NAME#v}"
67+
if [ -f CHANGELOG.md ]; then
68+
awk -v ver="$VERSION" '
69+
$0 ~ ("^## \\[" ver "\\]") { capture = 1; next }
70+
capture && /^## \[/ { exit }
71+
capture { print }
72+
' CHANGELOG.md > release-notes.md
73+
fi
74+
if [ ! -s release-notes.md ]; then
75+
echo "Release v${VERSION}. See the full changelog in CHANGELOG.md." > release-notes.md
76+
fi
77+
78+
- name: Create GitHub Release
79+
uses: softprops/action-gh-release@v2
80+
with:
81+
body_path: release-notes.md
82+
generate_release_notes: true

.github/workflows/tag-release.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Auto-tag when merging to main
2+
#
3+
# Reads the version from pyproject.toml and, if the matching tag does not
4+
# already exist, creates and pushes it, then triggers the release workflow
5+
# (build + publish to PyPI + GitHub Release).
6+
name: Tag Release
7+
8+
on:
9+
push:
10+
branches:
11+
- main
12+
13+
permissions:
14+
contents: write
15+
actions: write
16+
17+
jobs:
18+
tag:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
26+
- name: Get version from pyproject.toml
27+
id: version
28+
run: |
29+
VERSION=$(grep -m1 '^version = ' pyproject.toml | sed -E 's/^version = "(.*)"/\1/')
30+
if [ -z "$VERSION" ]; then
31+
echo "Could not read version from pyproject.toml" >&2
32+
exit 1
33+
fi
34+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
35+
echo "Version: $VERSION"
36+
37+
- name: Check if tag exists
38+
id: check_tag
39+
run: |
40+
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
41+
echo "exists=true" >> "$GITHUB_OUTPUT"
42+
echo "Tag v${{ steps.version.outputs.version }} already exists"
43+
else
44+
echo "exists=false" >> "$GITHUB_OUTPUT"
45+
echo "Tag v${{ steps.version.outputs.version }} does not exist"
46+
fi
47+
48+
- name: Create and push tag
49+
if: steps.check_tag.outputs.exists == 'false'
50+
run: |
51+
git config user.name "github-actions[bot]"
52+
git config user.email "github-actions[bot]@users.noreply.github.com"
53+
git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}"
54+
git push origin "v${{ steps.version.outputs.version }}"
55+
56+
- name: Trigger release workflow
57+
if: steps.check_tag.outputs.exists == 'false'
58+
uses: actions/github-script@v7
59+
with:
60+
script: |
61+
await github.rest.actions.createWorkflowDispatch({
62+
owner: context.repo.owner,
63+
repo: context.repo.repo,
64+
workflow_id: 'release.yml',
65+
ref: 'v${{ steps.version.outputs.version }}'
66+
})

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Changelog
2+
3+
All notable changes to the ChatBotKit Python SDK are documented in this file.
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [0.2.0] - 2026-06-22
8+
9+
### Added
10+
11+
- `skill_server` integration client (`client.integration.skill_server`) with
12+
`list`, `fetch`, `create`, `update`, and `delete`. The Skill Server
13+
integration exposes a skillset's abilities as a text-first HTTP API.
14+
- `site` client under `space` (`client.space.site`) with `list`, `fetch`,
15+
`create`, `update`, and `delete`, keyed by the parent space ID. A space site
16+
binds a `<label>.chatbotkit.space` subdomain to static content served from a
17+
space's storage.
18+
19+
### Changed
20+
21+
- Re-generated types from the latest API spec, including the `alias` field now
22+
present across integration create/update requests.
23+
24+
## [0.1.0] - 2026-06-11
25+
26+
### Added
27+
28+
- Initial release of the async Python SDK for ChatBotKit.

chatbotkit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
"Response",
1010
]
1111

12-
__version__ = "0.1.0"
12+
__version__ = "0.2.0"

chatbotkit/integration.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(self, client: Client) -> None:
2525
self.twilio = TwilioClient(client)
2626
self.email = EmailClient(client)
2727
self.mcp_server = McpServerClient(client)
28+
self.skill_server = SkillServerClient(client)
2829
self.microsoft_teams = MicrosoftTeamsClient(client)
2930
self.google_chat = GoogleChatClient(client)
3031
self.trigger = TriggerClient(client)
@@ -995,6 +996,60 @@ def delete(
995996
)
996997

997998

999+
class SkillServerClient:
1000+
def __init__(self, client: Client) -> None:
1001+
self._client = client
1002+
1003+
def list(
1004+
self,
1005+
request: types.SkillServerIntegrationListParams | Request | None = None,
1006+
) -> Response[types.SkillServerIntegrationListResponse, types.SkillServerIntegrationListStreamItem]:
1007+
return self._client.client_fetch(
1008+
"/api/v1/integration/skillserver/list",
1009+
query=request,
1010+
parse=types.SkillServerIntegrationListResponse.from_dict,
1011+
stream_parse=types.SkillServerIntegrationListStreamItem.from_dict,
1012+
)
1013+
1014+
def fetch(self, integration_id: str) -> Response[types.SkillServerIntegrationFetchResponse, Any]:
1015+
return self._client.client_fetch(
1016+
f"/api/v1/integration/skillserver/{integration_id}/fetch",
1017+
parse=types.SkillServerIntegrationFetchResponse.from_dict,
1018+
)
1019+
1020+
def create(
1021+
self,
1022+
request: types.SkillServerIntegrationCreateRequest | Request,
1023+
) -> Response[types.SkillServerIntegrationCreateResponse, Any]:
1024+
return self._client.client_fetch(
1025+
"/api/v1/integration/skillserver/create",
1026+
record=request,
1027+
parse=types.SkillServerIntegrationCreateResponse.from_dict,
1028+
)
1029+
1030+
def update(
1031+
self,
1032+
integration_id: str,
1033+
request: types.SkillServerIntegrationUpdateRequest | Request,
1034+
) -> Response[types.SkillServerIntegrationUpdateResponse, Any]:
1035+
return self._client.client_fetch(
1036+
f"/api/v1/integration/skillserver/{integration_id}/update",
1037+
record=request,
1038+
parse=types.SkillServerIntegrationUpdateResponse.from_dict,
1039+
)
1040+
1041+
def delete(
1042+
self,
1043+
integration_id: str,
1044+
request: Request | None = None,
1045+
) -> Response[types.SkillServerIntegrationDeleteResponse, Any]:
1046+
return self._client.client_fetch(
1047+
f"/api/v1/integration/skillserver/{integration_id}/delete",
1048+
record=request or {},
1049+
parse=types.SkillServerIntegrationDeleteResponse.from_dict,
1050+
)
1051+
1052+
9981053
class MicrosoftTeamsClient:
9991054
def __init__(self, client: Client) -> None:
10001055
self._client = client

0 commit comments

Comments
 (0)