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.
Files changed (50) hide show
  1. matrx_connect-0.1.1/.gitignore +271 -0
  2. matrx_connect-0.1.1/CLAUDE.md +218 -0
  3. matrx_connect-0.1.1/PKG-INFO +118 -0
  4. matrx_connect-0.1.1/README.md +89 -0
  5. matrx_connect-0.1.1/matrx_connect/__init__.py +40 -0
  6. matrx_connect-0.1.1/matrx_connect/chat_timing.py +494 -0
  7. matrx_connect-0.1.1/matrx_connect/context/__init__.py +286 -0
  8. matrx_connect-0.1.1/matrx_connect/context/app_context.py +488 -0
  9. matrx_connect-0.1.1/matrx_connect/context/data_types.py +1407 -0
  10. matrx_connect-0.1.1/matrx_connect/context/emitter_protocol.py +101 -0
  11. matrx_connect-0.1.1/matrx_connect/context/events.py +766 -0
  12. matrx_connect-0.1.1/matrx_connect/context/media_block.py +462 -0
  13. matrx_connect-0.1.1/matrx_connect/context/operations.py +200 -0
  14. matrx_connect-0.1.1/matrx_connect/context/phases.py +32 -0
  15. matrx_connect-0.1.1/matrx_connect/context/render_blocks.py +145 -0
  16. matrx_connect-0.1.1/matrx_connect/context/tool_event_data.py +116 -0
  17. matrx_connect-0.1.1/matrx_connect/dependencies.py +59 -0
  18. matrx_connect-0.1.1/matrx_connect/emitters/__init__.py +5 -0
  19. matrx_connect-0.1.1/matrx_connect/emitters/console_emitter.py +353 -0
  20. matrx_connect-0.1.1/matrx_connect/emitters/log_truncation.py +24 -0
  21. matrx_connect-0.1.1/matrx_connect/emitters/silent_emitter.py +130 -0
  22. matrx_connect-0.1.1/matrx_connect/emitters/stream_emitter.py +677 -0
  23. matrx_connect-0.1.1/matrx_connect/lane/__init__.py +45 -0
  24. matrx_connect-0.1.1/matrx_connect/lane/buckets.py +71 -0
  25. matrx_connect-0.1.1/matrx_connect/lane/contextvar.py +41 -0
  26. matrx_connect-0.1.1/matrx_connect/lane/core.py +725 -0
  27. matrx_connect-0.1.1/matrx_connect/lane/priorities.py +42 -0
  28. matrx_connect-0.1.1/matrx_connect/middleware/__init__.py +3 -0
  29. matrx_connect-0.1.1/matrx_connect/middleware/auth.py +744 -0
  30. matrx_connect-0.1.1/matrx_connect/request_controls.py +83 -0
  31. matrx_connect-0.1.1/matrx_connect/request_latency.py +52 -0
  32. matrx_connect-0.1.1/matrx_connect/reservations.py +256 -0
  33. matrx_connect-0.1.1/matrx_connect/service_auth.py +126 -0
  34. matrx_connect-0.1.1/matrx_connect/streaming/__init__.py +25 -0
  35. matrx_connect-0.1.1/matrx_connect/streaming/error_capture.py +69 -0
  36. matrx_connect-0.1.1/matrx_connect/streaming/primitives.py +118 -0
  37. matrx_connect-0.1.1/matrx_connect/streaming/response.py +647 -0
  38. matrx_connect-0.1.1/pyproject.toml +61 -0
  39. matrx_connect-0.1.1/scripts/release.sh +5 -0
  40. matrx_connect-0.1.1/tests/__init__.py +0 -0
  41. matrx_connect-0.1.1/tests/test_auth_middleware.py +889 -0
  42. matrx_connect-0.1.1/tests/test_chat_timing.py +187 -0
  43. matrx_connect-0.1.1/tests/test_error_capture.py +80 -0
  44. matrx_connect-0.1.1/tests/test_lane.py +343 -0
  45. matrx_connect-0.1.1/tests/test_log_truncation.py +28 -0
  46. matrx_connect-0.1.1/tests/test_media_block_no_storage_uri.py +57 -0
  47. matrx_connect-0.1.1/tests/test_prepared_lane_finalization.py +188 -0
  48. matrx_connect-0.1.1/tests/test_provider_retry_event.py +56 -0
  49. matrx_connect-0.1.1/tests/test_stream_emitter_logging.py +65 -0
  50. 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.