matrx-connect 0.1.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.
- matrx_connect-0.1.1/.gitignore +271 -0
- matrx_connect-0.1.1/CLAUDE.md +218 -0
- matrx_connect-0.1.1/PKG-INFO +118 -0
- matrx_connect-0.1.1/README.md +89 -0
- matrx_connect-0.1.1/matrx_connect/__init__.py +40 -0
- matrx_connect-0.1.1/matrx_connect/chat_timing.py +494 -0
- matrx_connect-0.1.1/matrx_connect/context/__init__.py +286 -0
- matrx_connect-0.1.1/matrx_connect/context/app_context.py +488 -0
- matrx_connect-0.1.1/matrx_connect/context/data_types.py +1407 -0
- matrx_connect-0.1.1/matrx_connect/context/emitter_protocol.py +101 -0
- matrx_connect-0.1.1/matrx_connect/context/events.py +766 -0
- matrx_connect-0.1.1/matrx_connect/context/media_block.py +462 -0
- matrx_connect-0.1.1/matrx_connect/context/operations.py +200 -0
- matrx_connect-0.1.1/matrx_connect/context/phases.py +32 -0
- matrx_connect-0.1.1/matrx_connect/context/render_blocks.py +145 -0
- matrx_connect-0.1.1/matrx_connect/context/tool_event_data.py +116 -0
- matrx_connect-0.1.1/matrx_connect/dependencies.py +59 -0
- matrx_connect-0.1.1/matrx_connect/emitters/__init__.py +5 -0
- matrx_connect-0.1.1/matrx_connect/emitters/console_emitter.py +353 -0
- matrx_connect-0.1.1/matrx_connect/emitters/log_truncation.py +24 -0
- matrx_connect-0.1.1/matrx_connect/emitters/silent_emitter.py +130 -0
- matrx_connect-0.1.1/matrx_connect/emitters/stream_emitter.py +677 -0
- matrx_connect-0.1.1/matrx_connect/lane/__init__.py +45 -0
- matrx_connect-0.1.1/matrx_connect/lane/buckets.py +71 -0
- matrx_connect-0.1.1/matrx_connect/lane/contextvar.py +41 -0
- matrx_connect-0.1.1/matrx_connect/lane/core.py +725 -0
- matrx_connect-0.1.1/matrx_connect/lane/priorities.py +42 -0
- matrx_connect-0.1.1/matrx_connect/middleware/__init__.py +3 -0
- matrx_connect-0.1.1/matrx_connect/middleware/auth.py +744 -0
- matrx_connect-0.1.1/matrx_connect/request_controls.py +83 -0
- matrx_connect-0.1.1/matrx_connect/request_latency.py +52 -0
- matrx_connect-0.1.1/matrx_connect/reservations.py +256 -0
- matrx_connect-0.1.1/matrx_connect/service_auth.py +126 -0
- matrx_connect-0.1.1/matrx_connect/streaming/__init__.py +25 -0
- matrx_connect-0.1.1/matrx_connect/streaming/error_capture.py +69 -0
- matrx_connect-0.1.1/matrx_connect/streaming/primitives.py +118 -0
- matrx_connect-0.1.1/matrx_connect/streaming/response.py +647 -0
- matrx_connect-0.1.1/pyproject.toml +61 -0
- matrx_connect-0.1.1/scripts/release.sh +5 -0
- matrx_connect-0.1.1/tests/__init__.py +0 -0
- matrx_connect-0.1.1/tests/test_auth_middleware.py +889 -0
- matrx_connect-0.1.1/tests/test_chat_timing.py +187 -0
- matrx_connect-0.1.1/tests/test_error_capture.py +80 -0
- matrx_connect-0.1.1/tests/test_lane.py +343 -0
- matrx_connect-0.1.1/tests/test_log_truncation.py +28 -0
- matrx_connect-0.1.1/tests/test_media_block_no_storage_uri.py +57 -0
- matrx_connect-0.1.1/tests/test_prepared_lane_finalization.py +188 -0
- matrx_connect-0.1.1/tests/test_provider_retry_event.py +56 -0
- matrx_connect-0.1.1/tests/test_stream_emitter_logging.py +65 -0
- matrx_connect-0.1.1/tests/test_structured_output_event.py +82 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
*.pyc
|
|
2
|
+
secrets/
|
|
3
|
+
ignore/
|
|
4
|
+
temp/
|
|
5
|
+
logs/
|
|
6
|
+
# The broad `logs/` rule above is for RUNTIME log output, but it also matched
|
|
7
|
+
# the dashboard's SOURCE directory and silently swallowed an entire feature's
|
|
8
|
+
# files (only the pre-existing index.tsx stayed tracked), breaking the prod
|
|
9
|
+
# Docker build with "Could not resolve ./structured-tab". Re-include the source.
|
|
10
|
+
!apps/dashboard/src/features/logs/
|
|
11
|
+
!apps/dashboard/src/features/logs/**
|
|
12
|
+
todo
|
|
13
|
+
text_notes/
|
|
14
|
+
aidream/secrets/2.env
|
|
15
|
+
automation_matrix/matrix_processing/temp/*
|
|
16
|
+
cd
|
|
17
|
+
# Byte-compiled / optimized / DLL files
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[cod]
|
|
20
|
+
*$py.class
|
|
21
|
+
|
|
22
|
+
# C extensions
|
|
23
|
+
*.so
|
|
24
|
+
.venv/
|
|
25
|
+
|
|
26
|
+
# Distribution / packaging
|
|
27
|
+
.Python
|
|
28
|
+
build/
|
|
29
|
+
develop-eggs/
|
|
30
|
+
dist/
|
|
31
|
+
downloads/
|
|
32
|
+
eggs/
|
|
33
|
+
.eggs/
|
|
34
|
+
lib/
|
|
35
|
+
lib64/
|
|
36
|
+
# The blanket lib/ rule above is from the standard Python .gitignore template
|
|
37
|
+
# and was silently swallowing TS source under the SPA `src/lib/` folders.
|
|
38
|
+
# Re-allow them explicitly so frontend builds don't ship without their lib layer.
|
|
39
|
+
!apps/dashboard/src/lib/
|
|
40
|
+
!apps/dashboard/src/lib/**
|
|
41
|
+
!apps/workflow-studio/src/lib/
|
|
42
|
+
!apps/workflow-studio/src/lib/**
|
|
43
|
+
parts/
|
|
44
|
+
sdist/
|
|
45
|
+
var/
|
|
46
|
+
wheels/
|
|
47
|
+
share/python-wheels/
|
|
48
|
+
*.egg-info/
|
|
49
|
+
.installed.cfg
|
|
50
|
+
*.egg
|
|
51
|
+
MANIFEST
|
|
52
|
+
|
|
53
|
+
# PyInstaller
|
|
54
|
+
# Usually these files are written by a python script from a template
|
|
55
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
56
|
+
*.manifest
|
|
57
|
+
*.spec
|
|
58
|
+
|
|
59
|
+
# Installer logs
|
|
60
|
+
pip-log.txt
|
|
61
|
+
pip-delete-this-directory.txt
|
|
62
|
+
|
|
63
|
+
# Unit test / coverage reports
|
|
64
|
+
ai/tests/clean_response.json
|
|
65
|
+
ai/tests/cx_storage_response.json
|
|
66
|
+
ai/tests/execution_test.py
|
|
67
|
+
ai/tests/final_response.json
|
|
68
|
+
htmlcov/
|
|
69
|
+
.tox/
|
|
70
|
+
.nox/
|
|
71
|
+
.coverage
|
|
72
|
+
.coverage.*
|
|
73
|
+
.cache
|
|
74
|
+
nosetests.xml
|
|
75
|
+
coverage.xml
|
|
76
|
+
*.cover
|
|
77
|
+
*.py,cover
|
|
78
|
+
.hypothesis/
|
|
79
|
+
.pytest_cache/
|
|
80
|
+
cover/
|
|
81
|
+
|
|
82
|
+
# Translations
|
|
83
|
+
*.mo
|
|
84
|
+
*.pot
|
|
85
|
+
|
|
86
|
+
# Django stuff:
|
|
87
|
+
*.log
|
|
88
|
+
local_settings.py
|
|
89
|
+
db.sqlite3
|
|
90
|
+
db.sqlite3-journal
|
|
91
|
+
|
|
92
|
+
# Flask stuff:
|
|
93
|
+
instance/
|
|
94
|
+
.webassets-cache
|
|
95
|
+
|
|
96
|
+
# Scrapy stuff:
|
|
97
|
+
.scrapy
|
|
98
|
+
|
|
99
|
+
# Sphinx documentation
|
|
100
|
+
docs/_build/
|
|
101
|
+
|
|
102
|
+
# PyBuilder
|
|
103
|
+
.pybuilder/
|
|
104
|
+
target/
|
|
105
|
+
|
|
106
|
+
# Jupyter Notebook
|
|
107
|
+
.ipynb_checkpoints
|
|
108
|
+
|
|
109
|
+
# IPython
|
|
110
|
+
profile_default/
|
|
111
|
+
ipython_config.py
|
|
112
|
+
|
|
113
|
+
# pyenv
|
|
114
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
115
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
116
|
+
# .python-version
|
|
117
|
+
|
|
118
|
+
# pipenv
|
|
119
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
120
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
121
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
122
|
+
# install all needed dependencies.
|
|
123
|
+
#Pipfile.lock
|
|
124
|
+
|
|
125
|
+
# poetry
|
|
126
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
127
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
128
|
+
# commonly ignored for libraries.
|
|
129
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
130
|
+
|
|
131
|
+
# pdm
|
|
132
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
133
|
+
#pdm.lock
|
|
134
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
135
|
+
# in version control.
|
|
136
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
137
|
+
.pdm.toml
|
|
138
|
+
|
|
139
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
140
|
+
__pypackages__/
|
|
141
|
+
|
|
142
|
+
# Celery stuff
|
|
143
|
+
celerybeat-schedule
|
|
144
|
+
celerybeat.pid
|
|
145
|
+
|
|
146
|
+
# SageMath parsed files
|
|
147
|
+
*.sage.py
|
|
148
|
+
|
|
149
|
+
# Environments
|
|
150
|
+
.env
|
|
151
|
+
.env_remote
|
|
152
|
+
.venv
|
|
153
|
+
env/
|
|
154
|
+
venv/
|
|
155
|
+
ENV/
|
|
156
|
+
env.bak/
|
|
157
|
+
venv.bak/
|
|
158
|
+
.env.armanonly
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# random armani files
|
|
179
|
+
/armani_dev/secrets/
|
|
180
|
+
/armani/
|
|
181
|
+
/_armani/
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# pytype static type analyzer
|
|
186
|
+
.pytype/
|
|
187
|
+
|
|
188
|
+
# Cython debug symbols
|
|
189
|
+
cython_debug/
|
|
190
|
+
|
|
191
|
+
.idea/
|
|
192
|
+
.vscode/
|
|
193
|
+
/node_modules/
|
|
194
|
+
|
|
195
|
+
# Frontend pnpm workspace (apps/) — node_modules at the workspace root and any
|
|
196
|
+
# member, plus Vite caches and build output. The unified lockfile (apps/pnpm-lock.yaml)
|
|
197
|
+
# IS committed; everything below is regenerated.
|
|
198
|
+
node_modules/
|
|
199
|
+
apps/**/.vite/
|
|
200
|
+
apps/**/dist/
|
|
201
|
+
.vite/
|
|
202
|
+
|
|
203
|
+
dump.rdb
|
|
204
|
+
|
|
205
|
+
frontend/
|
|
206
|
+
|
|
207
|
+
# AME Temp Files and directory structure
|
|
208
|
+
# Ignore all files in the temp directory and its subdirectories
|
|
209
|
+
/temp/**/*
|
|
210
|
+
/tmp/**/*
|
|
211
|
+
|
|
212
|
+
# Allow .gitkeep files to retain directory structure
|
|
213
|
+
!/temp/**/.gitkeep
|
|
214
|
+
!/tmp/**/.gitkeep
|
|
215
|
+
|
|
216
|
+
# Armani
|
|
217
|
+
.history*
|
|
218
|
+
.history/
|
|
219
|
+
local_data/
|
|
220
|
+
local_reports_data/
|
|
221
|
+
webscraper/quick_scrapes/temp/
|
|
222
|
+
automation_matrix/ai_apis/fireworks/_dev/*
|
|
223
|
+
automation_matrix/ai_apis/fireworks/_dev/fireworks_sample.py
|
|
224
|
+
*.pdf
|
|
225
|
+
*.flac
|
|
226
|
+
*.mp3
|
|
227
|
+
*.wav
|
|
228
|
+
miniconda.sh
|
|
229
|
+
/database/python_sql/temp_data/
|
|
230
|
+
.history*
|
|
231
|
+
.history/
|
|
232
|
+
.history/
|
|
233
|
+
|
|
234
|
+
_dev/
|
|
235
|
+
/_dev/
|
|
236
|
+
requirements_filtered.txt
|
|
237
|
+
|
|
238
|
+
# matrx-dev-tools backups
|
|
239
|
+
.env-backups/
|
|
240
|
+
# Matrx Ship config (contains API key)
|
|
241
|
+
.matrx-ship.json
|
|
242
|
+
|
|
243
|
+
# Matrx config (contains API keys)
|
|
244
|
+
.matrx.json
|
|
245
|
+
.matrx-tools.conf
|
|
246
|
+
|
|
247
|
+
# Claude Code local worktrees and per-user settings
|
|
248
|
+
.claude/worktrees/
|
|
249
|
+
.claude/settings.local.json
|
|
250
|
+
|
|
251
|
+
# Append-only snapshots from matrx_utils.update_history (unbounded; do not commit)
|
|
252
|
+
common/utils/data_in_code/data_history.json
|
|
253
|
+
packages/matrx-utils/matrx_utils/data_in_code/data_history.json
|
|
254
|
+
|
|
255
|
+
# Tool-dispatch debug logs — one file per server start, never committed
|
|
256
|
+
.matrx-debug/
|
|
257
|
+
|
|
258
|
+
# macOS Finder metadata
|
|
259
|
+
.DS_Store
|
|
260
|
+
**/.DS_Store
|
|
261
|
+
|
|
262
|
+
# Environment files
|
|
263
|
+
.env
|
|
264
|
+
.env.*
|
|
265
|
+
*.env
|
|
266
|
+
*.env.*
|
|
267
|
+
|
|
268
|
+
# Keep safe templates trackable
|
|
269
|
+
!.env.example
|
|
270
|
+
!.env.sample
|
|
271
|
+
!.env.template
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# CLAUDE.md — matrx-connect
|
|
2
|
+
|
|
3
|
+
> **Operating Principle: Build the platform, not the artifact.** Every task is a probe that exposes a missing capability — build it, then consume it. Code that only serves one artifact is forbidden. Full doctrine: [/PRINCIPLES.md](../../PRINCIPLES.md).
|
|
4
|
+
|
|
5
|
+
**Package:** `matrx-connect` (PyPI) — Python 3.12+ — currently v0.1.1
|
|
6
|
+
**Role in the graph:** Tier 1. Depends only on `matrx-utils`. Used by `matrx-graph`, `matrx-scraper` (optional), and `matrx-ai`.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Read this first
|
|
11
|
+
|
|
12
|
+
`matrx-connect` is the FastAPI-integration layer for the Matrx ecosystem: **auth middleware, streaming response infrastructure, request-scoped `AppContext`, and the `Emitter` protocol.** It is *not* a database connectivity package (despite the name — "connect" refers to connecting FastAPI apps to the Matrx streaming / auth contract).
|
|
13
|
+
|
|
14
|
+
Any aidream-specific concept that belongs in a package belongs here, **not in the packages above**. If a sibling package needs auth or streaming primitives, it should `configure_ext(...)` or import from matrx-connect; aidream should never be touched.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What this package provides
|
|
19
|
+
|
|
20
|
+
- **`AppContext`** (`matrx_connect.context.app_context`) — request-scoped identity + metadata held in a `ContextVar`. Fields: `user_id`, `email`, `auth_type`, `is_authenticated`, `is_admin`, `request_id`, `fingerprint_id`, `api_keys`, `metadata`, `emitter`. **Frozen** (Phase 1) — derive a modified copy with `with_overrides(**kwargs)` and install it via `set_app_context(...)` (the old mutating `extend()` was removed and now raises). Persistence intent fields: `store` (False = write NOTHING, ephemeral) and `system_run` (True = internal machine call; consumers persist only the cost spine and skip transcript machinery — set via matrx-ai `run_agent(system_run=True)`, semantics documented on the field).
|
|
21
|
+
- **Auth middleware** (`matrx_connect.middleware.auth`) — pluggable JWT / admin-token / fingerprint resolution. Constructor takes `jwt_secret`, `admin_token`, `admin_user_id`, `resolve_guest` callback.
|
|
22
|
+
- **`Emitter` protocol + `StreamEmitter` / `ConsoleEmitter`** — async event emitters with the full vocabulary (`send_chunk`, `send_reasoning`, `send_data`, `send_phase`, tool events, `fatal_error`, `send_end`).
|
|
23
|
+
- **`create_streaming_response(...)`** (`matrx_connect.streaming.response`) — the canonical streaming response wrapper. Spawns the task, manages lifecycle, emits heartbeats, clears the ContextVar on exit.
|
|
24
|
+
- **`context_dep`** — FastAPI `Depends()` helper for pulling `AppContext` into a route handler.
|
|
25
|
+
- **Event data types** (`matrx_connect.context.events`, `.data_types`, `.operations`, `.tool_event_data`, `.render_blocks`) — Pydantic event payload schemas shared with the frontend.
|
|
26
|
+
|
|
27
|
+
Public exports from `matrx_connect/__init__.py`: `AppContext`, `Emitter`, `EventType`, `StreamEvent`, `StreamEmitter`, `ConsoleEmitter`, `context_dep`, `get_app_context`, `set_app_context`, `try_get_app_context`, `clear_app_context`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## The streaming endpoint pattern (canonical)
|
|
32
|
+
|
|
33
|
+
Every streaming route in aidream and in any external consumer is expected to follow this shape. Do not deviate inside this package:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from matrx_connect import AppContext, context_dep
|
|
37
|
+
from matrx_connect.streaming import create_streaming_response
|
|
38
|
+
from fastapi import Depends
|
|
39
|
+
|
|
40
|
+
@router.post("/thing")
|
|
41
|
+
async def do_thing(payload: ThingRequest, ctx: AppContext = Depends(context_dep)):
|
|
42
|
+
return create_streaming_response(
|
|
43
|
+
ctx, _run, payload,
|
|
44
|
+
initial_message="Starting…", debug_label="ThingFlow",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
async def _run(emitter, payload: ThingRequest):
|
|
48
|
+
# AppContext is already in the ContextVar; use get_app_context() if you need it.
|
|
49
|
+
result = await do_work(payload)
|
|
50
|
+
await emitter.send_data(ResultPayload(...).model_dump())
|
|
51
|
+
await emitter.send_end()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Rules (enforce these on contributions to matrx-connect and consumers):
|
|
55
|
+
|
|
56
|
+
- `create_streaming_response` owns the context **teardown** — it `clear_app_context`s on exit; never `clear_app_context` yourself. A route that needs a route-scoped field (e.g. `conversation_id`) for the streaming task installs it with `ctx = ctx.with_overrides(field=value); set_app_context(ctx)` BEFORE calling `create_streaming_response`. AppContext is **frozen** (Phase 1), so `set_app_context` is REQUIRED to install the derived ctx into the ContextVar — omit it and the override is silently dropped.
|
|
57
|
+
- The task function takes `emitter` as its first arg, then business args. It does not handle `CancelledError` or generic exceptions; infrastructure does.
|
|
58
|
+
- Heartbeats and client-disconnect cleanup are the middleware's job, not the task's.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## The auth contract (read this if you're touching `middleware/auth.py`)
|
|
63
|
+
|
|
64
|
+
`AuthMiddleware` is the only place `ctx.user_id` gets set. The contract is intentionally narrow:
|
|
65
|
+
|
|
66
|
+
- **Accepted token types:** Supabase-signed JWTs only. The middleware verifies the signature with `jwt_secret`, checks `aud="authenticated"`, and pulls the user_id from `sub`. That's it.
|
|
67
|
+
- **No static admin token, no env-mapped user_id.** The legacy `admin_token` + `admin_user_id` escape hatch was removed (intentionally — see [`SCRAPER_SECURITY_BUGS.md`](../../docs/scraper/SCRAPER_SECURITY_BUGS.md) for why). Do not re-add it.
|
|
68
|
+
- **`is_admin` resolution:** an injected `is_admin_resolver(user_id) → bool` consults the host's `public.admins` table on every authenticated request. The middleware itself never decides admin status.
|
|
69
|
+
- **Local dev / agent authentication:** the host's dev-login endpoint mints a real Supabase JWT for a real user_id (gated by an env flag never set in prod). Agents use that JWT like any other client. There is no agent-specific code path in this package, and there must never be one.
|
|
70
|
+
|
|
71
|
+
If you find yourself wanting to add a fast path that maps a token-or-secret to a user_id without going through JWT validation, stop. The middleware's job is to accept Supabase JWTs and hand off to the host's resolvers. Anything else belongs in the host as an explicitly-named dev-only endpoint that *produces* a real JWT.
|
|
72
|
+
|
|
73
|
+
### Server-to-server: the approved-server handshake (`service_auth.py`) — NOT the middleware
|
|
74
|
+
|
|
75
|
+
The rule above is about the **middleware**. There is a separate, sanctioned way for one of our **servers** (not a person) to call a protected route: `matrx_connect.service_auth`. This is **opt-in per route** — a host applies it explicitly to specific routers; it is never wired into `AuthMiddleware`, and the middleware stays JWT-only.
|
|
76
|
+
|
|
77
|
+
- `require_authenticated_or_service(secret, *, require_user=False)` — a router dependency that admits EITHER a real user login (`is_authenticated` already set) OR an approved-server call: the bearer equals the shared `secret` (constant-time), and the acting user is named by the `X-Matrx-User-Id` header. It stamps that user into the request context.
|
|
78
|
+
- `verify_approved_server(request, *, secret, secret_header="authorization", require_user=False)` — the pure check (used by callers with a custom header or their own 503-when-unconfigured signal, e.g. the sandbox bridge / media-healer).
|
|
79
|
+
|
|
80
|
+
**Why this is safe and NOT the removed escape hatch:** the old hatch hard-bound every call to ONE fixed `admin_user_id` env var, so writes were mis-attributed to a fake admin and users' data mixed (the incident in [`SCRAPER_SECURITY_BUGS.md`](../../docs/scraper/SCRAPER_SECURITY_BUGS.md) §D). This primitive **never defaults to a user** — the acting user is exactly what the caller names, or empty for ephemeral no-write calls; a write endpoint enforces its own owner and raises on a missing user. It's the reusable form of the pattern the sandbox bridge (`cloud_files_bridge`) already used; the scraper, sandbox bridge, and media-healer now share this one primitive. Adding a per-route service path here is allowed; adding a token→user path to the middleware is not.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## The configuration / injection pattern
|
|
85
|
+
|
|
86
|
+
matrx-connect is itself a "thing other packages and apps inject into". Its own injection surface:
|
|
87
|
+
|
|
88
|
+
- `AuthMiddleware(...)` constructor — pass `jwt_secret`, `is_admin_resolver`, and `resolve_guest` callback from the host. (`admin_token` / `admin_user_id` are no longer accepted — see "The auth contract" above.)
|
|
89
|
+
- `AppContext.with_overrides(**kwargs)` + `set_app_context(...)` — host apps install request-scoped overrides (e.g. `conversation_id`) before the streaming task runs. AppContext is frozen; `extend()` was removed in Phase 1 and now raises.
|
|
90
|
+
- `Emitter` is a `typing.Protocol` — any object that satisfies the shape works; `StreamEmitter` is just the default HTTP/JSONL implementation.
|
|
91
|
+
|
|
92
|
+
Inside this package:
|
|
93
|
+
|
|
94
|
+
- **Never** read environment variables at import time. The middleware takes its config as constructor args.
|
|
95
|
+
- **Never** assume a specific database or ORM. The only persistence matrx-connect does is NDJSON over HTTP.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Dependency rules specific to this package
|
|
100
|
+
|
|
101
|
+
- ✅ May import `matrx_utils` (currently only for `vcprint` dev-logging).
|
|
102
|
+
- ❌ No `from matrx_orm import …`
|
|
103
|
+
- ❌ No `from matrx_graph import …`
|
|
104
|
+
- ❌ No `from matrx_scraper import …`
|
|
105
|
+
- ❌ No `from matrx_ai import …`
|
|
106
|
+
- ❌ No `from aidream import …`, no root-module imports.
|
|
107
|
+
|
|
108
|
+
If a feature seems to need an ORM (e.g. auth wants to load users from a DB), inject the loader function — don't pull in matrx-orm.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## The event vocabulary — when to use which
|
|
113
|
+
|
|
114
|
+
`matrx_connect.context.events.EventType` is THE registry for everything Python emits to the client. Every event has a Pydantic payload class registered in `PAYLOAD_REGISTRY`; `build_event(event_type, payload)` is the only sanctioned constructor (rejects payload-class mismatches at emit time). Adding a new event type is a coordinated change: enum entry + payload class + registry entry + Emitter Protocol method + StreamEmitter/ConsoleEmitter implementations + (optional) re-exports in `context/__init__.py`.
|
|
115
|
+
|
|
116
|
+
The most-confused choice points — read these before you reach for a new emitter call:
|
|
117
|
+
|
|
118
|
+
| You want to tell the client… | Use | Don't use |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| Token-by-token text or reasoning TEXT | `send_chunk` / `send_reasoning_chunk` | DATA |
|
|
121
|
+
| **The model started / stopped THINKING (content-less)** | **`send_reasoning_state`** | `send_phase`, `send_chunk` |
|
|
122
|
+
| Lifecycle stage (e.g. "connected", "tool_loop_started") | `send_phase` | INFO |
|
|
123
|
+
| Structured payload the UI renders inline (search results, conversation IDs, image URLs, …) | `send_data` | DATA + secret intent encoding |
|
|
124
|
+
| Operational note that decorates a successful response | `send_warning` (with severity) | ERROR |
|
|
125
|
+
| The response itself failed before/during stream | `send_error` (then `send_end`) | WARNING |
|
|
126
|
+
| Tool started / progress / completed | `send_tool_event` | INFO |
|
|
127
|
+
| A matrx-orm record was reserved / updated | `send_record_reserved` / `send_record_update` | RESOURCE_CHANGED |
|
|
128
|
+
| **A non-orm resource changed; client should refetch if displaying it** | **`send_resource_changed`** | DATA, INFO |
|
|
129
|
+
| **An agent finished and its declared `output_schema` was applied to the response** | **`send_structured_output`** | DATA, RECORD_UPDATE |
|
|
130
|
+
|
|
131
|
+
### `REASONING` — content-less thinking lifecycle (added 2026-07-05)
|
|
132
|
+
|
|
133
|
+
`send_reasoning_state("started" | "stopped")` — the model opened or closed a thinking/reasoning block, **independent of whether reasoning text streams**. Every provider signals a reasoning block start even when the reasoning text is suppressed (Anthropic `display="omitted"`, OpenAI `reasoning_summary="never"`, Gemini `include_thoughts=False`) — this event carries that signal so the UI shows a "thinking" state instead of a silent heartbeat gap.
|
|
134
|
+
|
|
135
|
+
- **This is NOT `reasoning_chunk`.** `reasoning_chunk` carries reasoning TEXT (only when requested); `reasoning` is the on/off lifecycle and fires regardless.
|
|
136
|
+
- **This is NOT a `phase`.** A model can think while the overall phase is `generating` or `using_tools`; reasoning is a sub-state that pairs `started`/`stopped`, not a one-way progress stage.
|
|
137
|
+
- **Emitted by the provider stream parsers**, not the orchestrator — each provider fires it at its own reasoning-block boundary (`matrx_ai/providers/*/*_api.py`). Balanced per turn; a turn with no thinking emits nothing.
|
|
138
|
+
|
|
139
|
+
### `RESOURCE_CHANGED` — generic refresh hint (added 2026-04-27)
|
|
140
|
+
|
|
141
|
+
Use `send_resource_changed(*, kind, action, resource_id, sandbox_id?, user_id?, metadata?)` whenever your code mutates something on the server side that a client may be displaying or caching, and **no other event type already covers it**. This is the generic "refetch this" primitive — distinct from RECORD_UPDATE (matrx-orm-row-specific) and DATA (which carries actual content, not change hints).
|
|
142
|
+
|
|
143
|
+
`kind` is namespaced and intentionally open-ended; consumers register their own. Established conventions:
|
|
144
|
+
|
|
145
|
+
- `fs.file`, `fs.directory` — sandbox / agent workspace filesystem (emitted by matrx-ai's `fs_write`, `fs_patch`, `fs_mkdir` — see `matrx_ai/tools/_change_events.py`)
|
|
146
|
+
- `cld_files` — AI Dream cloud-files row changed (size, version, mime_type)
|
|
147
|
+
- `sandbox.cwd` — agent's working directory shifted
|
|
148
|
+
- `cache.<key>` — explicit cache invalidation hint
|
|
149
|
+
- `active_tools` — the agent's active tool set changed mid-loop. Emitted by matrx-ai's `tools/dynamic_drain.py` whenever a registered tool calls `ctx.queue_tool_changes(...)` and the orchestrator drains it between iterations. **Exactly one event per non-empty drain**; an empty drain (no pending mutations) emits nothing — the channel is silent when the active set is unchanged. `metadata` carries:
|
|
150
|
+
- `added_tools: list[str]` — names of tools just added to the active set (in queue order; possibly empty when this drain only removed).
|
|
151
|
+
- `removed_tools: list[str]` — names of tools just removed (deduped, order preserved).
|
|
152
|
+
- `active_count: int` — total tools active *after* the drain (`config.tools` + `config.custom_tools`); use this to keep client-side counters in sync without reconciling lists.
|
|
153
|
+
- `added: int`, `removed: int`, `sources: list[str]` — back-compat scalars from the original payload; safe to ignore in new consumers.
|
|
154
|
+
|
|
155
|
+
The FE re-renders any "active tools" UI on this signal — see [TOOL_INJECTION_REFACTOR.md](../../docs/cx_chat/TOOL_INJECTION_REFACTOR.md) for the full pattern. The matrx-extend Chrome extension consumes this event into its `useActiveToolsStore` to update the Tools-tab badge and `loaded_categories` hint without polling.
|
|
156
|
+
|
|
157
|
+
`action` is a closed `Literal`: `created` | `modified` | `deleted` | `moved` | `renamed` | `invalidated`. Use `invalidated` as the batch/coalesced "I changed many things under this scope" hint (a directory wipe, a `git checkout` that touches dozens of files) instead of fanning out to one event per file.
|
|
158
|
+
|
|
159
|
+
For move/rename, `resource_id` is the **new** identifier and `metadata.previous_id` carries the old one.
|
|
160
|
+
|
|
161
|
+
The wire shape is auto-emitted into `aidream/api/generated/stream-events.ts` via the `/schema/all` endpoint, so the FE gets typed `ResourceChangedPayload` after running `pnpm sync-types`.
|
|
162
|
+
|
|
163
|
+
When in doubt: there is **no shame in adding a more specific event type later** if `kind` strings start to feel like a sub-type system. The point of RESOURCE_CHANGED is to give every package a typed home for invalidation hints without forcing matrx-connect to know about every domain.
|
|
164
|
+
|
|
165
|
+
### `STRUCTURED_OUTPUT` — agent's parsed response (added 2026-05-12)
|
|
166
|
+
|
|
167
|
+
Emitted automatically by `matrx_ai.agents.Agent.execute` whenever the agent has an `output_schema` declared and the execution loop has produced its final assistant message. Carries the full JSON Schema AND the parsed payload, so the FE can drive generic renderers (tables, forms, type-checked accessors) without re-running extraction or re-fetching the agent record.
|
|
168
|
+
|
|
169
|
+
- Fires AFTER all `CHUNK` events — prose-first streaming behavior is preserved; the structured event is a capstone, not a replacement.
|
|
170
|
+
- Fires whether extraction succeeded or failed. `success: false` (with `data: null` and a human-readable `reason`) tells the FE the agent declared a contract and the parser couldn't honour it — surface the gap, don't silently drop.
|
|
171
|
+
- Fires once per agent completion. Sub-agents emit their own events, tagged with the matching `operation_id` so the FE can attribute them to the right sub_agent init/completion pair.
|
|
172
|
+
- The schema is in the payload (verbatim from `agent.output_schema["schema"]`) — clients should not have to look up the agent to know the contract.
|
|
173
|
+
|
|
174
|
+
Wire shape:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"event": "structured_output",
|
|
179
|
+
"data": {
|
|
180
|
+
"schema_name": "combined_sdt_index",
|
|
181
|
+
"json_schema": { "type": "object", "required": ["items"], "properties": { … } },
|
|
182
|
+
"data": { "items": [ … ] },
|
|
183
|
+
"success": true,
|
|
184
|
+
"reason": "",
|
|
185
|
+
"match_count": 1,
|
|
186
|
+
"agent_name": "WC Medical & Legal Report Extractor",
|
|
187
|
+
"operation_id": null
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The FE picks up the typed `StructuredOutputPayload` automatically after running `pnpm sync-types`.
|
|
193
|
+
|
|
194
|
+
**Do not** hand-roll a `send_data` event to ship structured agent output. The funnel is `Agent.output_schema` + automatic emission. If a caller needs the parsed value server-side without an agent (e.g. a raw `execute_ai_request` call), call `parse_agent_output(text, schema)` directly — but the standard agent path emits the event for you.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Python standards (same as root)
|
|
199
|
+
|
|
200
|
+
- Full type hints on every signature. `Emitter` / `AppContext` shapes are part of the public contract; changing them is a breaking change.
|
|
201
|
+
- No docstrings unless the symbol is user-facing (the ones in `context/`, `streaming/`, `middleware/` qualify — keep them one line).
|
|
202
|
+
- Explicit exception handling. `fatal_error` is the one-way door for unrecoverable stream errors.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Testing this package in isolation
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
uv run pytest packages/matrx-connect/tests
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Tests must run without any sibling package beyond `matrx-utils`. Use FastAPI's `TestClient` for middleware; mock `resolve_guest` rather than reaching into aidream.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Known issues
|
|
217
|
+
|
|
218
|
+
Tracked centrally in the root `PACKAGES_MIGRATION_PLAN.md`. This package is the cleanest of the bunch — no known import violations or hardcoded paths.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matrx-connect
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: FastAPI middleware, auth, streaming infrastructure, and context management for the Matrx ecosystem
|
|
5
|
+
Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
|
|
6
|
+
Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
|
|
7
|
+
Project-URL: Issues, https://github.com/AI-Matrix-Engine/aidream-current/issues
|
|
8
|
+
Author-email: Matrx <admin@aimatrx.com>
|
|
9
|
+
Maintainer-email: Matrx <admin@aimatrx.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: auth,fastapi,matrx,middleware,sse,streaming
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: fastapi>=0.115
|
|
22
|
+
Requires-Dist: matrx-utils>=1.0.20
|
|
23
|
+
Requires-Dist: orjson>=3.10
|
|
24
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
25
|
+
Requires-Dist: pydantic>=2.12
|
|
26
|
+
Requires-Dist: pyjwt>=2.8
|
|
27
|
+
Requires-Dist: sse-starlette>=1.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# matrx-connect
|
|
31
|
+
|
|
32
|
+
FastAPI connectivity layer for the Matrx ecosystem: **auth middleware, streaming-response infrastructure, request-scoped `AppContext`, and the `Emitter` protocol**. Despite the name, "connect" is about connecting a FastAPI app to the Matrx streaming + auth contract — not database connectivity.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install matrx-connect
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Python 3.12+ required. Depends only on `matrx-utils` from the Matrx family.
|
|
41
|
+
|
|
42
|
+
## What's in the box
|
|
43
|
+
|
|
44
|
+
| Module | What it does |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `matrx_connect.context.app_context` | `AppContext` dataclass + `ContextVar` + `get_app_context` / `set_app_context` / `try_get_app_context` / `clear_app_context` |
|
|
47
|
+
| `matrx_connect.context.emitter_protocol` | `Emitter` protocol — every method any producer can call (`send_chunk`, `send_reasoning`, `send_phase`, `send_data`, `send_info`, `send_warning`, `fatal_error`, `send_end`, tool events, …) |
|
|
48
|
+
| `matrx_connect.context.events` | Pydantic event payload schemas shared with the frontend |
|
|
49
|
+
| `matrx_connect.emitters.stream_emitter` | `StreamEmitter` — the JSONL/NDJSON HTTP streaming implementation |
|
|
50
|
+
| `matrx_connect.emitters.console_emitter` | `ConsoleEmitter` — dev/test implementation that prints to stdout |
|
|
51
|
+
| `matrx_connect.middleware.auth` | `AuthMiddleware` — pluggable JWT + admin-token + fingerprint resolution, with a `resolve_guest` callback hook |
|
|
52
|
+
| `matrx_connect.streaming.response` | `create_streaming_response(ctx, task, *args, ...)` — the ONLY public entry point for streaming endpoints |
|
|
53
|
+
| `matrx_connect.dependencies` | `context_dep` — the FastAPI `Depends()` helper that pulls `AppContext` into a route handler |
|
|
54
|
+
|
|
55
|
+
## The streaming endpoint pattern
|
|
56
|
+
|
|
57
|
+
Every streaming route follows this exact shape. It's enforced across every repo that uses matrx-connect, so that the behavior of heartbeats, client disconnects, and error handling stays consistent:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from fastapi import APIRouter, Depends
|
|
61
|
+
from matrx_connect import AppContext, context_dep
|
|
62
|
+
from matrx_connect.streaming import create_streaming_response
|
|
63
|
+
|
|
64
|
+
router = APIRouter()
|
|
65
|
+
|
|
66
|
+
@router.post("/topics/{topic_id}/search")
|
|
67
|
+
async def trigger_search(topic_id: str, ctx: AppContext = Depends(context_dep)):
|
|
68
|
+
return create_streaming_response(
|
|
69
|
+
ctx, _run_search, topic_id,
|
|
70
|
+
initial_message="Starting search…", debug_label="ResearchSearch",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def _run_search(emitter, topic_id: str):
|
|
74
|
+
# AppContext is already set on the ContextVar.
|
|
75
|
+
# Cancellation + exception handling are done for you.
|
|
76
|
+
result = await do_work(topic_id)
|
|
77
|
+
await emitter.send_data(SearchResult(...).model_dump())
|
|
78
|
+
await emitter.send_end()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
What `create_streaming_response` does for you:
|
|
82
|
+
- Creates the `StreamEmitter`, attaches it to `AppContext`, and pushes `AppContext` onto the `ContextVar`.
|
|
83
|
+
- Spawns the task as a background `asyncio.Task`.
|
|
84
|
+
- Catches `CancelledError` (client disconnect) and generic exceptions — the latter are surfaced via `emitter.fatal_error(...)`.
|
|
85
|
+
- Emits heartbeat keepalives while the task is running.
|
|
86
|
+
- Clears the `ContextVar` on exit.
|
|
87
|
+
|
|
88
|
+
Your task function never touches `set_app_context` / `clear_app_context` / `CancelledError`. If you find yourself reaching for those symbols in application code, extend `create_streaming_response` instead.
|
|
89
|
+
|
|
90
|
+
## Wiring the auth middleware
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from fastapi import FastAPI
|
|
94
|
+
from matrx_connect.middleware.auth import AuthMiddleware
|
|
95
|
+
|
|
96
|
+
app = FastAPI()
|
|
97
|
+
app.add_middleware(
|
|
98
|
+
AuthMiddleware,
|
|
99
|
+
jwt_secret=settings.JWT_SECRET,
|
|
100
|
+
admin_token=settings.ADMIN_TOKEN,
|
|
101
|
+
admin_user_id=settings.ADMIN_USER_ID,
|
|
102
|
+
resolve_guest=my_guest_resolver, # optional callback for fingerprint-based guests
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
After this, every request has an `AppContext` on `request.state.context` and `context_dep` will hand it to any route handler that depends on it.
|
|
107
|
+
|
|
108
|
+
## Standalone-friendliness
|
|
109
|
+
|
|
110
|
+
`matrx-connect` has a single sibling dependency (`matrx-utils`, for verbose-logging). It assumes no ORM, no database, no Supabase — wire those in from your app. The `Emitter` is a `typing.Protocol`, so you can hand `create_streaming_response` any object that satisfies the shape.
|
|
111
|
+
|
|
112
|
+
## Contributing
|
|
113
|
+
|
|
114
|
+
See [CLAUDE.md](CLAUDE.md) for package-specific import rules and conventions. This package lives in the aidream monorepo at [github.com/AI-Matrix-Engine/aidream-current](https://github.com/AI-Matrix-Engine/aidream-current/tree/main/packages/matrx-connect).
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT.
|