Skip to content

Commit 13d3f54

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/pipecat-integration
# Conflicts: # .github/workflows/ci.yml
2 parents 4052b72 + 1e00254 commit 13d3f54

48 files changed

Lines changed: 4309 additions & 544 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
name: Bug Report
3+
about: Report a bug or unexpected behavior
4+
title: "[Bug] "
5+
labels: bug
6+
---
7+
8+
## Description
9+
A clear description of the bug.
10+
11+
## Steps to reproduce
12+
1.
13+
2.
14+
3.
15+
16+
## Expected behavior
17+
What you expected to happen.
18+
19+
## Actual behavior
20+
What actually happened (include the full error / traceback if there is one).
21+
22+
## Environment
23+
- floe-guard version (PyPI or npm):
24+
- Python / Node.js version:
25+
- Framework + version (CrewAI / LiteLLM / LangChain / OpenAI / Anthropic / `ai`):
26+
- OS:
27+
28+
## Additional context
29+
Any other context — e.g. the model id being priced, concurrency setup, or a
30+
minimal snippet.

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: true
2+
contact_links:
3+
- name: Security Vulnerability
4+
url: https://github.com/Floe-Labs/floe-guard/blob/main/SECURITY.md
5+
about: Report security issues privately via email — do NOT open a public issue
6+
- name: Floe Cookbook (runnable agent examples)
7+
url: https://github.com/Floe-Labs/floe-cookbook
8+
about: End-to-end reference agents built on hosted Floe
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
name: Feature Request
3+
about: Suggest a new feature, adapter, or improvement
4+
title: "[Feature] "
5+
labels: enhancement
6+
---
7+
8+
## Description
9+
A clear description of the feature you'd like.
10+
11+
## Use case
12+
Why do you need this? What problem does it solve?
13+
14+
## Proposed solution
15+
How you think it should work (if you have an idea). For a new framework
16+
adapter, note which framework/SDK and how it exposes token usage.
17+
18+
## Alternatives considered
19+
Any alternative solutions or workarounds you've tried.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Summary
2+
Brief description of what this PR does.
3+
4+
## Changes
5+
-
6+
7+
## Testing
8+
How was this tested?
9+
10+
- [ ] `pytest` passes (Python changes)
11+
- [ ] `ruff check .` and `ruff format .` are clean (Python changes)
12+
- [ ] `npm test` and `npm run typecheck` pass in `js/` (TypeScript changes)
13+
- [ ] Cost-map copies still match (`src/floe_guard/cost_map.json` == `js/src/cost_map.json`)
14+
- [ ] CHANGELOG.md updated (user-facing changes)
15+
16+
## Related issues
17+
Closes #

.github/workflows/ci.yml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ jobs:
6262
python-version: ${{ matrix.python-version }}
6363
- run: python -m pip install --upgrade pip
6464
# Install the framework extras so the adapter handler tests actually run
65-
# (instead of skipping). langchain-core covers the LangChain handler tests.
66-
- run: pip install -e ".[dev,langchain]"
65+
# (instead of skipping). langchain-core covers the LangChain handler
66+
# tests; litellm covers the callback-swallowing regression tests (the
67+
# CrewAI adapter tests stub crewai itself, so the heavy extra stays out).
68+
- run: pip install -e ".[dev,langchain,litellm]"
6769
# pipecat-ai requires Python >=3.11, so add it (and exercise the Pipecat
6870
# adapter tests) on every matrix cell except 3.10 — there the tests
6971
# importorskip cleanly.
@@ -72,6 +74,29 @@ jobs:
7274
- name: Pytest
7375
run: pytest -q
7476

77+
test-js:
78+
# The npm package (Vercel AI SDK middleware) lives in js/. Run its build,
79+
# typecheck, and tests on every PR — publish-npm.yml re-runs them at publish
80+
# time, but that's too late to catch a broken PR.
81+
runs-on: ubuntu-latest
82+
timeout-minutes: 10
83+
defaults:
84+
run:
85+
working-directory: js
86+
steps:
87+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
88+
with:
89+
persist-credentials: false
90+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
91+
with:
92+
node-version: '22'
93+
cache: npm
94+
cache-dependency-path: js/package-lock.json
95+
- run: npm ci
96+
- run: npm run build
97+
- run: npm run typecheck
98+
- run: npm test
99+
75100
test-bare:
76101
# Locks the dependency-free contract: the package and its test suite must
77102
# work with NO framework extra installed (handler tests skip cleanly, no

.github/workflows/publish-npm.yml

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
name: Publish to npm
22

3+
# Runs after the full CI workflow succeeds on main (not on the raw push), so a
4+
# red lint/cost-map-sync/python job blocks an npm release too. The old js/**
5+
# path filter is gone; the version gate below already makes no-op runs cheap.
36
on:
4-
push:
7+
workflow_run:
8+
workflows: [CI]
9+
types: [completed]
510
branches: [main]
6-
# The npm package lives in js/; only publish when it (or this workflow) changes.
7-
paths:
8-
- 'js/**'
9-
- '.github/workflows/publish-npm.yml'
1011
workflow_dispatch:
1112

1213
concurrency:
1314
group: publish-npm-${{ github.ref }}
1415
cancel-in-progress: false
1516

1617
permissions:
17-
contents: read
18+
contents: write # push the release tag; npm auth stays the token secret
1819

1920
jobs:
2021
publish:
21-
if: github.ref == 'refs/heads/main'
22+
# workflow_run's `branches` filter matches the HEAD branch NAME of the
23+
# triggering run — a fork PR from a branch named "main" would match it. The
24+
# event/head_repository checks restrict publishing to push-triggered CI on
25+
# this repo's own main.
26+
if: >-
27+
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
28+
(github.event_name == 'workflow_run' &&
29+
github.event.workflow_run.conclusion == 'success' &&
30+
github.event.workflow_run.event == 'push' &&
31+
github.event.workflow_run.head_repository.full_name == github.repository)
2232
runs-on: ubuntu-latest
2333
timeout-minutes: 15
2434
defaults:
@@ -28,6 +38,9 @@ jobs:
2838
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
2939
with:
3040
persist-credentials: false
41+
# workflow_run checks out the default branch by default; pin to the
42+
# exact commit CI validated.
43+
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
3144

3245
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
3346
with:
@@ -69,3 +82,16 @@ jobs:
6982
run: npm publish --access public
7083
env:
7184
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
85+
86+
- name: Tag the release
87+
# Maps the published artifact back to its exact commit ("js-" prefix:
88+
# the PyPI package versions independently and tags "py-v<version>").
89+
if: steps.check.outputs.should_publish == 'true'
90+
env:
91+
GH_TOKEN: ${{ github.token }}
92+
PKG_VERSION: ${{ steps.check.outputs.version }}
93+
run: |
94+
gh api "repos/${{ github.repository }}/git/refs" \
95+
-f ref="refs/tags/js-v$PKG_VERSION" \
96+
-f sha="$(git rev-parse HEAD)" \
97+
|| echo "::warning::tag js-v$PKG_VERSION already exists or could not be created"

.github/workflows/publish.yml

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
name: Publish to PyPI
22

3+
# Runs after the full CI workflow (lint, python matrix, cost-map-sync, js)
4+
# succeeds on main — not on the raw push. npm 0.2.0 once shipped while main CI
5+
# was red on cost-map-sync; gating on workflow_run closes that hole.
36
on:
4-
push:
7+
workflow_run:
8+
workflows: [CI]
9+
types: [completed]
510
branches: [main]
611
workflow_dispatch:
712

@@ -10,17 +15,29 @@ concurrency:
1015
cancel-in-progress: false
1116

1217
permissions:
13-
contents: read
18+
contents: write # push the release tag; PyPI auth stays the token secret
1419

1520
jobs:
1621
publish:
17-
if: github.ref == 'refs/heads/main'
22+
# workflow_run's `branches` filter matches the HEAD branch NAME of the
23+
# triggering run — a fork PR from a branch named "main" would match it. The
24+
# event/head_repository checks restrict publishing to push-triggered CI on
25+
# this repo's own main.
26+
if: >-
27+
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
28+
(github.event_name == 'workflow_run' &&
29+
github.event.workflow_run.conclusion == 'success' &&
30+
github.event.workflow_run.event == 'push' &&
31+
github.event.workflow_run.head_repository.full_name == github.repository)
1832
runs-on: ubuntu-latest
1933
timeout-minutes: 15
2034
steps:
2135
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
2236
with:
2337
persist-credentials: false
38+
# workflow_run checks out the default branch by default; pin to the
39+
# exact commit CI validated.
40+
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
2441

2542
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
2643
with:
@@ -77,3 +94,16 @@ jobs:
7794
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
7895
with:
7996
password: ${{ secrets.PYPI_API_TOKEN }}
97+
98+
- name: Tag the release
99+
# Maps the published artifact back to its exact commit ("py-" prefix:
100+
# the npm package versions independently and tags "js-v<version>").
101+
if: steps.check.outputs.should_publish == 'true'
102+
env:
103+
GH_TOKEN: ${{ github.token }}
104+
PKG_VERSION: ${{ steps.meta.outputs.version }}
105+
run: |
106+
gh api "repos/${{ github.repository }}/git/refs" \
107+
-f ref="refs/tags/py-v$PKG_VERSION" \
108+
-f sha="$(git rev-parse HEAD)" \
109+
|| echo "::warning::tag py-v$PKG_VERSION already exists or could not be created"
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Version Guard
2+
3+
# Publishing is version-gated on main, so a source change merged without a
4+
# version bump silently never ships — that is exactly how PyPI fell 3.5 weeks
5+
# behind the repo. Fail the PR instead, unless the change is deliberately
6+
# unreleased (apply the skip-version-guard label).
7+
#
8+
# Lives in its own workflow (not ci.yml) so the labeled/unlabeled trigger types
9+
# — needed for the skip label to re-evaluate without a manual re-run — don't
10+
# re-run and cancel the whole CI matrix on every label change.
11+
on:
12+
pull_request:
13+
types: [opened, synchronize, reopened, labeled, unlabeled]
14+
15+
concurrency:
16+
group: version-guard-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
permissions:
20+
contents: read
21+
22+
jobs:
23+
version-guard:
24+
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-version-guard') }}
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 5
27+
steps:
28+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
29+
with:
30+
persist-credentials: false
31+
# The checkout is refs/pull/N/merge — a synthetic merge commit whose
32+
# first parent is the CURRENT base tip. Depth 2 makes HEAD^1
33+
# resolvable; comparing against it (rather than the event's
34+
# pull_request.base.sha snapshot, which goes stale as the base moves)
35+
# means a release merged to main after the PR opened still counts.
36+
fetch-depth: 2
37+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
38+
with:
39+
python-version: '3.13'
40+
- name: Require a version bump when package source changes
41+
run: |
42+
set -euo pipefail
43+
changed=$(git diff --name-only HEAD^1 HEAD)
44+
fail=0
45+
46+
# pyproject.toml is included: dependency/extras changes alter the
47+
# shipped artifact just like src changes do.
48+
if echo "$changed" | grep -q '^\(src/floe_guard/\|pyproject\.toml\)'; then
49+
old=$(git show "HEAD^1:pyproject.toml" | python -c "import sys,tomllib;print(tomllib.loads(sys.stdin.read())['project']['version'])")
50+
new=$(python -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
51+
if [ "$old" = "$new" ]; then
52+
echo "::error::Python package source or packaging metadata changed but pyproject.toml version is still $new — bump it (or label the PR skip-version-guard)."
53+
fail=1
54+
fi
55+
fi
56+
57+
if echo "$changed" | grep -q '^js/\(src/\|package\.json\)'; then
58+
old=$(git show "HEAD^1:js/package.json" | python -c "import sys,json;print(json.loads(sys.stdin.read())['version'])")
59+
new=$(python -c "import json;print(json.load(open('js/package.json'))['version'])")
60+
if [ "$old" = "$new" ]; then
61+
echo "::error::js package source or packaging metadata changed but js/package.json version is still $new — bump it (or label the PR skip-version-guard)."
62+
fail=1
63+
fi
64+
fi
65+
66+
exit "$fail"

0 commit comments

Comments
 (0)