backspin 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backspin-0.5.1/.gitattributes +4 -0
- backspin-0.5.1/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- backspin-0.5.1/.github/ISSUE_TEMPLATE/config.yml +5 -0
- backspin-0.5.1/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- backspin-0.5.1/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- backspin-0.5.1/.github/dependabot.yml +19 -0
- backspin-0.5.1/.github/workflows/ci.yml +82 -0
- backspin-0.5.1/.github/workflows/release.yml +60 -0
- backspin-0.5.1/.gitignore +19 -0
- backspin-0.5.1/.pre-commit-config.yaml +14 -0
- backspin-0.5.1/CHANGELOG.md +161 -0
- backspin-0.5.1/CODE_OF_CONDUCT.md +65 -0
- backspin-0.5.1/CONTRIBUTING.md +47 -0
- backspin-0.5.1/LICENSE +21 -0
- backspin-0.5.1/PKG-INFO +308 -0
- backspin-0.5.1/README.md +262 -0
- backspin-0.5.1/README.zh-CN.md +251 -0
- backspin-0.5.1/RELEASE.md +62 -0
- backspin-0.5.1/SECURITY.md +29 -0
- backspin-0.5.1/backspin/__init__.py +38 -0
- backspin-0.5.1/backspin/cli.py +379 -0
- backspin-0.5.1/backspin/cost.py +85 -0
- backspin-0.5.1/backspin/diff.py +121 -0
- backspin-0.5.1/backspin/export.py +71 -0
- backspin-0.5.1/backspin/fakes.py +151 -0
- backspin-0.5.1/backspin/integrations/__init__.py +0 -0
- backspin-0.5.1/backspin/integrations/anthropic.py +279 -0
- backspin-0.5.1/backspin/integrations/openai.py +298 -0
- backspin-0.5.1/backspin/proxy.py +367 -0
- backspin-0.5.1/backspin/py.typed +0 -0
- backspin-0.5.1/backspin/pytest_plugin.py +83 -0
- backspin-0.5.1/backspin/recorder.py +346 -0
- backspin-0.5.1/backspin/redaction.py +49 -0
- backspin-0.5.1/backspin/replay.py +327 -0
- backspin-0.5.1/backspin/runfile.py +182 -0
- backspin-0.5.1/backspin/server.py +91 -0
- backspin-0.5.1/backspin/share.py +58 -0
- backspin-0.5.1/backspin/testing.py +121 -0
- backspin-0.5.1/backspin/tui.py +130 -0
- backspin-0.5.1/backspin/ui/app.js +327 -0
- backspin-0.5.1/backspin/ui/i18n.js +75 -0
- backspin-0.5.1/backspin/ui/index.html +68 -0
- backspin-0.5.1/backspin/ui/style.css +214 -0
- backspin-0.5.1/docs/architecture.md +69 -0
- backspin-0.5.1/docs/format-spec.md +106 -0
- backspin-0.5.1/docs/ui-diff.png +0 -0
- backspin-0.5.1/docs/ui-timeline.png +0 -0
- backspin-0.5.1/examples/live_agent.py +36 -0
- backspin-0.5.1/examples/mock_agent.py +73 -0
- backspin-0.5.1/examples/what_if.py +80 -0
- backspin-0.5.1/pyproject.toml +124 -0
- backspin-0.5.1/sdks/typescript/LICENSE +21 -0
- backspin-0.5.1/sdks/typescript/README.md +34 -0
- backspin-0.5.1/sdks/typescript/package-lock.json +51 -0
- backspin-0.5.1/sdks/typescript/package.json +40 -0
- backspin-0.5.1/sdks/typescript/src/capture.ts +158 -0
- backspin-0.5.1/sdks/typescript/src/diff.ts +87 -0
- backspin-0.5.1/sdks/typescript/src/index.ts +13 -0
- backspin-0.5.1/sdks/typescript/src/recorder.ts +159 -0
- backspin-0.5.1/sdks/typescript/src/redaction.ts +41 -0
- backspin-0.5.1/sdks/typescript/src/replay.ts +80 -0
- backspin-0.5.1/sdks/typescript/src/runfile.ts +117 -0
- backspin-0.5.1/sdks/typescript/src/test/sdk.test.ts +258 -0
- backspin-0.5.1/sdks/typescript/tsconfig.build.json +5 -0
- backspin-0.5.1/sdks/typescript/tsconfig.json +14 -0
- backspin-0.5.1/tests/__init__.py +0 -0
- backspin-0.5.1/tests/conftest.py +25 -0
- backspin-0.5.1/tests/mock_anthropic_server.py +108 -0
- backspin-0.5.1/tests/mock_openai_server.py +157 -0
- backspin-0.5.1/tests/test_anthropic_integration.py +159 -0
- backspin-0.5.1/tests/test_branch.py +108 -0
- backspin-0.5.1/tests/test_branch_agent.py +86 -0
- backspin-0.5.1/tests/test_cli.py +106 -0
- backspin-0.5.1/tests/test_cost.py +55 -0
- backspin-0.5.1/tests/test_diff.py +54 -0
- backspin-0.5.1/tests/test_edge_cases.py +139 -0
- backspin-0.5.1/tests/test_export.py +68 -0
- backspin-0.5.1/tests/test_openai_capture.py +100 -0
- backspin-0.5.1/tests/test_openai_integration.py +212 -0
- backspin-0.5.1/tests/test_performance.py +47 -0
- backspin-0.5.1/tests/test_proxy.py +201 -0
- backspin-0.5.1/tests/test_pytest_plugin.py +48 -0
- backspin-0.5.1/tests/test_recorder.py +131 -0
- backspin-0.5.1/tests/test_redaction.py +110 -0
- backspin-0.5.1/tests/test_replay.py +103 -0
- backspin-0.5.1/tests/test_runfile.py +64 -0
- backspin-0.5.1/tests/test_server.py +82 -0
- backspin-0.5.1/tests/test_share.py +48 -0
- backspin-0.5.1/tests/test_spans.py +98 -0
- backspin-0.5.1/tests/test_tui.py +61 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something recorded / replayed / diffed wrong
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**What happened?** A clear description of the wrong behavior.
|
|
8
|
+
|
|
9
|
+
**Minimal reproduction**
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# the smallest agent snippet that shows it
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**The run file.** backspin is built so a bug report can be a `*.backspin.jsonl`
|
|
16
|
+
file — attach it when you can (redact first if it contains sensitive content,
|
|
17
|
+
see the README's redaction section). If not, include:
|
|
18
|
+
|
|
19
|
+
- backspin version (`pip show backspin`)
|
|
20
|
+
- Python version, OS
|
|
21
|
+
- how the run was captured: `capture_openai` / `capture_anthropic` / proxy / TS SDK
|
|
22
|
+
- provider and protocol (OpenAI-compatible / Anthropic Messages)
|
|
23
|
+
|
|
24
|
+
**What did you expect?**
|
|
25
|
+
|
|
26
|
+
**Anything else?** Tracebacks, warnings (`ReplayMismatchWarning`?), screenshots of the viewer.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest a capability for the recorder / replay / diff workflow
|
|
4
|
+
labels: enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Your use case.** What are you trying to debug that backspin doesn't help with today?
|
|
8
|
+
|
|
9
|
+
**Proposed solution.** What should it do? An API sketch is welcome:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# e.g. rec.capture_x(...) / backspin some-command ...
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Alternatives you considered.** Cloud tools, manual logging, etc.
|
|
16
|
+
|
|
17
|
+
**Does it fit the project's scope?** backspin stays local-first, file-based,
|
|
18
|
+
and dependency-free at its core — features that need a server or an account
|
|
19
|
+
belong elsewhere.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## What & why
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences: what changes, and why a user would care. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] `pytest` green locally (including integration + performance suites)
|
|
8
|
+
- [ ] `ruff check backspin/ tests/` clean
|
|
9
|
+
- [ ] `mypy backspin/` clean
|
|
10
|
+
- [ ] Behavior change has a test (run-format changes need a round-trip test)
|
|
11
|
+
- [ ] `CHANGELOG.md` updated under an "Unreleased" heading
|
|
12
|
+
- [ ] No new core dependencies (`backspin/` stays stdlib-only outside extras)
|
|
13
|
+
|
|
14
|
+
<!-- If this touches the run file format: SCHEMA_VERSION bumped + load_run
|
|
15
|
+
still reads old files. -->
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: pip
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
groups:
|
|
8
|
+
python-dev-deps:
|
|
9
|
+
applies-to: version-updates
|
|
10
|
+
patterns:
|
|
11
|
+
- "*"
|
|
12
|
+
- package-ecosystem: npm
|
|
13
|
+
directory: "/sdks/typescript"
|
|
14
|
+
schedule:
|
|
15
|
+
interval: weekly
|
|
16
|
+
- package-ecosystem: github-actions
|
|
17
|
+
directory: "/"
|
|
18
|
+
schedule:
|
|
19
|
+
interval: monthly
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 10
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
cache: pip
|
|
22
|
+
- run: pip install ruff
|
|
23
|
+
- run: ruff check backspin/ tests/
|
|
24
|
+
|
|
25
|
+
typecheck:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
timeout-minutes: 10
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.12"
|
|
33
|
+
cache: pip
|
|
34
|
+
- run: pip install -e ".[dev]"
|
|
35
|
+
- run: mypy backspin/
|
|
36
|
+
|
|
37
|
+
test:
|
|
38
|
+
strategy:
|
|
39
|
+
fail-fast: false
|
|
40
|
+
matrix:
|
|
41
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
42
|
+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
43
|
+
exclude:
|
|
44
|
+
- os: windows-latest
|
|
45
|
+
python: "3.9"
|
|
46
|
+
- os: windows-latest
|
|
47
|
+
python: "3.10"
|
|
48
|
+
- os: macos-latest
|
|
49
|
+
python: "3.9"
|
|
50
|
+
runs-on: ${{ matrix.os }}
|
|
51
|
+
timeout-minutes: 20
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v4
|
|
54
|
+
- uses: actions/setup-python@v5
|
|
55
|
+
with:
|
|
56
|
+
python-version: ${{ matrix.python }}
|
|
57
|
+
cache: pip
|
|
58
|
+
- run: pip install -e ".[dev]"
|
|
59
|
+
# 3.9 skips the anthropic SDK (dropped 3.9), which drags total
|
|
60
|
+
# coverage below the normal gate; relax just that leg.
|
|
61
|
+
- run: >
|
|
62
|
+
pytest -q --cov=backspin --cov-report=xml --cov-report=term
|
|
63
|
+
${{ matrix.python == '3.9' && '--cov-fail-under=60' || '' }}
|
|
64
|
+
|
|
65
|
+
typescript:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
timeout-minutes: 10
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- uses: actions/setup-node@v4
|
|
71
|
+
with:
|
|
72
|
+
node-version: "20"
|
|
73
|
+
- working-directory: sdks/typescript
|
|
74
|
+
run: |
|
|
75
|
+
npm ci
|
|
76
|
+
npm test
|
|
77
|
+
# the published tarball must not ship tests and must typecheck clean
|
|
78
|
+
- working-directory: sdks/typescript
|
|
79
|
+
run: |
|
|
80
|
+
rm -rf dist
|
|
81
|
+
npm run build
|
|
82
|
+
test ! -d dist/test
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Push a tag `vX.Y.Z` to publish. PyPI uses trusted publishing (no token):
|
|
4
|
+
# configure the "backspin" project on pypi.org once —
|
|
5
|
+
# Settings > Publishing > add GitHub publisher (repo: zaibuchihuoji/backspin,
|
|
6
|
+
# workflow: release.yml, environment: pypi).
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # PyPI trusted publishing (OIDC)
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build-python:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
timeout-minutes: 15
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
cache: pip
|
|
24
|
+
- run: pip install -e ".[dev]" build
|
|
25
|
+
- run: ruff check backspin/ tests/
|
|
26
|
+
- run: mypy backspin/
|
|
27
|
+
- run: pytest -q
|
|
28
|
+
- run: python -m build
|
|
29
|
+
- uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
publish-pypi:
|
|
35
|
+
needs: build-python
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
environment: pypi
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/download-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
- name: Publish to PyPI
|
|
44
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
45
|
+
|
|
46
|
+
build-typescript:
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
timeout-minutes: 10
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
- uses: actions/setup-node@v4
|
|
52
|
+
with:
|
|
53
|
+
node-version: "20"
|
|
54
|
+
- working-directory: sdks/typescript
|
|
55
|
+
run: |
|
|
56
|
+
npm ci
|
|
57
|
+
npm test
|
|
58
|
+
npm run build
|
|
59
|
+
# npm publish stays manual until an NPM_TOKEN secret is configured:
|
|
60
|
+
# npm publish --access public (from sdks/typescript, on the tag)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
runs/
|
|
13
|
+
*.backspin.jsonl
|
|
14
|
+
node_modules/
|
|
15
|
+
sdks/typescript/dist/
|
|
16
|
+
.coverage
|
|
17
|
+
coverage.xml
|
|
18
|
+
.mimosa/
|
|
19
|
+
.DS_Store
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.16.5
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
8
|
+
rev: v5.0.0
|
|
9
|
+
hooks:
|
|
10
|
+
- id: end-of-file-fixer
|
|
11
|
+
- id: trailing-whitespace
|
|
12
|
+
- id: check-json
|
|
13
|
+
- id: check-toml
|
|
14
|
+
- id: check-merge-conflict
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to backspin are documented here.
|
|
4
|
+
|
|
5
|
+
## 0.5.1 — 2026-08-29
|
|
6
|
+
|
|
7
|
+
Hardening release: engineering-quality fixes found in a full project audit.
|
|
8
|
+
|
|
9
|
+
- **TS SDK: async tools recorded correctly** — `Recorder.tool()` now awaits
|
|
10
|
+
async functions, recording the resolved value (previously a Promise, which
|
|
11
|
+
serialized to `{}`) with the real duration; async rejections are recorded
|
|
12
|
+
with their error. A stream the consumer abandons mid-loop is now still
|
|
13
|
+
recorded (`finalize` runs in `finally`). `diffRuns` no longer falls back to
|
|
14
|
+
`"[object Object]"` for fingerprint-less events.
|
|
15
|
+
- **TS SDK: redaction parity** — `redactStrings(transform)` / `mask(regex)`
|
|
16
|
+
mirror the Python API; `Recorder({ redact })` keeps secrets out of run
|
|
17
|
+
files while structural fields stay readable. npm packaging hardened
|
|
18
|
+
(`files`, `exports`, `engines`, `prepublishOnly`, SDK LICENSE/README,
|
|
19
|
+
tests excluded from the tarball). Version sync: Python and TS SDK both
|
|
20
|
+
0.5.1.
|
|
21
|
+
- **Recorder survives disk failures** — a run file that becomes unwritable
|
|
22
|
+
(disk full, closed handle) stops recording with one `RuntimeWarning`
|
|
23
|
+
instead of raising into instrumented agent code (a stated ground rule).
|
|
24
|
+
- **Stream context managers no longer swallow exceptions** — `__exit__` in
|
|
25
|
+
both OpenAI and Anthropic stream wrappers returned from a `finally`
|
|
26
|
+
block, which could discard in-flight exceptions; rewritten.
|
|
27
|
+
- **Proxy hardening** — non-object JSON bodies return a clean 400 instead
|
|
28
|
+
of an unhandled 500 (both `/v1/chat/completions` and `/v1/messages`).
|
|
29
|
+
- **Viewer API** — a well-formed but missing run name returns 404 instead
|
|
30
|
+
of a 500; corrupt runs still return 422.
|
|
31
|
+
- **Viewer UI** — language toggle re-renders in place (no page reload),
|
|
32
|
+
`title` attributes and `<html lang>` localize, cards escape by default,
|
|
33
|
+
run list / timeline rows / tabs are keyboard-operable (roles, tabindex,
|
|
34
|
+
Enter/Space, Esc closes the inspector, focus rings).
|
|
35
|
+
- **Tooling** — ruff (curated rule set) and mypy are clean across the
|
|
36
|
+
codebase and enforced in CI; fixed real findings (`share.py` missing
|
|
37
|
+
`Any` import, `replay.py` missing `Callable` import). pytest-cov wired
|
|
38
|
+
in. CI: lint + typecheck + test matrix (adds macOS and Python 3.13) +
|
|
39
|
+
a TypeScript job (tests + publish-shape check); release workflow builds
|
|
40
|
+
and publishes to PyPI via trusted publishing on `v*` tags.
|
|
41
|
+
- **Community/meta** — SECURITY.md (incl. the recordings-contain-secrets
|
|
42
|
+
threat model), CODE_OF_CONDUCT.md, issue templates, PR checklist,
|
|
43
|
+
Dependabot, pre-commit config, README badges (CI/PyPI/license/ruff),
|
|
44
|
+
SPDX license expression, PyPI URLs; RELEASE.md rewritten as a generic
|
|
45
|
+
maintainer handbook.
|
|
46
|
+
- Tests: Python 116 (was 107), TypeScript 10 (was 6).
|
|
47
|
+
|
|
48
|
+
## 0.5.0 — 2026-08-29
|
|
49
|
+
|
|
50
|
+
- **Agent-level what-if** — `branch_agent(fn, run, mutations)`: re-run the
|
|
51
|
+
actual agent function against a mutated cassette. Unlike `branch()`
|
|
52
|
+
(request-sequence replay), the full run shape — logs, tool calls, spans,
|
|
53
|
+
downstream requests — is regenerated for real, and `diff_runs` pins the
|
|
54
|
+
first divergence at the exact event where the mutation took effect.
|
|
55
|
+
`examples/what_if.py` demonstrates both levels.
|
|
56
|
+
|
|
57
|
+
## 0.4.1 — 2026-08-29
|
|
58
|
+
|
|
59
|
+
- **Viewer i18n** — the local viewer is now Chinese-first with an EN/中文
|
|
60
|
+
toggle in the header (choice persisted in localStorage); share files
|
|
61
|
+
inherit the same interface. README screenshots updated.
|
|
62
|
+
- `share` also inlines the new `i18n.js` asset.
|
|
63
|
+
|
|
64
|
+
## 0.4.0 — 2026-08-29
|
|
65
|
+
|
|
66
|
+
Multi-provider release: Anthropic natively, TypeScript SDK, export/share/TUI.
|
|
67
|
+
|
|
68
|
+
- **Anthropic native** — `rec.capture_anthropic(client)`: sync/async/
|
|
69
|
+
streaming capture of the Messages API (text + tool_use blocks), events
|
|
70
|
+
recorded with `provider="anthropic"` and usage normalized to
|
|
71
|
+
`prompt_tokens`/`completion_tokens` so costs/diffs work cross-provider.
|
|
72
|
+
`backspin proxy` gains a `/v1/messages` adapter (record + replay,
|
|
73
|
+
streaming included) — verified end-to-end against the real anthropic SDK.
|
|
74
|
+
- **TypeScript SDK** (`sdks/typescript`, `@backspin/sdk`) — Recorder with
|
|
75
|
+
AsyncLocalStorage spans, `captureOpenAI` (sync/async/streaming),
|
|
76
|
+
`Cassette`/`stubClient` replay, `diffRuns`. Same `.backspin.jsonl` run
|
|
77
|
+
format — Python recordings replay in TypeScript and vice versa. 6 node:test
|
|
78
|
+
suites.
|
|
79
|
+
- **Dataset export** — `backspin export <run> --format pairs|sft` turns
|
|
80
|
+
recordings into eval/fine-tune JSONL.
|
|
81
|
+
- **Single-file sharing** — `backspin share <run>` bundles the run + the
|
|
82
|
+
whole viewer into one HTML file; opens in any browser, zero install.
|
|
83
|
+
- **TUI** — `backspin tui`: keyboard-driven terminal viewer (runs → timeline
|
|
84
|
+
→ step JSON), injectable IO, fully unit-tested.
|
|
85
|
+
- Python suite: 104 tests; TypeScript suite: 6 tests.
|
|
86
|
+
|
|
87
|
+
## 0.3.0 — 2026-08-29
|
|
88
|
+
|
|
89
|
+
The "real debugger" release: what-if branching, zero-code proxy
|
|
90
|
+
integration, structured spans, and costs.
|
|
91
|
+
|
|
92
|
+
- **What-if branching** — `Cassette.mutate()` alters a recorded answer;
|
|
93
|
+
`backspin.replay.branch()` records the mutated replay as a new run
|
|
94
|
+
(`branch_of` metadata), rewriting downstream requests so mutations
|
|
95
|
+
propagate; `diff_runs(..., llm_only=True)` and
|
|
96
|
+
`backspin branch <file> --step N --content "..."` pinpoint where the
|
|
97
|
+
timelines split.
|
|
98
|
+
- **`backspin proxy`** — OpenAI-compatible local proxy in two modes:
|
|
99
|
+
*record* (forward to upstream, capture everything, streaming included —
|
|
100
|
+
zero-code integration for any framework/language) and *replay* (serve a
|
|
101
|
+
recorded run back as an API, no network). Authorization headers are
|
|
102
|
+
forwarded but never recorded.
|
|
103
|
+
- **Spans** — `with rec.span(name, meta=...)`: nested agent → tool →
|
|
104
|
+
sub-LLM structure. Events carry `span_id`/`depth`; async-safe via
|
|
105
|
+
contextvars (per-task stacks); exceptions recorded on the exit event;
|
|
106
|
+
span durations excluded from run totals to avoid double counting.
|
|
107
|
+
- **Costs** — built-in per-1M-token price table (OpenAI, Claude, Gemini,
|
|
108
|
+
DeepSeek; exact-then-longest-prefix matching), `cost_usd` /
|
|
109
|
+
`cost_complete` in run totals, cost card in the viewer, `~$` in `show`.
|
|
110
|
+
- **pytest plugin** — `backspin` fixture (pytest11 entry point) with
|
|
111
|
+
strict `assert_replays_identically()`: order-fallback replays are
|
|
112
|
+
failures, only fingerprint-exact replay passes.
|
|
113
|
+
- **Viewer** — span tree indentation, cost card.
|
|
114
|
+
- **Docs** — `docs/format-spec.md` (the run file specification with
|
|
115
|
+
versioning rules) and `docs/architecture.md`.
|
|
116
|
+
- Internal: diff signatures understand span events; `_usage_of` fixes
|
|
117
|
+
streamed usage extraction; stream wrappers gain context-manager/close
|
|
118
|
+
passthrough.
|
|
119
|
+
|
|
120
|
+
## 0.2.0 — 2026-08-29
|
|
121
|
+
|
|
122
|
+
Hardening release: the record → replay → diff → view loop is now verified
|
|
123
|
+
against the real OpenAI SDK and guarded by edge-case and performance tests.
|
|
124
|
+
|
|
125
|
+
- **Real-SDK integration suite** — a local OpenAI-compatible HTTP/SSE mock
|
|
126
|
+
server runs the genuine `openai` SDK through sync, async, streaming
|
|
127
|
+
(tool-call deltas, `include_usage`), context-manager and error paths.
|
|
128
|
+
Found and fixed two real bugs:
|
|
129
|
+
- async resource detection (modern SDKs ship async methods that are not
|
|
130
|
+
coroutine functions — detection now uses the resource type name)
|
|
131
|
+
- streamed `usage` chunks were dropped in stream reconstruction
|
|
132
|
+
- **Redaction API** — `Recorder(redact=...)` plus
|
|
133
|
+
`backspin.redaction.redact_strings` / `mask`. Structural fields (model,
|
|
134
|
+
tool name, fingerprint, durations) stay in clear; every other payload
|
|
135
|
+
value — including unknown custom keys — goes through the redactor.
|
|
136
|
+
Fingerprints are computed pre-redaction so replay matching is unaffected.
|
|
137
|
+
- **Streaming wrappers** now pass through `close()`, the async/with context
|
|
138
|
+
manager protocols and positional args.
|
|
139
|
+
- **Performance guards** — 20k-event record/load and 2×4000-event diff stay
|
|
140
|
+
in the seconds range.
|
|
141
|
+
- **Edge-case suite** — unicode round-trips, concurrent recorders,
|
|
142
|
+
thread-safe single recorder, writes after close, circular references,
|
|
143
|
+
empty cassettes, corrupt run files over the viewer API, CLI `--json`.
|
|
144
|
+
- **Packaging** — wheel verified to ship the viewer UI assets and
|
|
145
|
+
`py.typed`.
|
|
146
|
+
|
|
147
|
+
## 0.1.0 — 2026-08-29
|
|
148
|
+
|
|
149
|
+
Initial MVP.
|
|
150
|
+
|
|
151
|
+
- `Recorder`: single-file JSONL run format (`.backspin.jsonl`), header +
|
|
152
|
+
ordered steps, error capture with tracebacks, custom events
|
|
153
|
+
- OpenAI-shaped capture: sync / async / streaming, tool-call reconstruction
|
|
154
|
+
- `@rec.tool` decorator (sync + async)
|
|
155
|
+
- Deterministic replay: `Cassette`, `stub_client`, `patch_openai`
|
|
156
|
+
(fingerprint match, order fallback with warning)
|
|
157
|
+
- `diff_runs`: step alignment by signature, first-divergence detection
|
|
158
|
+
- CLI: `backspin ls / show / diff / ui`
|
|
159
|
+
- Local viewer: FastAPI + zero-build vanilla JS (waterfall timeline, step
|
|
160
|
+
inspector, side-by-side diff)
|
|
161
|
+
- `backspin.testing.FakeOpenAI` for keyless demos and tests
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
+
orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment include:
|
|
18
|
+
|
|
19
|
+
* Demonstrating empathy and kindness toward other people
|
|
20
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
21
|
+
* Giving and gracefully accepting constructive feedback
|
|
22
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
23
|
+
and learning from the experience
|
|
24
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
25
|
+
community
|
|
26
|
+
|
|
27
|
+
Examples of unacceptable behavior include:
|
|
28
|
+
|
|
29
|
+
* The use of sexualized language or imagery, and sexual attention or advances
|
|
30
|
+
of any kind
|
|
31
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
32
|
+
* Public or private harassment
|
|
33
|
+
* Publishing others' private information, such as a physical or email address,
|
|
34
|
+
without their explicit permission
|
|
35
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
36
|
+
professional setting
|
|
37
|
+
|
|
38
|
+
## Enforcement Responsibilities
|
|
39
|
+
|
|
40
|
+
Community leaders are responsible for clarifying and enforcing our standards
|
|
41
|
+
of acceptable behavior and will take appropriate and fair corrective action in
|
|
42
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
43
|
+
or harmful.
|
|
44
|
+
|
|
45
|
+
## Scope
|
|
46
|
+
|
|
47
|
+
This Code of Conduct applies within all community spaces, and also applies
|
|
48
|
+
when an individual is officially representing the community in public spaces.
|
|
49
|
+
|
|
50
|
+
## Enforcement
|
|
51
|
+
|
|
52
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
53
|
+
reported to the community leaders responsible for enforcement via a private
|
|
54
|
+
message to the maintainers or via [GitHub's private vulnerability/advisory
|
|
55
|
+
channel](https://github.com/zaibuchihuoji/backspin/security/advisories/new) if
|
|
56
|
+
private. All complaints will be reviewed and investigated promptly and fairly.
|
|
57
|
+
|
|
58
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
59
|
+
reporter of any incident.
|
|
60
|
+
|
|
61
|
+
## Attribution
|
|
62
|
+
|
|
63
|
+
This Code of Conduct is adapted from the [Contributor Covenant](
|
|
64
|
+
https://www.contributor-covenant.org), version 2.1, available at
|
|
65
|
+
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Contributing to backspin
|
|
2
|
+
|
|
3
|
+
Thanks for helping make agent debugging less painful.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone <your fork>
|
|
9
|
+
cd backspin
|
|
10
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
11
|
+
pip install -e ".[dev]"
|
|
12
|
+
pytest
|
|
13
|
+
ruff check backspin/ tests/ # must be clean
|
|
14
|
+
mypy backspin/ # must be clean
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`pytest` runs the full suite, including the real-SDK integration tests
|
|
18
|
+
(they talk to a local OpenAI-compatible mock server — no network, no API
|
|
19
|
+
key needed). `pip install pre-commit && pre-commit install` wires the same
|
|
20
|
+
lint into your commits.
|
|
21
|
+
|
|
22
|
+
## Ground rules
|
|
23
|
+
|
|
24
|
+
- **Core stays dependency-free.** `backspin/` (except the `ui` extra)
|
|
25
|
+
must import nothing beyond the standard library. If you need a library,
|
|
26
|
+
it belongs in an extra or in the test suite.
|
|
27
|
+
- **A recorder must never crash the agent.** Serialization problems degrade
|
|
28
|
+
to reprs; exceptions in recorded code are captured and re-raised. Keep it
|
|
29
|
+
that way.
|
|
30
|
+
- **Every behavior change needs a test.** If it touches the run format,
|
|
31
|
+
add a round-trip test (record → `load_run` → assert).
|
|
32
|
+
- **The run file format is a contract.** Bump `SCHEMA_VERSION` in
|
|
33
|
+
`runfile.py` and document the change in `CHANGELOG.md` if you must break
|
|
34
|
+
it — then keep `load_run` able to read old files.
|
|
35
|
+
|
|
36
|
+
## Style
|
|
37
|
+
|
|
38
|
+
- Type-hinted Python, `from __future__ import annotations`, 3.9+ compatible.
|
|
39
|
+
- ASCII-only CLI output (Windows consoles); unicode is fine in file content.
|
|
40
|
+
- Comments explain constraints, not mechanics.
|
|
41
|
+
|
|
42
|
+
## Submitting
|
|
43
|
+
|
|
44
|
+
1. Branch from `main`.
|
|
45
|
+
2. `pytest` green, `ruff check` and `mypy` clean — CI enforces all three.
|
|
46
|
+
3. Update `CHANGELOG.md` under an "Unreleased" heading.
|
|
47
|
+
4. Open a PR describing the user-visible change (the template has the checklist).
|
backspin-0.5.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 backspin contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|