feat: independent versioning for integrations (#565)
* feat: independent versioning for integrations - Add per-integration changelog pages at /changelog/integrations/<name> - Move main changelog to changelog/index.md (URL unchanged) - Add --integration flag to generate-changelog for LLM-based per-integration changelog generation - Add scripts/release-integration.sh <name> <version> for cutting integration releases - Add .github/workflows/release-integration.yml to publish on integrations/** tags - Remove integrations from main release.sh and release.yml cycle * fix: add agno and hermes integration docs to version-0.4 for production build * chore: apply ruff formatting to generate_changelog.py
This commit is contained in:
parent
c10c9c89e9
commit
31f1c53c8f
15 changed files with 976 additions and 297 deletions
99
.github/workflows/release-integration.yml
vendored
Normal file
99
.github/workflows/release-integration.yml
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
name: Release Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'integrations/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write # for PyPI trusted publishing
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Extract integration info
|
||||||
|
id: info
|
||||||
|
run: |
|
||||||
|
# refs/tags/integrations/litellm/v0.1.0 → integration=litellm, version=0.1.0
|
||||||
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
INTEGRATION=$(echo "$TAG" | cut -d'/' -f2)
|
||||||
|
VERSION=$(echo "$TAG" | cut -d'/' -f3 | sed 's/^v//')
|
||||||
|
echo "integration=$INTEGRATION" >> $GITHUB_OUTPUT
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
echo "Integration: $INTEGRATION, Version: $VERSION"
|
||||||
|
|
||||||
|
- name: Detect integration type
|
||||||
|
id: type
|
||||||
|
run: |
|
||||||
|
if [ -f "hindsight-integrations/${{ steps.info.outputs.integration }}/pyproject.toml" ]; then
|
||||||
|
echo "type=python" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "type=typescript" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Python integrations (litellm, pydantic-ai, crewai) ──────────────────
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
if: steps.type.outputs.type == 'python'
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
if: steps.type.outputs.type == 'python'
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version-file: ".python-version"
|
||||||
|
|
||||||
|
- name: Build Python package
|
||||||
|
if: steps.type.outputs.type == 'python'
|
||||||
|
working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }}
|
||||||
|
run: uv build --out-dir dist
|
||||||
|
|
||||||
|
- name: Publish Python package to PyPI
|
||||||
|
if: steps.type.outputs.type == 'python'
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
packages-dir: ./hindsight-integrations/${{ steps.info.outputs.integration }}/dist
|
||||||
|
skip-existing: true
|
||||||
|
|
||||||
|
# ── TypeScript integrations (ai-sdk, chat, openclaw) ────────────────────
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
if: steps.type.outputs.type == 'typescript'
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
if: steps.type.outputs.type == 'typescript'
|
||||||
|
working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }}
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build TypeScript package
|
||||||
|
if: steps.type.outputs.type == 'typescript'
|
||||||
|
working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }}
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Publish TypeScript package to npm
|
||||||
|
if: steps.type.outputs.type == 'typescript'
|
||||||
|
working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }}
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
OUTPUT=$(npm publish --access public 2>&1)
|
||||||
|
EXIT_CODE=$?
|
||||||
|
echo "$OUTPUT"
|
||||||
|
if [ $EXIT_CODE -ne 0 ]; then
|
||||||
|
if echo "$OUTPUT" | grep -q "cannot publish over"; then
|
||||||
|
echo "Package version already published, skipping..."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit $EXIT_CODE
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
232
.github/workflows/release.yml
vendored
232
.github/workflows/release.yml
vendored
|
|
@ -46,30 +46,10 @@ jobs:
|
||||||
working-directory: ./hindsight-all-slim
|
working-directory: ./hindsight-all-slim
|
||||||
run: uv build --out-dir dist
|
run: uv build --out-dir dist
|
||||||
|
|
||||||
- name: Build hindsight-litellm
|
|
||||||
working-directory: ./hindsight-integrations/litellm
|
|
||||||
run: uv build --out-dir dist
|
|
||||||
|
|
||||||
- name: Build hindsight-embed
|
- name: Build hindsight-embed
|
||||||
working-directory: ./hindsight-embed
|
working-directory: ./hindsight-embed
|
||||||
run: uv build --out-dir dist
|
run: uv build --out-dir dist
|
||||||
|
|
||||||
- name: Build hindsight-crewai
|
|
||||||
working-directory: ./hindsight-integrations/crewai
|
|
||||||
run: uv build --out-dir dist
|
|
||||||
|
|
||||||
- name: Build hindsight-pydantic-ai
|
|
||||||
working-directory: ./hindsight-integrations/pydantic-ai
|
|
||||||
run: uv build --out-dir dist
|
|
||||||
|
|
||||||
- name: Build hindsight-hermes
|
|
||||||
working-directory: ./hindsight-integrations/hermes
|
|
||||||
run: uv build --out-dir dist
|
|
||||||
|
|
||||||
- name: Build hindsight-agno
|
|
||||||
working-directory: ./hindsight-integrations/agno
|
|
||||||
run: uv build --out-dir dist
|
|
||||||
|
|
||||||
# Publish in order (client and api-slim first, then api/all wrappers which depend on them)
|
# Publish in order (client and api-slim first, then api/all wrappers which depend on them)
|
||||||
- name: Publish hindsight-client to PyPI
|
- name: Publish hindsight-client to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
@ -101,42 +81,12 @@ jobs:
|
||||||
packages-dir: ./hindsight-all-slim/dist
|
packages-dir: ./hindsight-all-slim/dist
|
||||||
skip-existing: true
|
skip-existing: true
|
||||||
|
|
||||||
- name: Publish hindsight-litellm to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages-dir: ./hindsight-integrations/litellm/dist
|
|
||||||
skip-existing: true
|
|
||||||
|
|
||||||
- name: Publish hindsight-embed to PyPI
|
- name: Publish hindsight-embed to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
packages-dir: ./hindsight-embed/dist
|
packages-dir: ./hindsight-embed/dist
|
||||||
skip-existing: true
|
skip-existing: true
|
||||||
|
|
||||||
- name: Publish hindsight-crewai to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages-dir: ./hindsight-integrations/crewai/dist
|
|
||||||
skip-existing: true
|
|
||||||
|
|
||||||
- name: Publish hindsight-pydantic-ai to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages-dir: ./hindsight-integrations/pydantic-ai/dist
|
|
||||||
skip-existing: true
|
|
||||||
|
|
||||||
- name: Publish hindsight-hermes to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages-dir: ./hindsight-integrations/hermes/dist
|
|
||||||
skip-existing: true
|
|
||||||
|
|
||||||
- name: Publish hindsight-agno to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages-dir: ./hindsight-integrations/agno/dist
|
|
||||||
skip-existing: true
|
|
||||||
|
|
||||||
# Upload artifacts for GitHub release
|
# Upload artifacts for GitHub release
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
|
|
@ -148,12 +98,7 @@ jobs:
|
||||||
hindsight-api/dist/*
|
hindsight-api/dist/*
|
||||||
hindsight-all/dist/*
|
hindsight-all/dist/*
|
||||||
hindsight-all-slim/dist/*
|
hindsight-all-slim/dist/*
|
||||||
hindsight-integrations/litellm/dist/*
|
|
||||||
hindsight-embed/dist/*
|
hindsight-embed/dist/*
|
||||||
hindsight-integrations/crewai/dist/*
|
|
||||||
hindsight-integrations/pydantic-ai/dist/*
|
|
||||||
hindsight-integrations/hermes/dist/*
|
|
||||||
hindsight-integrations/agno/dist/*
|
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
release-typescript-client:
|
release-typescript-client:
|
||||||
|
|
@ -205,153 +150,6 @@ jobs:
|
||||||
path: hindsight-clients/typescript/*.tgz
|
path: hindsight-clients/typescript/*.tgz
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
release-openclaw-integration:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: npm
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: '22'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
working-directory: ./hindsight-integrations/openclaw
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
working-directory: ./hindsight-integrations/openclaw
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Publish to npm
|
|
||||||
working-directory: ./hindsight-integrations/openclaw
|
|
||||||
run: |
|
|
||||||
set +e
|
|
||||||
OUTPUT=$(npm publish --access public 2>&1)
|
|
||||||
EXIT_CODE=$?
|
|
||||||
echo "$OUTPUT"
|
|
||||||
if [ $EXIT_CODE -ne 0 ]; then
|
|
||||||
if echo "$OUTPUT" | grep -q "cannot publish over"; then
|
|
||||||
echo "Package version already published, skipping..."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
exit $EXIT_CODE
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Pack for GitHub release
|
|
||||||
working-directory: ./hindsight-integrations/openclaw
|
|
||||||
run: npm pack
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: openclaw-integration
|
|
||||||
path: hindsight-integrations/openclaw/*.tgz
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
release-ai-sdk-integration:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: npm
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: '22'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
working-directory: ./hindsight-integrations/ai-sdk
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
working-directory: ./hindsight-integrations/ai-sdk
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Publish to npm
|
|
||||||
working-directory: ./hindsight-integrations/ai-sdk
|
|
||||||
run: |
|
|
||||||
set +e
|
|
||||||
OUTPUT=$(npm publish --access public 2>&1)
|
|
||||||
EXIT_CODE=$?
|
|
||||||
echo "$OUTPUT"
|
|
||||||
if [ $EXIT_CODE -ne 0 ]; then
|
|
||||||
if echo "$OUTPUT" | grep -q "cannot publish over"; then
|
|
||||||
echo "Package version already published, skipping..."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
exit $EXIT_CODE
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Pack for GitHub release
|
|
||||||
working-directory: ./hindsight-integrations/ai-sdk
|
|
||||||
run: npm pack
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: ai-sdk-integration
|
|
||||||
path: hindsight-integrations/ai-sdk/*.tgz
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
release-chat-integration:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: npm
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: '22'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
working-directory: ./hindsight-integrations/chat
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
working-directory: ./hindsight-integrations/chat
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Publish to npm
|
|
||||||
working-directory: ./hindsight-integrations/chat
|
|
||||||
run: |
|
|
||||||
set +e
|
|
||||||
OUTPUT=$(npm publish --access public 2>&1)
|
|
||||||
EXIT_CODE=$?
|
|
||||||
echo "$OUTPUT"
|
|
||||||
if [ $EXIT_CODE -ne 0 ]; then
|
|
||||||
if echo "$OUTPUT" | grep -q "cannot publish over"; then
|
|
||||||
echo "Package version already published, skipping..."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
exit $EXIT_CODE
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Pack for GitHub release
|
|
||||||
working-directory: ./hindsight-integrations/chat
|
|
||||||
run: npm pack
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: chat-integration
|
|
||||||
path: hindsight-integrations/chat/*.tgz
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
release-control-plane:
|
release-control-plane:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: npm
|
environment: npm
|
||||||
|
|
@ -609,7 +407,7 @@ jobs:
|
||||||
|
|
||||||
create-github-release:
|
create-github-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [release-python-packages, release-typescript-client, release-openclaw-integration, release-ai-sdk-integration, release-chat-integration, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart]
|
needs: [release-python-packages, release-typescript-client, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart]
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
|
|
@ -632,24 +430,6 @@ jobs:
|
||||||
name: typescript-client
|
name: typescript-client
|
||||||
path: ./artifacts/typescript-client
|
path: ./artifacts/typescript-client
|
||||||
|
|
||||||
- name: Download OpenClaw Integration
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: openclaw-integration
|
|
||||||
path: ./artifacts/openclaw-integration
|
|
||||||
|
|
||||||
- name: Download AI SDK Integration
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: ai-sdk-integration
|
|
||||||
path: ./artifacts/ai-sdk-integration
|
|
||||||
|
|
||||||
- name: Download Chat Integration
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: chat-integration
|
|
||||||
path: ./artifacts/chat-integration
|
|
||||||
|
|
||||||
- name: Download Control Plane
|
- name: Download Control Plane
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
|
|
@ -689,19 +469,9 @@ jobs:
|
||||||
cp artifacts/python-packages/hindsight-api/dist/* release-assets/ || true
|
cp artifacts/python-packages/hindsight-api/dist/* release-assets/ || true
|
||||||
cp artifacts/python-packages/hindsight-all/dist/* release-assets/ || true
|
cp artifacts/python-packages/hindsight-all/dist/* release-assets/ || true
|
||||||
cp artifacts/python-packages/hindsight-all-slim/dist/* release-assets/ || true
|
cp artifacts/python-packages/hindsight-all-slim/dist/* release-assets/ || true
|
||||||
cp artifacts/python-packages/hindsight-integrations/litellm/dist/* release-assets/ || true
|
|
||||||
cp artifacts/python-packages/hindsight-integrations/pydantic-ai/dist/* release-assets/ || true
|
|
||||||
cp artifacts/python-packages/hindsight-integrations/hermes/dist/* release-assets/ || true
|
|
||||||
cp artifacts/python-packages/hindsight-integrations/agno/dist/* release-assets/ || true
|
|
||||||
cp artifacts/python-packages/hindsight-embed/dist/* release-assets/ || true
|
cp artifacts/python-packages/hindsight-embed/dist/* release-assets/ || true
|
||||||
# TypeScript client
|
# TypeScript client
|
||||||
cp artifacts/typescript-client/*.tgz release-assets/ || true
|
cp artifacts/typescript-client/*.tgz release-assets/ || true
|
||||||
# OpenClaw Integration
|
|
||||||
cp artifacts/openclaw-integration/*.tgz release-assets/ || true
|
|
||||||
# AI SDK Integration
|
|
||||||
cp artifacts/ai-sdk-integration/*.tgz release-assets/ || true
|
|
||||||
# Chat Integration
|
|
||||||
cp artifacts/chat-integration/*.tgz release-assets/ || true
|
|
||||||
# Control Plane
|
# Control Plane
|
||||||
cp artifacts/control-plane/*.tgz release-assets/ || true
|
cp artifacts/control-plane/*.tgz release-assets/ || true
|
||||||
# Rust CLI binaries
|
# Rust CLI binaries
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@ GITHUB_REPO = "vectorize-io/hindsight"
|
||||||
GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases"
|
GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases"
|
||||||
GITHUB_COMMIT_URL = f"https://github.com/{GITHUB_REPO}/commit"
|
GITHUB_COMMIT_URL = f"https://github.com/{GITHUB_REPO}/commit"
|
||||||
REPO_PATH = Path(__file__).parent.parent.parent
|
REPO_PATH = Path(__file__).parent.parent.parent
|
||||||
CHANGELOG_PATH = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog.md"
|
CHANGELOG_PATH = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog" / "index.md"
|
||||||
|
INTEGRATION_CHANGELOG_DIR = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog" / "integrations"
|
||||||
|
|
||||||
|
VALID_INTEGRATIONS = ["litellm", "pydantic-ai", "crewai", "ai-sdk", "chat", "openclaw"]
|
||||||
|
|
||||||
|
|
||||||
class ChangelogEntry(BaseModel):
|
class ChangelogEntry(BaseModel):
|
||||||
|
|
@ -82,6 +85,31 @@ def get_git_tags() -> list[str]:
|
||||||
return valid_tags
|
return valid_tags
|
||||||
|
|
||||||
|
|
||||||
|
def get_integration_tags(integration: str) -> list[str]:
|
||||||
|
"""Get all tags for a specific integration, sorted by semver (newest first)."""
|
||||||
|
prefix = f"integrations/{integration}/v"
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "tag", "-l", f"{prefix}*"],
|
||||||
|
cwd=REPO_PATH,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
tags = [t.strip() for t in result.stdout.strip().split("\n") if t.strip()]
|
||||||
|
|
||||||
|
valid_tags = []
|
||||||
|
for tag in tags:
|
||||||
|
version_part = tag.removeprefix(prefix)
|
||||||
|
try:
|
||||||
|
parse_semver(version_part)
|
||||||
|
valid_tags.append(tag)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
valid_tags.sort(key=lambda t: parse_semver(t.removeprefix(prefix)), reverse=True)
|
||||||
|
return valid_tags
|
||||||
|
|
||||||
|
|
||||||
def find_previous_version(new_version: str, existing_tags: list[str]) -> str | None:
|
def find_previous_version(new_version: str, existing_tags: list[str]) -> str | None:
|
||||||
"""Find the previous version based on semver rules."""
|
"""Find the previous version based on semver rules."""
|
||||||
new_major, new_minor, new_patch = parse_semver(new_version)
|
new_major, new_minor, new_patch = parse_semver(new_version)
|
||||||
|
|
@ -105,13 +133,41 @@ def find_previous_version(new_version: str, existing_tags: list[str]) -> str | N
|
||||||
return candidates[0][0]
|
return candidates[0][0]
|
||||||
|
|
||||||
|
|
||||||
def get_commits(from_ref: str | None, to_ref: str) -> list[Commit]:
|
def find_previous_integration_tag(new_version: str, existing_tags: list[str], integration: str) -> str | None:
|
||||||
|
"""Find the previous integration tag based on semver rules."""
|
||||||
|
prefix = f"integrations/{integration}/v"
|
||||||
|
new_major, new_minor, new_patch = parse_semver(new_version)
|
||||||
|
|
||||||
|
candidates = []
|
||||||
|
for tag in existing_tags:
|
||||||
|
version_part = tag.removeprefix(prefix)
|
||||||
|
try:
|
||||||
|
major, minor, patch = parse_semver(version_part)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (major, minor, patch) >= (new_major, new_minor, new_patch):
|
||||||
|
continue
|
||||||
|
|
||||||
|
candidates.append((tag, (major, minor, patch)))
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
return None
|
||||||
|
|
||||||
|
candidates.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
return candidates[0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_commits(from_ref: str | None, to_ref: str, path_filter: str | None = None) -> list[Commit]:
|
||||||
"""Get commits between two refs as structured data."""
|
"""Get commits between two refs as structured data."""
|
||||||
if from_ref:
|
if from_ref:
|
||||||
cmd = ["git", "log", "--format=%h|%s", "--no-merges", f"{from_ref}..{to_ref}"]
|
cmd = ["git", "log", "--format=%h|%s", "--no-merges", f"{from_ref}..{to_ref}"]
|
||||||
else:
|
else:
|
||||||
cmd = ["git", "log", "--format=%h|%s", "--no-merges", to_ref]
|
cmd = ["git", "log", "--format=%h|%s", "--no-merges", to_ref]
|
||||||
|
|
||||||
|
if path_filter:
|
||||||
|
cmd += ["--", path_filter]
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
cwd=REPO_PATH,
|
cwd=REPO_PATH,
|
||||||
|
|
@ -131,13 +187,16 @@ def get_commits(from_ref: str | None, to_ref: str) -> list[Commit]:
|
||||||
return commits
|
return commits
|
||||||
|
|
||||||
|
|
||||||
def get_detailed_diff(from_ref: str | None, to_ref: str) -> str:
|
def get_detailed_diff(from_ref: str | None, to_ref: str, path_filter: str | None = None) -> str:
|
||||||
"""Get file change stats between two refs."""
|
"""Get file change stats between two refs."""
|
||||||
if from_ref:
|
if from_ref:
|
||||||
cmd = ["git", "diff", "--stat", f"{from_ref}..{to_ref}"]
|
cmd = ["git", "diff", "--stat", f"{from_ref}..{to_ref}"]
|
||||||
else:
|
else:
|
||||||
cmd = ["git", "diff", "--stat", f"{to_ref}^..{to_ref}"]
|
cmd = ["git", "diff", "--stat", f"{to_ref}^..{to_ref}"]
|
||||||
|
|
||||||
|
if path_filter:
|
||||||
|
cmd += ["--", path_filter]
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
cwd=REPO_PATH,
|
cwd=REPO_PATH,
|
||||||
|
|
@ -153,11 +212,14 @@ def analyze_commits_with_llm(
|
||||||
version: str,
|
version: str,
|
||||||
commits: list[Commit],
|
commits: list[Commit],
|
||||||
file_diff: str,
|
file_diff: str,
|
||||||
|
integration: str | None = None,
|
||||||
) -> list[ChangelogEntry]:
|
) -> list[ChangelogEntry]:
|
||||||
"""Use LLM to analyze commits and return structured changelog entries."""
|
"""Use LLM to analyze commits and return structured changelog entries."""
|
||||||
commits_json = json.dumps([{"commit_id": c.hash, "message": c.message} for c in commits], indent=2)
|
commits_json = json.dumps([{"commit_id": c.hash, "message": c.message} for c in commits], indent=2)
|
||||||
|
|
||||||
prompt = f"""Analyze the following git commits for release {version} of Hindsight (an AI memory system).
|
subject = f"the {integration} integration for Hindsight" if integration else f"release {version} of Hindsight"
|
||||||
|
|
||||||
|
prompt = f"""Analyze the following git commits for {subject} (an AI memory system).
|
||||||
|
|
||||||
For each meaningful change, create a changelog entry with:
|
For each meaningful change, create a changelog entry with:
|
||||||
- category: one of "feature", "improvement", "bugfix", "breaking", "other"
|
- category: one of "feature", "improvement", "bugfix", "breaking", "other"
|
||||||
|
|
@ -192,9 +254,14 @@ def build_changelog_markdown(
|
||||||
version: str,
|
version: str,
|
||||||
tag: str,
|
tag: str,
|
||||||
entries: list[ChangelogEntry],
|
entries: list[ChangelogEntry],
|
||||||
|
integration: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Build markdown changelog from structured entries."""
|
"""Build markdown changelog from structured entries."""
|
||||||
release_url = f"{GITHUB_RELEASES_URL}/tag/{tag}"
|
tag_url = (
|
||||||
|
f"https://github.com/{GITHUB_REPO}/releases/tag/{tag}"
|
||||||
|
if not integration
|
||||||
|
else f"https://github.com/{GITHUB_REPO}/tree/{tag}"
|
||||||
|
)
|
||||||
|
|
||||||
# Group entries by category
|
# Group entries by category
|
||||||
categories = {
|
categories = {
|
||||||
|
|
@ -213,7 +280,7 @@ def build_changelog_markdown(
|
||||||
categories["other"][1].append(entry)
|
categories["other"][1].append(entry)
|
||||||
|
|
||||||
# Build markdown
|
# Build markdown
|
||||||
lines = [f"## [{version}]({release_url})", ""]
|
lines = [f"## [{version}]({tag_url})", ""]
|
||||||
|
|
||||||
has_entries = False
|
has_entries = False
|
||||||
for cat_key in ["breaking", "feature", "improvement", "bugfix", "other"]:
|
for cat_key in ["breaking", "feature", "improvement", "bugfix", "other"]:
|
||||||
|
|
@ -234,22 +301,12 @@ def build_changelog_markdown(
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def read_existing_changelog() -> tuple[str, str]:
|
def read_existing_changelog(path: Path, default_header: str) -> tuple[str, str]:
|
||||||
"""Read existing changelog and split into header and content."""
|
"""Read existing changelog and split into header and content."""
|
||||||
if not CHANGELOG_PATH.exists():
|
if not path.exists():
|
||||||
header = """---
|
return default_header, ""
|
||||||
hide_table_of_contents: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Changelog
|
content = path.read_text()
|
||||||
|
|
||||||
This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted.
|
|
||||||
|
|
||||||
For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases).
|
|
||||||
"""
|
|
||||||
return header, ""
|
|
||||||
|
|
||||||
content = CHANGELOG_PATH.read_text()
|
|
||||||
|
|
||||||
match = re.search(r"^## ", content, re.MULTILINE)
|
match = re.search(r"^## ", content, re.MULTILINE)
|
||||||
if match:
|
if match:
|
||||||
|
|
@ -262,11 +319,11 @@ For full release details, see [GitHub Releases](https://github.com/vectorize-io/
|
||||||
return header, releases
|
return header, releases
|
||||||
|
|
||||||
|
|
||||||
def write_changelog(header: str, new_entry: str, existing_releases: str) -> None:
|
def write_changelog(path: Path, header: str, new_entry: str, existing_releases: str) -> None:
|
||||||
"""Write changelog with new entry prepended."""
|
"""Write changelog with new entry prepended."""
|
||||||
content = header + new_entry + "\n" + existing_releases
|
content = header + new_entry + "\n" + existing_releases
|
||||||
CHANGELOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
CHANGELOG_PATH.write_text(content.rstrip() + "\n")
|
path.write_text(content.rstrip() + "\n")
|
||||||
|
|
||||||
|
|
||||||
def generate_changelog_entry(
|
def generate_changelog_entry(
|
||||||
|
|
@ -329,22 +386,150 @@ def generate_changelog_entry(
|
||||||
|
|
||||||
new_entry = build_changelog_markdown(display_version, tag, entries)
|
new_entry = build_changelog_markdown(display_version, tag, entries)
|
||||||
|
|
||||||
header, existing_releases = read_existing_changelog()
|
default_header = """---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted.
|
||||||
|
|
||||||
|
For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases).
|
||||||
|
|
||||||
|
"""
|
||||||
|
header, existing_releases = read_existing_changelog(CHANGELOG_PATH, default_header)
|
||||||
|
|
||||||
if f"## [{display_version}]" in existing_releases:
|
if f"## [{display_version}]" in existing_releases:
|
||||||
console.print(f"[red]Error: Version {display_version} already exists in changelog[/red]")
|
console.print(f"[red]Error: Version {display_version} already exists in changelog[/red]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
write_changelog(header, new_entry, existing_releases)
|
write_changelog(CHANGELOG_PATH, header, new_entry, existing_releases)
|
||||||
|
|
||||||
console.print(f"\n[green]Changelog updated: {CHANGELOG_PATH}[/green]")
|
console.print(f"\n[green]Changelog updated: {CHANGELOG_PATH}[/green]")
|
||||||
console.print(f"\n[bold]New entry:[/bold]\n{new_entry}")
|
console.print(f"\n[bold]New entry:[/bold]\n{new_entry}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_integration_changelog_entry(
|
||||||
|
integration: str,
|
||||||
|
version: str,
|
||||||
|
llm_model: str = "gpt-5.2",
|
||||||
|
) -> None:
|
||||||
|
"""Generate changelog entry for a specific integration version."""
|
||||||
|
if integration not in VALID_INTEGRATIONS:
|
||||||
|
console.print(f"[red]Error: Unknown integration '{integration}'. Valid: {', '.join(VALID_INTEGRATIONS)}[/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
api_key = os.environ.get("OPENAI_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
console.print("[red]Error: OPENAI_API_KEY environment variable not set[/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
client = OpenAI(api_key=api_key)
|
||||||
|
|
||||||
|
display_version = version.lstrip("v")
|
||||||
|
path_filter = f"hindsight-integrations/{integration}/"
|
||||||
|
changelog_path = INTEGRATION_CHANGELOG_DIR / f"{integration}.md"
|
||||||
|
|
||||||
|
console.print(f"[blue]Fetching integration tags for {integration}...[/blue]")
|
||||||
|
existing_tags = get_integration_tags(integration)
|
||||||
|
|
||||||
|
previous_tag = find_previous_integration_tag(display_version, existing_tags, integration)
|
||||||
|
|
||||||
|
if previous_tag:
|
||||||
|
console.print(f"[green]Found previous tag: {previous_tag}[/green]")
|
||||||
|
else:
|
||||||
|
console.print("[yellow]No previous tag found, will include all commits touching this integration[/yellow]")
|
||||||
|
|
||||||
|
console.print(f"[blue]Getting commits for {path_filter}...[/blue]")
|
||||||
|
commits = get_commits(previous_tag, "HEAD", path_filter=path_filter)
|
||||||
|
file_diff = get_detailed_diff(previous_tag, "HEAD", path_filter=path_filter)
|
||||||
|
|
||||||
|
if not commits:
|
||||||
|
console.print("[yellow]Warning: No commits found touching this integration path[/yellow]")
|
||||||
|
entries = []
|
||||||
|
else:
|
||||||
|
console.print(f"[blue]Found {len(commits)} commits[/blue]")
|
||||||
|
|
||||||
|
console.print("\n[bold]Commits:[/bold]")
|
||||||
|
for c in commits:
|
||||||
|
console.print(f" {c.hash} {c.message}")
|
||||||
|
|
||||||
|
console.print("\n[bold]Files changed:[/bold]")
|
||||||
|
console.print(file_diff[:4000] if len(file_diff) > 4000 else file_diff)
|
||||||
|
console.print("")
|
||||||
|
|
||||||
|
console.print(f"[blue]Analyzing commits with LLM ({llm_model})...[/blue]")
|
||||||
|
entries = analyze_commits_with_llm(
|
||||||
|
client, llm_model, display_version, commits, file_diff, integration=integration
|
||||||
|
)
|
||||||
|
|
||||||
|
console.print(f"\n[bold]LLM identified {len(entries)} changelog entries:[/bold]")
|
||||||
|
for entry in entries:
|
||||||
|
console.print(f" [{entry.category}] {entry.summary} ({entry.commit_id})")
|
||||||
|
|
||||||
|
integration_tag = f"integrations/{integration}/v{display_version}"
|
||||||
|
new_entry = build_changelog_markdown(display_version, integration_tag, entries, integration=integration)
|
||||||
|
|
||||||
|
package_name = _get_package_name(integration)
|
||||||
|
default_header = f"""---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# {_integration_display_name(integration)} Integration Changelog
|
||||||
|
|
||||||
|
Changelog for [`{package_name}`]({_package_url(integration, package_name)}).
|
||||||
|
|
||||||
|
For the source code, see [`hindsight-integrations/{integration}`](https://github.com/{GITHUB_REPO}/tree/main/hindsight-integrations/{integration}).
|
||||||
|
|
||||||
|
← [Back to main changelog](/changelog)
|
||||||
|
|
||||||
|
"""
|
||||||
|
header, existing_releases = read_existing_changelog(changelog_path, default_header)
|
||||||
|
|
||||||
|
if f"## [{display_version}]" in existing_releases:
|
||||||
|
console.print(f"[red]Error: Version {display_version} already exists in integration changelog[/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
write_changelog(changelog_path, header, new_entry, existing_releases)
|
||||||
|
|
||||||
|
console.print(f"\n[green]Integration changelog updated: {changelog_path}[/green]")
|
||||||
|
console.print(f"\n[bold]New entry:[/bold]\n{new_entry}")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_package_name(integration: str) -> str:
|
||||||
|
packages = {
|
||||||
|
"litellm": "hindsight-litellm",
|
||||||
|
"pydantic-ai": "hindsight-pydantic-ai",
|
||||||
|
"crewai": "hindsight-crewai",
|
||||||
|
"ai-sdk": "@vectorize-io/hindsight-ai-sdk",
|
||||||
|
"chat": "@vectorize-io/hindsight-chat",
|
||||||
|
"openclaw": "@vectorize-io/hindsight-openclaw",
|
||||||
|
}
|
||||||
|
return packages[integration]
|
||||||
|
|
||||||
|
|
||||||
|
def _package_url(integration: str, package_name: str) -> str:
|
||||||
|
if package_name.startswith("@"):
|
||||||
|
return f"https://www.npmjs.com/package/{package_name}"
|
||||||
|
return f"https://pypi.org/project/{package_name}/"
|
||||||
|
|
||||||
|
|
||||||
|
def _integration_display_name(integration: str) -> str:
|
||||||
|
names = {
|
||||||
|
"litellm": "LiteLLM",
|
||||||
|
"pydantic-ai": "Pydantic AI",
|
||||||
|
"crewai": "CrewAI",
|
||||||
|
"ai-sdk": "AI SDK",
|
||||||
|
"chat": "Chat SDK",
|
||||||
|
"openclaw": "OpenClaw",
|
||||||
|
}
|
||||||
|
return names.get(integration, integration)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Generate changelog entry for a release",
|
description="Generate changelog entry for a release",
|
||||||
usage="generate-changelog VERSION [--model MODEL]",
|
usage="generate-changelog VERSION [--model MODEL] [--integration NAME]",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"version",
|
"version",
|
||||||
|
|
@ -355,13 +540,25 @@ def main():
|
||||||
default="gpt-5.2",
|
default="gpt-5.2",
|
||||||
help="OpenAI model to use (default: gpt-5.2)",
|
help="OpenAI model to use (default: gpt-5.2)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--integration",
|
||||||
|
default=None,
|
||||||
|
help=f"Generate changelog for a specific integration. Valid: {', '.join(VALID_INTEGRATIONS)}",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
generate_changelog_entry(
|
if args.integration:
|
||||||
version=args.version,
|
generate_integration_changelog_entry(
|
||||||
llm_model=args.model,
|
integration=args.integration,
|
||||||
)
|
version=args.version,
|
||||||
|
llm_model=args.model,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
generate_changelog_entry(
|
||||||
|
version=args.version,
|
||||||
|
llm_model=args.model,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ This changelog highlights user-facing changes only. Internal maintenance, CI/CD,
|
||||||
|
|
||||||
For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases).
|
For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases).
|
||||||
|
|
||||||
|
## Integration Changelogs
|
||||||
|
|
||||||
|
| Integration | Package | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| [LiteLLM](/changelog/integrations/litellm) | `hindsight-litellm` | Universal LLM memory via LiteLLM (100+ providers) |
|
||||||
|
| [Pydantic AI](/changelog/integrations/pydantic-ai) | `hindsight-pydantic-ai` | Persistent memory tools for Pydantic AI agents |
|
||||||
|
| [CrewAI](/changelog/integrations/crewai) | `hindsight-crewai` | Persistent memory for CrewAI agents |
|
||||||
|
| [AI SDK](/changelog/integrations/ai-sdk) | `@vectorize-io/hindsight-ai-sdk` | Memory integration for Vercel AI SDK |
|
||||||
|
| [Chat SDK](/changelog/integrations/chat) | `@vectorize-io/hindsight-chat` | Memory integration for Vercel Chat SDK |
|
||||||
|
| [OpenClaw](/changelog/integrations/openclaw) | `@vectorize-io/hindsight-openclaw` | Hindsight memory plugin for OpenClaw |
|
||||||
|
|
||||||
## [0.4.19](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.19)
|
## [0.4.19](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.19)
|
||||||
|
|
||||||
**Features**
|
**Features**
|
||||||
11
hindsight-docs/src/pages/changelog/integrations/ai-sdk.md
Normal file
11
hindsight-docs/src/pages/changelog/integrations/ai-sdk.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# AI SDK Integration Changelog
|
||||||
|
|
||||||
|
Changelog for [`@vectorize-io/hindsight-ai-sdk`](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk) — memory integration for Vercel AI SDK.
|
||||||
|
|
||||||
|
For the source code, see [`hindsight-integrations/ai-sdk`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/ai-sdk).
|
||||||
|
|
||||||
|
← [Back to main changelog](/changelog)
|
||||||
11
hindsight-docs/src/pages/changelog/integrations/chat.md
Normal file
11
hindsight-docs/src/pages/changelog/integrations/chat.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Chat SDK Integration Changelog
|
||||||
|
|
||||||
|
Changelog for [`@vectorize-io/hindsight-chat`](https://www.npmjs.com/package/@vectorize-io/hindsight-chat) — memory integration for Vercel Chat SDK.
|
||||||
|
|
||||||
|
For the source code, see [`hindsight-integrations/chat`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/chat).
|
||||||
|
|
||||||
|
← [Back to main changelog](/changelog)
|
||||||
11
hindsight-docs/src/pages/changelog/integrations/crewai.md
Normal file
11
hindsight-docs/src/pages/changelog/integrations/crewai.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# CrewAI Integration Changelog
|
||||||
|
|
||||||
|
Changelog for [`hindsight-crewai`](https://pypi.org/project/hindsight-crewai/) — persistent memory for CrewAI agents.
|
||||||
|
|
||||||
|
For the source code, see [`hindsight-integrations/crewai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/crewai).
|
||||||
|
|
||||||
|
← [Back to main changelog](/changelog)
|
||||||
11
hindsight-docs/src/pages/changelog/integrations/litellm.md
Normal file
11
hindsight-docs/src/pages/changelog/integrations/litellm.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# LiteLLM Integration Changelog
|
||||||
|
|
||||||
|
Changelog for [`hindsight-litellm`](https://pypi.org/project/hindsight-litellm/) — universal LLM memory integration via LiteLLM.
|
||||||
|
|
||||||
|
For the source code, see [`hindsight-integrations/litellm`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/litellm).
|
||||||
|
|
||||||
|
← [Back to main changelog](/changelog)
|
||||||
11
hindsight-docs/src/pages/changelog/integrations/openclaw.md
Normal file
11
hindsight-docs/src/pages/changelog/integrations/openclaw.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpenClaw Integration Changelog
|
||||||
|
|
||||||
|
Changelog for [`@vectorize-io/hindsight-openclaw`](https://www.npmjs.com/package/@vectorize-io/hindsight-openclaw) — Hindsight memory plugin for OpenClaw.
|
||||||
|
|
||||||
|
For the source code, see [`hindsight-integrations/openclaw`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/openclaw).
|
||||||
|
|
||||||
|
← [Back to main changelog](/changelog)
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
hide_table_of_contents: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pydantic AI Integration Changelog
|
||||||
|
|
||||||
|
Changelog for [`hindsight-pydantic-ai`](https://pypi.org/project/hindsight-pydantic-ai/) — persistent memory tools for Pydantic AI agents.
|
||||||
|
|
||||||
|
For the source code, see [`hindsight-integrations/pydantic-ai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/pydantic-ai).
|
||||||
|
|
||||||
|
← [Back to main changelog](/changelog)
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 9
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agno
|
||||||
|
|
||||||
|
Persistent memory tools for [Agno](https://github.com/agno-agi/agno) agents via Hindsight. Give your agents long-term memory with retain, recall, and reflect — using Agno's native Toolkit pattern.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Native Toolkit** - Extends Agno's `Toolkit` base class, just like `Mem0Tools`
|
||||||
|
- **Memory Instructions** - Pre-recall memories for injection into `Agent(instructions=[...])`
|
||||||
|
- **Three Memory Tools** - Retain (store), Recall (search), Reflect (synthesize) — include any combination
|
||||||
|
- **Flexible Bank Resolution** - Static bank ID, `RunContext.user_id`, or custom resolver
|
||||||
|
- **Simple Configuration** - Configure once globally, or pass a client directly
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install hindsight-agno
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```python
|
||||||
|
from agno.agent import Agent
|
||||||
|
from agno.models.openai import OpenAIChat
|
||||||
|
from hindsight_agno import HindsightTools
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
model=OpenAIChat(id="gpt-4o-mini"),
|
||||||
|
tools=[HindsightTools(
|
||||||
|
bank_id="user-123",
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
|
||||||
|
agent.print_response("Remember that I prefer dark mode")
|
||||||
|
agent.print_response("What are my preferences?")
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent now has three tools it can call:
|
||||||
|
|
||||||
|
- **`retain_memory`** — Store information to long-term memory
|
||||||
|
- **`recall_memory`** — Search long-term memory for relevant facts
|
||||||
|
- **`reflect_on_memory`** — Synthesize a reasoned answer from memories
|
||||||
|
|
||||||
|
## With Memory Instructions
|
||||||
|
|
||||||
|
Pre-recall relevant memories and inject them into the system prompt:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from hindsight_agno import HindsightTools, memory_instructions
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
model=OpenAIChat(id="gpt-4o-mini"),
|
||||||
|
tools=[HindsightTools(
|
||||||
|
bank_id="user-123",
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
)],
|
||||||
|
instructions=[memory_instructions(
|
||||||
|
bank_id="user-123",
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Selecting Tools
|
||||||
|
|
||||||
|
Include only the tools you need:
|
||||||
|
|
||||||
|
```python
|
||||||
|
tools = [HindsightTools(
|
||||||
|
bank_id="user-123",
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
enable_retain=True,
|
||||||
|
enable_recall=True,
|
||||||
|
enable_reflect=False, # Omit reflect
|
||||||
|
)]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bank Resolution
|
||||||
|
|
||||||
|
The bank ID is resolved in order:
|
||||||
|
|
||||||
|
1. **`bank_resolver`** — Custom callable `(RunContext) -> str`
|
||||||
|
2. **`bank_id`** — Static bank ID passed to constructor
|
||||||
|
3. **`run_context.user_id`** — Automatic per-user banks
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Per-user banks from RunContext
|
||||||
|
agent = Agent(
|
||||||
|
model=OpenAIChat(id="gpt-4o-mini"),
|
||||||
|
tools=[HindsightTools(hindsight_api_url="http://localhost:8888")],
|
||||||
|
user_id="user-123", # Used as bank_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Custom resolver
|
||||||
|
def resolve_bank(ctx):
|
||||||
|
return f"team-{ctx.user_id}"
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
model=OpenAIChat(id="gpt-4o-mini"),
|
||||||
|
tools=[HindsightTools(
|
||||||
|
bank_resolver=resolve_bank,
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Global Configuration
|
||||||
|
|
||||||
|
Instead of passing connection details to every toolkit, configure once:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from hindsight_agno import configure, HindsightTools
|
||||||
|
|
||||||
|
configure(
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
api_key="your-api-key", # Or set HINDSIGHT_API_KEY env var
|
||||||
|
budget="mid", # Recall budget: low/mid/high
|
||||||
|
max_tokens=4096, # Max tokens for recall results
|
||||||
|
tags=["env:prod"], # Tags for stored memories
|
||||||
|
recall_tags=["scope:global"], # Tags to filter recall
|
||||||
|
recall_tags_match="any", # Tag match mode: any/all/any_strict/all_strict
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now create toolkit without passing connection details
|
||||||
|
tools = [HindsightTools(bank_id="user-123")]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
### `HindsightTools()`
|
||||||
|
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `bank_id` | `None` | Static Hindsight memory bank ID |
|
||||||
|
| `bank_resolver` | `None` | Callable `(RunContext) -> str` for dynamic bank ID |
|
||||||
|
| `client` | `None` | Pre-configured Hindsight client |
|
||||||
|
| `hindsight_api_url` | `None` | API URL (used if no client provided) |
|
||||||
|
| `api_key` | `None` | API key (used if no client provided) |
|
||||||
|
| `budget` | `"mid"` | Recall/reflect budget level (low/mid/high) |
|
||||||
|
| `max_tokens` | `4096` | Maximum tokens for recall results |
|
||||||
|
| `tags` | `None` | Tags applied when storing memories |
|
||||||
|
| `recall_tags` | `None` | Tags to filter when searching |
|
||||||
|
| `recall_tags_match` | `"any"` | Tag matching mode |
|
||||||
|
| `enable_retain` | `True` | Include the retain (store) tool |
|
||||||
|
| `enable_recall` | `True` | Include the recall (search) tool |
|
||||||
|
| `enable_reflect` | `True` | Include the reflect (synthesize) tool |
|
||||||
|
|
||||||
|
### `memory_instructions()`
|
||||||
|
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `bank_id` | *required* | Hindsight memory bank ID |
|
||||||
|
| `client` | `None` | Pre-configured Hindsight client |
|
||||||
|
| `hindsight_api_url` | `None` | API URL (used if no client provided) |
|
||||||
|
| `api_key` | `None` | API key (used if no client provided) |
|
||||||
|
| `query` | `"relevant context about the user"` | Recall query for memory injection |
|
||||||
|
| `budget` | `"low"` | Recall budget level |
|
||||||
|
| `max_results` | `5` | Maximum memories to inject |
|
||||||
|
| `max_tokens` | `4096` | Maximum tokens for recall results |
|
||||||
|
| `prefix` | `"Relevant memories:\n"` | Text prepended before memory list |
|
||||||
|
| `tags` | `None` | Tags to filter recall results |
|
||||||
|
| `tags_match` | `"any"` | Tag matching mode |
|
||||||
|
|
||||||
|
### `configure()`
|
||||||
|
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `hindsight_api_url` | Production API | Hindsight API URL |
|
||||||
|
| `api_key` | `HINDSIGHT_API_KEY` env | API key for authentication |
|
||||||
|
| `budget` | `"mid"` | Default recall budget level |
|
||||||
|
| `max_tokens` | `4096` | Default max tokens for recall |
|
||||||
|
| `tags` | `None` | Default tags for retain operations |
|
||||||
|
| `recall_tags` | `None` | Default tags to filter recall |
|
||||||
|
| `recall_tags_match` | `"any"` | Default tag matching mode |
|
||||||
|
| `verbose` | `False` | Enable verbose logging |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python >= 3.10
|
||||||
|
- agno
|
||||||
|
- hindsight-client >= 0.4.0
|
||||||
|
- A running Hindsight API server
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
---
|
||||||
|
sidebar_position: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hermes Agent
|
||||||
|
|
||||||
|
Hindsight memory integration for [Hermes Agent](https://github.com/NousResearch/hermes-agent). Gives your Hermes agent persistent long-term memory via retain, recall, and reflect tools.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
This package registers three tools into Hermes via its plugin system:
|
||||||
|
|
||||||
|
- **`hindsight_retain`** — Stores information to long-term memory. Hermes calls this when the user shares facts, preferences, or anything worth remembering.
|
||||||
|
- **`hindsight_recall`** — Searches long-term memory for relevant information. Returns a numbered list of matching memories.
|
||||||
|
- **`hindsight_reflect`** — Synthesizes a thoughtful answer from stored memories. Use this when you want Hermes to reason over what it knows rather than return raw facts.
|
||||||
|
|
||||||
|
These tools appear under the `[hindsight]` toolset in Hermes's `/tools` list.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Install hindsight-hermes into the Hermes venv
|
||||||
|
|
||||||
|
The package must be installed in the **same Python environment** that Hermes runs in, so the entry point is discoverable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv pip install hindsight-hermes --python $HOME/.hermes/hermes-agent/venv/bin/python
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Set environment variables
|
||||||
|
|
||||||
|
The plugin reads its configuration from environment variables. Set these before launching Hermes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Required — tells the plugin where Hindsight is running
|
||||||
|
export HINDSIGHT_API_URL=http://localhost:8888
|
||||||
|
|
||||||
|
# Required — the memory bank to read/write. Think of this as a "brain" for one user or agent.
|
||||||
|
export HINDSIGHT_BANK_ID=my-agent
|
||||||
|
|
||||||
|
# Optional — only needed if using Hindsight Cloud (https://api.hindsight.vectorize.io)
|
||||||
|
export HINDSIGHT_API_KEY=your-api-key
|
||||||
|
|
||||||
|
# Optional — recall budget: low (fast), mid (default), high (thorough)
|
||||||
|
export HINDSIGHT_BUDGET=mid
|
||||||
|
```
|
||||||
|
|
||||||
|
If neither `HINDSIGHT_API_URL` nor `HINDSIGHT_API_KEY` is set, the plugin silently skips registration — Hermes starts normally without the Hindsight tools.
|
||||||
|
|
||||||
|
### 3. Disable Hermes's built-in memory tool
|
||||||
|
|
||||||
|
Hermes has its own `memory` tool that saves to local files (`~/.hermes/`). If both are active, the LLM tends to prefer the built-in one since it's familiar. Disable it so the LLM uses Hindsight instead:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hermes tools disable memory
|
||||||
|
```
|
||||||
|
|
||||||
|
This persists across sessions. You can re-enable it later with `hermes tools enable memory`.
|
||||||
|
|
||||||
|
### 4. Start Hindsight API
|
||||||
|
|
||||||
|
Follow the [Quick Start](/developer/api/quickstart) guide to get the Hindsight API running, then come back here.
|
||||||
|
|
||||||
|
### 5. Launch Hermes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hermes
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the plugin loaded by typing `/tools` — you should see:
|
||||||
|
|
||||||
|
```
|
||||||
|
[hindsight]
|
||||||
|
* hindsight_recall - Search long-term memory for relevant information.
|
||||||
|
* hindsight_reflect - Synthesize a thoughtful answer from long-term memories.
|
||||||
|
* hindsight_retain - Store information to long-term memory for later retrieval.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Test it
|
||||||
|
|
||||||
|
**Store a memory:**
|
||||||
|
> Remember that my favourite colour is red
|
||||||
|
|
||||||
|
You should see `⚡ hindsight` in the response, confirming it called `hindsight_retain`.
|
||||||
|
|
||||||
|
**Recall a memory:**
|
||||||
|
> What's my favourite colour?
|
||||||
|
|
||||||
|
**Reflect on memories:**
|
||||||
|
> Based on what you know about me, suggest a colour scheme for my IDE
|
||||||
|
|
||||||
|
This calls `hindsight_reflect`, which synthesizes a response from all stored memories.
|
||||||
|
|
||||||
|
**Verify via API:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:8888/v1/default/banks/my-agent/memories/recall \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"query": "favourite colour", "budget": "low"}' | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Tools don't appear in `/tools`
|
||||||
|
|
||||||
|
1. **Check the plugin is installed in the right venv.** Run this from the Hermes venv:
|
||||||
|
```bash
|
||||||
|
python -c "from hindsight_hermes import register; print('OK')"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check the entry point is registered:**
|
||||||
|
```bash
|
||||||
|
python -c "
|
||||||
|
import importlib.metadata
|
||||||
|
eps = importlib.metadata.entry_points(group='hermes_agent.plugins')
|
||||||
|
print(list(eps))
|
||||||
|
"
|
||||||
|
```
|
||||||
|
You should see `EntryPoint(name='hindsight', value='hindsight_hermes', group='hermes_agent.plugins')`.
|
||||||
|
|
||||||
|
3. **Check env vars are set.** The plugin skips registration silently if `HINDSIGHT_API_URL` and `HINDSIGHT_API_KEY` are both unset.
|
||||||
|
|
||||||
|
### Hermes uses built-in memory instead of Hindsight
|
||||||
|
|
||||||
|
Run `hermes tools disable memory` and restart. The built-in `memory` tool and Hindsight tools have overlapping purposes — the LLM will prefer whichever it's more familiar with, which is usually the built-in one.
|
||||||
|
|
||||||
|
### Bank not found errors
|
||||||
|
|
||||||
|
The plugin auto-creates banks on first use. If you see bank errors, check that the Hindsight API is running and `HINDSIGHT_API_URL` is correct.
|
||||||
|
|
||||||
|
### Connection refused
|
||||||
|
|
||||||
|
Make sure the Hindsight API is running and listening on the URL you configured. Test with:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual registration (advanced)
|
||||||
|
|
||||||
|
If you don't want to use the plugin system, you can register tools directly in a Hermes startup script or custom agent:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from hindsight_hermes import register_tools
|
||||||
|
|
||||||
|
register_tools(
|
||||||
|
bank_id="my-agent",
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
budget="mid",
|
||||||
|
tags=["hermes"], # applied to all retained memories
|
||||||
|
recall_tags=["hermes"], # filter recall to only these tags
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
This imports `tools.registry` from Hermes at call time and registers the three tools directly. This approach gives you more control over parameters but requires Hermes to be importable.
|
||||||
|
|
||||||
|
## Memory instructions (system prompt injection)
|
||||||
|
|
||||||
|
Pre-recall memories at startup and inject them into the system prompt, so the agent starts every conversation with relevant context:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from hindsight_hermes import memory_instructions
|
||||||
|
|
||||||
|
context = memory_instructions(
|
||||||
|
bank_id="my-agent",
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
query="user preferences and important context",
|
||||||
|
budget="low",
|
||||||
|
max_results=5,
|
||||||
|
)
|
||||||
|
# Returns:
|
||||||
|
# Relevant memories:
|
||||||
|
# 1. User's favourite colour is red
|
||||||
|
# 2. User prefers dark mode
|
||||||
|
```
|
||||||
|
|
||||||
|
This never raises — if the API is down or no memories exist, it returns an empty string.
|
||||||
|
|
||||||
|
## Global configuration (advanced)
|
||||||
|
|
||||||
|
Instead of passing parameters to every call, configure once:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from hindsight_hermes import configure
|
||||||
|
|
||||||
|
configure(
|
||||||
|
hindsight_api_url="http://localhost:8888",
|
||||||
|
api_key="your-key",
|
||||||
|
budget="mid",
|
||||||
|
tags=["hermes"],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Subsequent calls to `register_tools()` or `memory_instructions()` will use these defaults if no explicit values are provided.
|
||||||
|
|
||||||
|
## MCP alternative
|
||||||
|
|
||||||
|
Hermes also supports MCP servers natively. You can use Hindsight's MCP server directly instead of this plugin — no `hindsight-hermes` package needed:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# In your Hermes config
|
||||||
|
mcp_servers:
|
||||||
|
- name: hindsight
|
||||||
|
url: http://localhost:8888/mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
This exposes the same retain/recall/reflect operations through Hermes's MCP integration. The tradeoff is that MCP tools may have different naming and the LLM needs to discover them, whereas the plugin registers tools with Hermes-native schemas.
|
||||||
|
|
||||||
|
## Configuration reference
|
||||||
|
|
||||||
|
| Parameter | Env Var | Default | Description |
|
||||||
|
|-----------|---------|---------|-------------|
|
||||||
|
| `hindsight_api_url` | `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` | Hindsight API URL |
|
||||||
|
| `api_key` | `HINDSIGHT_API_KEY` | — | API key for authentication |
|
||||||
|
| `bank_id` | `HINDSIGHT_BANK_ID` | — | Memory bank ID |
|
||||||
|
| `budget` | `HINDSIGHT_BUDGET` | `mid` | Recall budget (low/mid/high) |
|
||||||
|
| `max_tokens` | — | `4096` | Max tokens for recall results |
|
||||||
|
| `tags` | — | — | Tags applied when storing memories |
|
||||||
|
| `recall_tags` | — | — | Tags to filter recall results |
|
||||||
|
| `recall_tags_match` | — | `any` | Tag matching mode (any/all/any_strict/all_strict) |
|
||||||
|
| `toolset` | — | `hindsight` | Hermes toolset group name |
|
||||||
|
|
@ -275,6 +275,22 @@
|
||||||
"icon": "/img/icons/pydanticai.png"
|
"icon": "/img/icons/pydanticai.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "doc",
|
||||||
|
"id": "sdks/integrations/agno",
|
||||||
|
"label": "Agno",
|
||||||
|
"customProps": {
|
||||||
|
"icon": "/img/icons/agno.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doc",
|
||||||
|
"id": "sdks/integrations/hermes",
|
||||||
|
"label": "Hermes Agent",
|
||||||
|
"customProps": {
|
||||||
|
"icon": "/img/icons/hermes.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "doc",
|
"type": "doc",
|
||||||
"id": "sdks/integrations/skills",
|
"id": "sdks/integrations/skills",
|
||||||
|
|
|
||||||
148
scripts/release-integration.sh
Executable file
148
scripts/release-integration.sh
Executable file
|
|
@ -0,0 +1,148 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
print_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||||
|
print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
|
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
|
VALID_INTEGRATIONS=("litellm" "pydantic-ai" "crewai" "ai-sdk" "chat" "openclaw")
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
print_error "Usage: $0 <integration> <version>"
|
||||||
|
echo ""
|
||||||
|
echo " integration One of: ${VALID_INTEGRATIONS[*]}"
|
||||||
|
echo " version Semantic version (e.g. 0.2.0)"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 litellm 0.2.0"
|
||||||
|
echo " $0 pydantic-ai 1.0.0"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
INTEGRATION=$1
|
||||||
|
VERSION=$2
|
||||||
|
|
||||||
|
# Validate integration name
|
||||||
|
VALID=false
|
||||||
|
for v in "${VALID_INTEGRATIONS[@]}"; do
|
||||||
|
if [ "$v" = "$INTEGRATION" ]; then
|
||||||
|
VALID=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$VALID" = "false" ]; then
|
||||||
|
print_error "Unknown integration '$INTEGRATION'"
|
||||||
|
print_info "Valid integrations: ${VALID_INTEGRATIONS[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate version format
|
||||||
|
if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
print_error "Invalid version format. Please use semantic versioning (e.g., 0.2.0)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TAG="integrations/$INTEGRATION/v$VERSION"
|
||||||
|
|
||||||
|
print_info "Releasing $INTEGRATION v$VERSION (tag: $TAG)"
|
||||||
|
|
||||||
|
# Check if we're on main branch
|
||||||
|
CURRENT_BRANCH=$(git branch --show-current)
|
||||||
|
if [ "$CURRENT_BRANCH" != "main" ]; then
|
||||||
|
print_warn "You are not on the main branch (current: $CURRENT_BRANCH)"
|
||||||
|
read -p "Do you want to continue? (y/n) " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
print_error "Release cancelled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if working directory is clean
|
||||||
|
if [[ -n $(git status -s) ]]; then
|
||||||
|
print_error "Working directory is not clean. Please commit or stash your changes."
|
||||||
|
git status -s
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if tag already exists
|
||||||
|
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||||
|
print_error "Tag $TAG already exists"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load .env for OPENAI_API_KEY if needed
|
||||||
|
if [ -z "$OPENAI_API_KEY" ] && [ -f ".env" ]; then
|
||||||
|
print_info "Loading environment from .env"
|
||||||
|
set -a
|
||||||
|
source ".env"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$OPENAI_API_KEY" ]; then
|
||||||
|
print_error "OPENAI_API_KEY is not set and no .env file found. Required for changelog generation."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine integration type and update version
|
||||||
|
INTEGRATION_DIR="hindsight-integrations/$INTEGRATION"
|
||||||
|
|
||||||
|
if [ ! -d "$INTEGRATION_DIR" ]; then
|
||||||
|
print_error "Integration directory not found: $INTEGRATION_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$INTEGRATION_DIR/pyproject.toml" ]; then
|
||||||
|
print_info "Updating version in $INTEGRATION_DIR/pyproject.toml"
|
||||||
|
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$INTEGRATION_DIR/pyproject.toml"
|
||||||
|
rm "$INTEGRATION_DIR/pyproject.toml.bak"
|
||||||
|
elif [ -f "$INTEGRATION_DIR/package.json" ]; then
|
||||||
|
print_info "Updating version in $INTEGRATION_DIR/package.json"
|
||||||
|
sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$INTEGRATION_DIR/package.json"
|
||||||
|
rm "$INTEGRATION_DIR/package.json.bak"
|
||||||
|
else
|
||||||
|
print_error "No pyproject.toml or package.json found in $INTEGRATION_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate changelog entry using LLM
|
||||||
|
print_info "Generating changelog entry..."
|
||||||
|
if cd hindsight-dev && uv run generate-changelog "$VERSION" --integration "$INTEGRATION"; then
|
||||||
|
cd ..
|
||||||
|
print_info "Changelog generated"
|
||||||
|
else
|
||||||
|
cd ..
|
||||||
|
print_error "Changelog generation failed"
|
||||||
|
git checkout .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit version bump + changelog together
|
||||||
|
print_info "Committing changes..."
|
||||||
|
git add "hindsight-integrations/$INTEGRATION/" "hindsight-docs/src/pages/changelog/integrations/$INTEGRATION.md"
|
||||||
|
git commit --no-verify -m "release($INTEGRATION): v$VERSION"
|
||||||
|
|
||||||
|
# Create annotated tag
|
||||||
|
print_info "Creating tag $TAG..."
|
||||||
|
git tag -a "$TAG" -m "Release $INTEGRATION v$VERSION"
|
||||||
|
|
||||||
|
# Push commit and tag
|
||||||
|
print_info "Pushing to remote..."
|
||||||
|
git push origin "$CURRENT_BRANCH"
|
||||||
|
git push origin "$TAG"
|
||||||
|
|
||||||
|
print_info "✅ Released $INTEGRATION v$VERSION"
|
||||||
|
print_info "Tag: $TAG"
|
||||||
|
print_info "GitHub Actions will publish to the package registry."
|
||||||
|
|
@ -64,8 +64,8 @@ fi
|
||||||
|
|
||||||
print_info "Updating version in all components..."
|
print_info "Updating version in all components..."
|
||||||
|
|
||||||
# Update Python packages
|
# Update Python packages (integrations are versioned independently via scripts/release-integration.sh)
|
||||||
PYTHON_PACKAGES=("hindsight-api" "hindsight-api-slim" "hindsight-all-slim" "hindsight-dev" "hindsight-all" "hindsight-integrations/litellm" "hindsight-integrations/crewai" "hindsight-integrations/pydantic-ai" "hindsight-integrations/hermes" "hindsight-integrations/agno" "hindsight-embed")
|
PYTHON_PACKAGES=("hindsight-api" "hindsight-api-slim" "hindsight-all-slim" "hindsight-dev" "hindsight-all" "hindsight-embed")
|
||||||
for package in "${PYTHON_PACKAGES[@]}"; do
|
for package in "${PYTHON_PACKAGES[@]}"; do
|
||||||
PYPROJECT_FILE="$package/pyproject.toml"
|
PYPROJECT_FILE="$package/pyproject.toml"
|
||||||
if [ -f "$PYPROJECT_FILE" ]; then
|
if [ -f "$PYPROJECT_FILE" ]; then
|
||||||
|
|
@ -144,36 +144,6 @@ else
|
||||||
print_warn "File $TYPESCRIPT_CLIENT_PKG not found, skipping"
|
print_warn "File $TYPESCRIPT_CLIENT_PKG not found, skipping"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update OpenClaw integration
|
|
||||||
OPENCLAW_PKG="hindsight-integrations/openclaw/package.json"
|
|
||||||
if [ -f "$OPENCLAW_PKG" ]; then
|
|
||||||
print_info "Updating $OPENCLAW_PKG"
|
|
||||||
sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$OPENCLAW_PKG"
|
|
||||||
rm "${OPENCLAW_PKG}.bak"
|
|
||||||
else
|
|
||||||
print_warn "File $OPENCLAW_PKG not found, skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update AI SDK integration
|
|
||||||
AI_SDK_PKG="hindsight-integrations/ai-sdk/package.json"
|
|
||||||
if [ -f "$AI_SDK_PKG" ]; then
|
|
||||||
print_info "Updating $AI_SDK_PKG"
|
|
||||||
sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$AI_SDK_PKG"
|
|
||||||
rm "${AI_SDK_PKG}.bak"
|
|
||||||
else
|
|
||||||
print_warn "File $AI_SDK_PKG not found, skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update Chat SDK integration
|
|
||||||
CHAT_SDK_PKG="hindsight-integrations/chat/package.json"
|
|
||||||
if [ -f "$CHAT_SDK_PKG" ]; then
|
|
||||||
print_info "Updating $CHAT_SDK_PKG"
|
|
||||||
sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$CHAT_SDK_PKG"
|
|
||||||
rm "${CHAT_SDK_PKG}.bak"
|
|
||||||
else
|
|
||||||
print_warn "File $CHAT_SDK_PKG not found, skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update documentation version (creates new version or syncs to existing)
|
# Update documentation version (creates new version or syncs to existing)
|
||||||
print_info "Updating documentation for version $VERSION..."
|
print_info "Updating documentation for version $VERSION..."
|
||||||
if [ -f "scripts/update-docs-version.sh" ]; then
|
if [ -f "scripts/update-docs-version.sh" ]; then
|
||||||
|
|
@ -216,14 +186,11 @@ COMMIT_MSG="Release v$VERSION
|
||||||
|
|
||||||
- Update version to $VERSION in all components
|
- Update version to $VERSION in all components
|
||||||
- Regenerate OpenAPI spec and client SDKs
|
- Regenerate OpenAPI spec and client SDKs
|
||||||
- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-litellm, hindsight-crewai, hindsight-pydantic-ai, hindsight-hermes, hindsight-agno, hindsight-embed
|
- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-embed
|
||||||
- Python client: hindsight-clients/python
|
- Python client: hindsight-clients/python
|
||||||
- TypeScript client: hindsight-clients/typescript
|
- TypeScript client: hindsight-clients/typescript
|
||||||
- Rust CLI: hindsight-cli
|
- Rust CLI: hindsight-cli
|
||||||
- Control Plane: hindsight-control-plane
|
- Control Plane: hindsight-control-plane
|
||||||
- OpenClaw integration: hindsight-integrations/openclaw
|
|
||||||
- AI SDK integration: hindsight-integrations/ai-sdk
|
|
||||||
- Chat SDK integration: hindsight-integrations/chat
|
|
||||||
- Helm chart"
|
- Helm chart"
|
||||||
|
|
||||||
# Add docs update note
|
# Add docs update note
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue