langgraph-stream-parser 0.2.2__tar.gz → 0.3.0__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.
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/CHANGELOG.md +43 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/PKG-INFO +12 -12
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/README.md +11 -11
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/pyproject.toml +1 -1
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/demo/stub.py +3 -3
- langgraph_stream_parser-0.3.0/src/langgraph_stream_parser/host/__init__.py +17 -0
- langgraph_stream_parser-0.3.0/src/langgraph_stream_parser/host/__main__.py +18 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/host/config.py +99 -33
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_host.py +1 -1
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_host_config.py +79 -0
- langgraph_stream_parser-0.2.2/src/langgraph_stream_parser/host/__init__.py +0 -16
- langgraph_stream_parser-0.2.2/src/langgraph_stream_parser/host/__main__.py +0 -17
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/.github/workflows/ci.yml +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/.github/workflows/release.yml +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/.gitignore +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/LICENSE +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/assets/header.svg +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/examples/agent.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/examples/fastapi_websocket.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/examples/jupyter_example.ipynb +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/spec.md +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/__init__.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/adapters/__init__.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/adapters/base.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/adapters/cli.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/adapters/fastapi.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/adapters/jupyter.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/adapters/print.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/adapters/session.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/compat.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/demo/__init__.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/demo/agent.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/events.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/extractors/__init__.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/extractors/base.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/extractors/builtins.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/extractors/interrupts.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/extractors/messages.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/handlers/__init__.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/handlers/messages.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/handlers/updates.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/host/loader.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/host/workspace.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/parser.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/src/langgraph_stream_parser/resume.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/__init__.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/fixtures/__init__.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/fixtures/mocks.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_cli_adapter.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_compat.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_demo.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_demo_stub.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_dual_mode.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_events.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_extractors.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_fastapi_adapter.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_generic_extractor.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_jupyter.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_lc14_compat.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_parser.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_print_adapter.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_real_model.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_reasoning_display.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_resume.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_session_adapter.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_subagent.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_v2_stream.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_wire_contract.py +0 -0
- {langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/uv.lock +0 -0
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-06-10
|
|
4
|
+
|
|
5
|
+
The host family is renamed to **LangStage** ("every stage for your LangGraph agent"); this package keeps its name as the shared core.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`LANGSTAGE_*` is the canonical config vocabulary**: `LANGSTAGE_AGENT_SPEC` etc. env vars, project `langstage.toml`, global `~/.langstage/config.toml`, and `LANGSTAGE_CONFIG_HOME`. The legacy names (`DEEPAGENT_*`, `deepagents.toml`, `~/.deepagents/config.toml`, `DEEPAGENTS_CONFIG_HOME`) still resolve everywhere as deprecated fallbacks — canonical wins when both are set; legacy env use emits a once-per-var `DeprecationWarning`. Moving the global config out of `~/.deepagents/` also exits the schema collision with LangChain's dcode, which owns that directory.
|
|
9
|
+
- Host subclasses may declare either spelling in their `_ENV` maps; both names resolve (`_env_pair` derivation), so downstream hosts need no immediate change.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- `HostConfig.title` default: `"Deep Agent"` → `"LangStage"`.
|
|
13
|
+
- `describe()` shows both vocabularies per field (`env: LANGSTAGE_X (legacy DEEPAGENT_X)`).
|
|
14
|
+
- README family table updated to the LangStage package names.
|
|
15
|
+
|
|
16
|
+
## [0.2.2] - 2026-06-10
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `demo.create_stub_agent()` / `langgraph_stream_parser.demo.stub:graph` — the keyless, deterministic echo agent behind every surface's `--demo` mode. Lazy imports; base install stays dependency-free.
|
|
20
|
+
|
|
21
|
+
## [0.2.1] - 2026-06-08
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `GenericToolExtractor` + `default_extractor` fallback for unknown tools (#16).
|
|
25
|
+
- Tag-driven Release workflow (#17).
|
|
26
|
+
|
|
27
|
+
## [0.2.0] - 2026-06-02
|
|
28
|
+
|
|
29
|
+
Repositions langgraph-stream-parser as the **shared runtime substrate** for the deep-agent host family (`cowork-dash`, `deepagent-lab`, `deepagent-code`, `deepagent-vscode`).
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- **`host/` submodule** — shared host conventions:
|
|
33
|
+
- `load_agent_spec("path.py:var" | "module:var")` — strict agent-spec loader (the `:object` suffix is required).
|
|
34
|
+
- `HostConfig` — layered config resolver: `defaults < deepagents.toml < DEEPAGENT_* env < overrides`, with per-field source tracking. Subclass and extend the `_ENV` / `_TOML` maps (merged across the MRO) to add host-specific keys. `DEEPAGENT_AGENT_SPEC` is the canonical agent-spec env var.
|
|
35
|
+
- `load_toml_config()` — loads + deep-merges global `~/.deepagents/config.toml` (override dir via `DEEPAGENTS_CONFIG_HOME`) and the nearest project `deepagents.toml`.
|
|
36
|
+
- `Workspace` — workspace-root wrapper with traversal-safe `subpath()`.
|
|
37
|
+
- `python -m langgraph_stream_parser.host` (and `HostConfig.describe()`) — prints each resolved value, its source, and the env var / TOML key that sets it.
|
|
38
|
+
- **`adapters.SessionAdapter`** — session-scoped streaming for web hosts: per-session event queue, cancellation, side-channel `push_event()`, and persistent SSE that survives client reconnects.
|
|
39
|
+
- **`demo.create_default_agent()`** — shared filesystem-backed default agent factory (behind the `[demo]` extra; lazy-imports `deepagents`).
|
|
40
|
+
- **Four built-in extractors** for the agentskills.io / Hermes pattern, wired into the default set so every host gets them through `compat`: `SkillManageExtractor` (`skill_manage`), `SkillViewExtractor` (`skill_view`), `CompressionExtractor` (`__compression__`), `MemoryExtractor` (`memory`).
|
|
41
|
+
- `event_to_dict(event, *, max_result_len=...)` — lets hosts drop bespoke serializer shims.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- `skip_tools` previously suppressed a tool's **extractor** as well as its lifecycle events, silently dropping `todo_list` / `reflection`. Extractors now run for skipped tools; only the lifecycle (start/end) events are suppressed.
|
|
45
|
+
|
|
3
46
|
## [0.1.9] - 2026-05-19
|
|
4
47
|
|
|
5
48
|
Compatibility refresh for **langgraph 1.2**, **langchain-core 1.4**, and **deepagents 0.6**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langgraph-stream-parser
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Universal parser for LangGraph streaming outputs
|
|
5
5
|
Project-URL: Homepage, https://github.com/dkedar7/langgraph-stream-parser
|
|
6
6
|
Project-URL: Documentation, https://github.com/dkedar7/langgraph-stream-parser#readme
|
|
@@ -49,26 +49,26 @@ Description-Content-Type: text/markdown
|
|
|
49
49
|
|
|
50
50
|
Universal parser for LangGraph streaming outputs. Normalizes complex, variable output shapes from `graph.stream()` and `graph.astream()` into consistent, typed event objects.
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Every stage for your LangGraph agent
|
|
53
53
|
|
|
54
|
-
`langgraph-stream-parser` is the shared core of the **
|
|
54
|
+
`langgraph-stream-parser` is the shared core of the **[LangStage](https://github.com/dkedar7/langstage) family**: write your agent once — any LangGraph `CompiledGraph` — and run it on every stage with the same spec string (`module:attr` or `path/to/file.py:attr`), the same `langstage.toml` config file, and the same `LANGSTAGE_*` environment variables. (The pre-rename `deepagents.toml` / `DEEPAGENT_*` vocabulary still resolves as a deprecated fallback.)
|
|
55
55
|
|
|
56
|
-
|
|
|
56
|
+
| Stage | Package | Try it |
|
|
57
57
|
|---|---|---|
|
|
58
|
-
| Web app | [
|
|
59
|
-
| JupyterLab | [
|
|
60
|
-
| Terminal | [
|
|
61
|
-
| VS Code | [
|
|
62
|
-
| Reference agent | [
|
|
58
|
+
| Web app | [langstage](https://github.com/dkedar7/langstage) | `langstage run --agent my_agent.py:graph` |
|
|
59
|
+
| JupyterLab | [langstage-jupyter](https://github.com/dkedar7/langstage-jupyter) | `pip install langstage-jupyter`, then the chat sidebar in `jupyter lab` |
|
|
60
|
+
| Terminal | [langstage-cli](https://github.com/dkedar7/langstage-cli) | `langstage-cli -a my_agent.py:graph` |
|
|
61
|
+
| VS Code | [langstage-vscode](https://github.com/dkedar7/langstage-vscode) | chat participant + stdio sidecar |
|
|
62
|
+
| Reference agent | [langstage-hermes](https://github.com/dkedar7/langstage-hermes) | `LANGSTAGE_AGENT_SPEC=langstage_hermes.agent:graph` on any stage |
|
|
63
63
|
| Shared core | langgraph-stream-parser | **you are here** |
|
|
64
64
|
|
|
65
|
-
No agent yet? Every
|
|
65
|
+
No agent yet? Every stage has a keyless demo mode backed by this package's stub agent — no API key required:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
export
|
|
68
|
+
export LANGSTAGE_AGENT_SPEC=langgraph_stream_parser.demo.stub:graph # or each CLI's --demo flag
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
And the resolved configuration (each value, its source, and the env var / `
|
|
71
|
+
And the resolved configuration (each value, its source, and the env var / `langstage.toml` key that sets it) is printable everywhere: `python -m langgraph_stream_parser.host`, or each CLI's `--show-config`.
|
|
72
72
|
|
|
73
73
|
## Installation
|
|
74
74
|
|
|
@@ -6,26 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
Universal parser for LangGraph streaming outputs. Normalizes complex, variable output shapes from `graph.stream()` and `graph.astream()` into consistent, typed event objects.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Every stage for your LangGraph agent
|
|
10
10
|
|
|
11
|
-
`langgraph-stream-parser` is the shared core of the **
|
|
11
|
+
`langgraph-stream-parser` is the shared core of the **[LangStage](https://github.com/dkedar7/langstage) family**: write your agent once — any LangGraph `CompiledGraph` — and run it on every stage with the same spec string (`module:attr` or `path/to/file.py:attr`), the same `langstage.toml` config file, and the same `LANGSTAGE_*` environment variables. (The pre-rename `deepagents.toml` / `DEEPAGENT_*` vocabulary still resolves as a deprecated fallback.)
|
|
12
12
|
|
|
13
|
-
|
|
|
13
|
+
| Stage | Package | Try it |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| Web app | [
|
|
16
|
-
| JupyterLab | [
|
|
17
|
-
| Terminal | [
|
|
18
|
-
| VS Code | [
|
|
19
|
-
| Reference agent | [
|
|
15
|
+
| Web app | [langstage](https://github.com/dkedar7/langstage) | `langstage run --agent my_agent.py:graph` |
|
|
16
|
+
| JupyterLab | [langstage-jupyter](https://github.com/dkedar7/langstage-jupyter) | `pip install langstage-jupyter`, then the chat sidebar in `jupyter lab` |
|
|
17
|
+
| Terminal | [langstage-cli](https://github.com/dkedar7/langstage-cli) | `langstage-cli -a my_agent.py:graph` |
|
|
18
|
+
| VS Code | [langstage-vscode](https://github.com/dkedar7/langstage-vscode) | chat participant + stdio sidecar |
|
|
19
|
+
| Reference agent | [langstage-hermes](https://github.com/dkedar7/langstage-hermes) | `LANGSTAGE_AGENT_SPEC=langstage_hermes.agent:graph` on any stage |
|
|
20
20
|
| Shared core | langgraph-stream-parser | **you are here** |
|
|
21
21
|
|
|
22
|
-
No agent yet? Every
|
|
22
|
+
No agent yet? Every stage has a keyless demo mode backed by this package's stub agent — no API key required:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
export
|
|
25
|
+
export LANGSTAGE_AGENT_SPEC=langgraph_stream_parser.demo.stub:graph # or each CLI's --demo flag
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
And the resolved configuration (each value, its source, and the env var / `
|
|
28
|
+
And the resolved configuration (each value, its source, and the env var / `langstage.toml` key that sets it) is printable everywhere: `python -m langgraph_stream_parser.host`, or each CLI's `--show-config`.
|
|
29
29
|
|
|
30
30
|
## Installation
|
|
31
31
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""A keyless, deterministic stub agent — the engine behind every ``--demo`` mode.
|
|
2
2
|
|
|
3
|
-
Every
|
|
4
|
-
|
|
3
|
+
Every LangStage surface (langstage, langstage-cli, langstage-jupyter,
|
|
4
|
+
langstage-vscode) offers a demo mode so a new user can see the surface working
|
|
5
5
|
before configuring a real agent or any API key. This module is the single
|
|
6
6
|
shared implementation: a real compiled LangGraph graph with a checkpointer,
|
|
7
7
|
streaming token-by-token through the exact same parser path as a production
|
|
@@ -9,7 +9,7 @@ agent — but the "model" is a local echo, so it needs no network and no keys.
|
|
|
9
9
|
|
|
10
10
|
Point any surface at it with the standard spec string::
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
LANGSTAGE_AGENT_SPEC=langgraph_stream_parser.demo.stub:graph
|
|
13
13
|
|
|
14
14
|
or build a customized one in code::
|
|
15
15
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Host conventions shared across the LangStage surfaces.
|
|
2
|
+
|
|
3
|
+
Agent-spec loading, the shared ``LANGSTAGE_*`` config schema (legacy
|
|
4
|
+
``DEEPAGENT_*`` still resolves), and a workspace wrapper — the plumbing every
|
|
5
|
+
host (``langstage``, ``langstage-jupyter``, ``langstage-cli``,
|
|
6
|
+
``langstage-vscode``) needs but used to reimplement.
|
|
7
|
+
"""
|
|
8
|
+
from .config import HostConfig, load_toml_config
|
|
9
|
+
from .loader import load_agent_spec
|
|
10
|
+
from .workspace import Workspace
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"load_agent_spec",
|
|
14
|
+
"HostConfig",
|
|
15
|
+
"load_toml_config",
|
|
16
|
+
"Workspace",
|
|
17
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""``python -m langgraph_stream_parser.host`` — print the resolved shared config.
|
|
2
|
+
|
|
3
|
+
Shows each ``LANGSTAGE_*`` value (legacy ``DEEPAGENT_*`` names still resolve),
|
|
4
|
+
where it resolved from (default / TOML / env / override), and the env var +
|
|
5
|
+
``langstage.toml`` key that set it — so you never have to remember the
|
|
6
|
+
variable names. Hosts can ship their own subclass printer for host-specific
|
|
7
|
+
keys; this covers the shared core.
|
|
8
|
+
"""
|
|
9
|
+
from .config import HostConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> int:
|
|
13
|
+
print(HostConfig.resolve().describe())
|
|
14
|
+
return 0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
raise SystemExit(main())
|
|
@@ -1,21 +1,30 @@
|
|
|
1
|
-
"""Shared ``
|
|
1
|
+
"""Shared ``LANGSTAGE_*`` configuration for hosts.
|
|
2
2
|
|
|
3
3
|
``HostConfig`` holds the keys every host has in common (agent spec, workspace
|
|
4
4
|
root, bind/title basics) and resolves them from one layered chain:
|
|
5
5
|
|
|
6
|
-
defaults <
|
|
6
|
+
defaults < langstage.toml < LANGSTAGE_* env vars < explicit overrides
|
|
7
7
|
|
|
8
8
|
Host-specific keys (theme, auth, model, Jupyter token, ...) belong in each
|
|
9
9
|
host's own subclass — drift *below* the shared core is fine — but every host
|
|
10
10
|
gets the same resolution order, the same TOML files, and the same env-var
|
|
11
11
|
names, so there's one place to look.
|
|
12
12
|
|
|
13
|
+
Legacy vocabulary (the pre-LangStage names) still works everywhere as a
|
|
14
|
+
deprecated fallback: ``DEEPAGENT_*`` env vars, project ``deepagents.toml``,
|
|
15
|
+
global ``~/.deepagents/config.toml``, and ``DEEPAGENTS_CONFIG_HOME``. The
|
|
16
|
+
canonical names win when both are set; using only the legacy env names emits
|
|
17
|
+
a once-per-var ``DeprecationWarning``. Moving the global config out of
|
|
18
|
+
``~/.deepagents/`` also exits the schema collision with LangChain's dcode,
|
|
19
|
+
which owns that directory now.
|
|
20
|
+
|
|
13
21
|
Discoverability: ``HostConfig.resolve().describe()`` (or
|
|
14
22
|
``python -m langgraph_stream_parser.host``) prints each value, where it came
|
|
15
23
|
from, and the env var / TOML key that sets it — so you never have to remember
|
|
16
|
-
|
|
24
|
+
the variable names.
|
|
17
25
|
"""
|
|
18
26
|
import os
|
|
27
|
+
import warnings
|
|
19
28
|
from dataclasses import MISSING, dataclass, fields, replace
|
|
20
29
|
from pathlib import Path
|
|
21
30
|
from typing import Any, Callable, ClassVar
|
|
@@ -28,8 +37,40 @@ except ModuleNotFoundError: # pragma: no cover - 3.10 path
|
|
|
28
37
|
except ModuleNotFoundError:
|
|
29
38
|
_tomllib = None # type: ignore
|
|
30
39
|
|
|
31
|
-
GLOBAL_TOML = Path.home() / ".
|
|
32
|
-
PROJECT_TOML = "
|
|
40
|
+
GLOBAL_TOML = Path.home() / ".langstage" / "config.toml"
|
|
41
|
+
PROJECT_TOML = "langstage.toml"
|
|
42
|
+
# Pre-rename locations, still honoured as fallbacks.
|
|
43
|
+
LEGACY_GLOBAL_TOML = Path.home() / ".deepagents" / "config.toml"
|
|
44
|
+
LEGACY_PROJECT_TOML = "deepagents.toml"
|
|
45
|
+
|
|
46
|
+
_CANONICAL_ENV_PREFIX = "LANGSTAGE"
|
|
47
|
+
_LEGACY_ENV_PREFIX = "DEEPAGENT"
|
|
48
|
+
|
|
49
|
+
_warned_legacy_env: set[str] = set()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _env_pair(declared: str) -> tuple[str, str]:
|
|
53
|
+
"""Return ``(canonical, legacy)`` env-var names for a declared name.
|
|
54
|
+
|
|
55
|
+
Hosts may declare either spelling in their ``_ENV`` maps during the
|
|
56
|
+
rename transition; both resolve, canonical wins.
|
|
57
|
+
"""
|
|
58
|
+
if declared.startswith(_CANONICAL_ENV_PREFIX):
|
|
59
|
+
return declared, _LEGACY_ENV_PREFIX + declared[len(_CANONICAL_ENV_PREFIX):]
|
|
60
|
+
if declared.startswith(_LEGACY_ENV_PREFIX):
|
|
61
|
+
return _CANONICAL_ENV_PREFIX + declared[len(_LEGACY_ENV_PREFIX):], declared
|
|
62
|
+
return declared, declared
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _warn_legacy_env(legacy: str, canonical: str) -> None:
|
|
66
|
+
if legacy in _warned_legacy_env:
|
|
67
|
+
return
|
|
68
|
+
_warned_legacy_env.add(legacy)
|
|
69
|
+
warnings.warn(
|
|
70
|
+
f"{legacy} is deprecated; use {canonical}.",
|
|
71
|
+
DeprecationWarning,
|
|
72
|
+
stacklevel=4,
|
|
73
|
+
)
|
|
33
74
|
|
|
34
75
|
|
|
35
76
|
def _env_bool(value: str | None, default: bool = False) -> bool:
|
|
@@ -43,17 +84,27 @@ def _env_bool(value: str | None, default: bool = False) -> bool:
|
|
|
43
84
|
|
|
44
85
|
|
|
45
86
|
def _global_toml_path() -> Path:
|
|
46
|
-
override = os.getenv("DEEPAGENTS_CONFIG_HOME")
|
|
47
|
-
|
|
87
|
+
override = os.getenv("LANGSTAGE_CONFIG_HOME") or os.getenv("DEEPAGENTS_CONFIG_HOME")
|
|
88
|
+
if override:
|
|
89
|
+
return Path(override).expanduser() / "config.toml"
|
|
90
|
+
# New home wins when present; otherwise fall back to the legacy location
|
|
91
|
+
# (which load_toml_config skips anyway if the file doesn't exist).
|
|
92
|
+
return GLOBAL_TOML if GLOBAL_TOML.is_file() else LEGACY_GLOBAL_TOML
|
|
48
93
|
|
|
49
94
|
|
|
50
95
|
def _find_project_toml(start: Path | None = None) -> Path | None:
|
|
51
|
-
"""Walk up from ``start`` (or cwd) looking for ``
|
|
96
|
+
"""Walk up from ``start`` (or cwd) looking for ``langstage.toml``.
|
|
97
|
+
|
|
98
|
+
Checks ``langstage.toml`` then legacy ``deepagents.toml`` in each
|
|
99
|
+
directory, so the nearest file wins and the new name wins within a
|
|
100
|
+
directory.
|
|
101
|
+
"""
|
|
52
102
|
here = (start or Path.cwd()).resolve()
|
|
53
103
|
for directory in (here, *here.parents):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
for fname in (PROJECT_TOML, LEGACY_PROJECT_TOML):
|
|
105
|
+
candidate = directory / fname
|
|
106
|
+
if candidate.is_file():
|
|
107
|
+
return candidate
|
|
57
108
|
return None
|
|
58
109
|
|
|
59
110
|
|
|
@@ -75,11 +126,13 @@ def _read_toml(path: Path) -> dict:
|
|
|
75
126
|
|
|
76
127
|
|
|
77
128
|
def load_toml_config(start: Path | None = None) -> tuple[dict, list[Path]]:
|
|
78
|
-
"""Load + deep-merge the global and project ``
|
|
129
|
+
"""Load + deep-merge the global and project ``langstage.toml`` files.
|
|
79
130
|
|
|
80
|
-
Global is ``~/.
|
|
81
|
-
``
|
|
82
|
-
|
|
131
|
+
Global is ``~/.langstage/config.toml`` (override the dir with
|
|
132
|
+
``LANGSTAGE_CONFIG_HOME``; legacy ``~/.deepagents/config.toml`` and
|
|
133
|
+
``DEEPAGENTS_CONFIG_HOME`` still work as fallbacks); project is the
|
|
134
|
+
nearest ``langstage.toml`` — or legacy ``deepagents.toml`` — at or above
|
|
135
|
+
``start``/cwd. Project wins on conflicts. Returns
|
|
83
136
|
``(merged_config, sources_used)``; ``({}, [])`` if no TOML reader is
|
|
84
137
|
available (Python 3.10 without ``tomli``).
|
|
85
138
|
"""
|
|
@@ -120,28 +173,30 @@ class HostConfig:
|
|
|
120
173
|
@dataclass
|
|
121
174
|
class WebConfig(HostConfig):
|
|
122
175
|
theme: str = "auto"
|
|
123
|
-
_ENV = {"theme": ("
|
|
176
|
+
_ENV = {"theme": ("LANGSTAGE_THEME", str)}
|
|
124
177
|
_TOML = {"theme": "ui.theme"}
|
|
125
178
|
|
|
126
179
|
``resolve()`` merges the maps across the MRO, so the subclass inherits all
|
|
127
180
|
of ``HostConfig``'s keys and adds its own.
|
|
128
181
|
"""
|
|
129
182
|
|
|
130
|
-
agent_spec: str | None = None #
|
|
131
|
-
workspace_root: Path = Path(".") #
|
|
132
|
-
host: str = "localhost" #
|
|
133
|
-
port: int = 8050 #
|
|
134
|
-
debug: bool = False #
|
|
135
|
-
title: str = "
|
|
183
|
+
agent_spec: str | None = None # LANGSTAGE_AGENT_SPEC ("path.py:var")
|
|
184
|
+
workspace_root: Path = Path(".") # LANGSTAGE_WORKSPACE_ROOT
|
|
185
|
+
host: str = "localhost" # LANGSTAGE_HOST
|
|
186
|
+
port: int = 8050 # LANGSTAGE_PORT
|
|
187
|
+
debug: bool = False # LANGSTAGE_DEBUG
|
|
188
|
+
title: str = "LangStage" # LANGSTAGE_TITLE
|
|
136
189
|
|
|
137
|
-
# field -> (
|
|
190
|
+
# field -> (env var, caster). Canonical names are LANGSTAGE_*; the
|
|
191
|
+
# matching DEEPAGENT_* legacy names resolve as deprecated fallbacks
|
|
192
|
+
# (see _env_pair).
|
|
138
193
|
_ENV: ClassVar[dict[str, tuple[str, Callable[[str], Any]]]] = {
|
|
139
|
-
"agent_spec": ("
|
|
140
|
-
"workspace_root": ("
|
|
141
|
-
"host": ("
|
|
142
|
-
"port": ("
|
|
143
|
-
"debug": ("
|
|
144
|
-
"title": ("
|
|
194
|
+
"agent_spec": ("LANGSTAGE_AGENT_SPEC", str),
|
|
195
|
+
"workspace_root": ("LANGSTAGE_WORKSPACE_ROOT", Path),
|
|
196
|
+
"host": ("LANGSTAGE_HOST", str),
|
|
197
|
+
"port": ("LANGSTAGE_PORT", int),
|
|
198
|
+
"debug": ("LANGSTAGE_DEBUG", _env_bool),
|
|
199
|
+
"title": ("LANGSTAGE_TITLE", str),
|
|
145
200
|
}
|
|
146
201
|
# field -> dotted key in deepagents.toml
|
|
147
202
|
_TOML: ClassVar[dict[str, str]] = {
|
|
@@ -226,10 +281,17 @@ class HostConfig:
|
|
|
226
281
|
|
|
227
282
|
if name in env_map:
|
|
228
283
|
var, caster = env_map[name]
|
|
229
|
-
|
|
284
|
+
canonical, legacy = _env_pair(var)
|
|
285
|
+
ev = env.get(canonical)
|
|
286
|
+
used = canonical
|
|
287
|
+
if ev is None or ev == "":
|
|
288
|
+
ev = env.get(legacy)
|
|
289
|
+
used = legacy
|
|
290
|
+
if ev not in (None, "") and legacy != canonical:
|
|
291
|
+
_warn_legacy_env(legacy, canonical)
|
|
230
292
|
if ev is not None and ev != "":
|
|
231
293
|
val = caster(ev)
|
|
232
|
-
src = f"env:{
|
|
294
|
+
src = f"env:{used}"
|
|
233
295
|
|
|
234
296
|
if name in overrides:
|
|
235
297
|
val = overrides[name]
|
|
@@ -270,7 +332,11 @@ class HostConfig:
|
|
|
270
332
|
origin = src.get(f.name, "default")
|
|
271
333
|
hints = []
|
|
272
334
|
if f.name in env_map:
|
|
273
|
-
|
|
335
|
+
canonical, legacy = _env_pair(env_map[f.name][0])
|
|
336
|
+
env_hint = f"env: {canonical}"
|
|
337
|
+
if legacy != canonical:
|
|
338
|
+
env_hint += f" (legacy {legacy})"
|
|
339
|
+
hints.append(env_hint)
|
|
274
340
|
if f.name in toml_map:
|
|
275
341
|
hints.append(f"toml: {toml_map[f.name]}")
|
|
276
342
|
hint = f" ({', '.join(hints)})" if hints else ""
|
|
@@ -280,7 +346,7 @@ class HostConfig:
|
|
|
280
346
|
if toml_paths:
|
|
281
347
|
lines.append(" TOML read from: " + ", ".join(str(p) for p in toml_paths))
|
|
282
348
|
else:
|
|
283
|
-
lines.append(" TOML: no deepagents.toml found")
|
|
349
|
+
lines.append(" TOML: no langstage.toml (or legacy deepagents.toml) found")
|
|
284
350
|
return "\n".join(lines)
|
|
285
351
|
|
|
286
352
|
|
|
@@ -67,7 +67,7 @@ class TestHostConfig:
|
|
|
67
67
|
assert cfg.host == "localhost"
|
|
68
68
|
assert cfg.port == 8050
|
|
69
69
|
assert cfg.debug is False
|
|
70
|
-
assert cfg.title == "
|
|
70
|
+
assert cfg.title == "LangStage"
|
|
71
71
|
|
|
72
72
|
def test_from_env(self, monkeypatch):
|
|
73
73
|
monkeypatch.setenv("DEEPAGENT_AGENT_SPEC", "agent.py:graph")
|
|
@@ -111,6 +111,85 @@ class TestSubclass:
|
|
|
111
111
|
assert "DEEPAGENT_THEME" in cfg.describe()
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
class TestLangstageVocabulary:
|
|
115
|
+
"""Canonical LANGSTAGE_* / langstage.toml with deprecated legacy fallbacks."""
|
|
116
|
+
|
|
117
|
+
def test_canonical_env_resolves(self, isolated_global, tmp_path):
|
|
118
|
+
cfg = HostConfig.resolve(
|
|
119
|
+
env={"LANGSTAGE_AGENT_SPEC": "a.py:g", "LANGSTAGE_PORT": "9100"},
|
|
120
|
+
toml_start=tmp_path,
|
|
121
|
+
)
|
|
122
|
+
assert cfg.agent_spec == "a.py:g"
|
|
123
|
+
assert cfg.port == 9100
|
|
124
|
+
assert cfg.sources["agent_spec"] == "env:LANGSTAGE_AGENT_SPEC"
|
|
125
|
+
|
|
126
|
+
def test_canonical_beats_legacy(self, isolated_global, tmp_path):
|
|
127
|
+
cfg = HostConfig.resolve(
|
|
128
|
+
env={"LANGSTAGE_PORT": "1111", "DEEPAGENT_PORT": "2222"},
|
|
129
|
+
toml_start=tmp_path,
|
|
130
|
+
)
|
|
131
|
+
assert cfg.port == 1111
|
|
132
|
+
assert cfg.sources["port"] == "env:LANGSTAGE_PORT"
|
|
133
|
+
|
|
134
|
+
def test_legacy_env_warns_once(self, isolated_global, tmp_path):
|
|
135
|
+
import langgraph_stream_parser.host.config as config_mod
|
|
136
|
+
|
|
137
|
+
config_mod._warned_legacy_env.discard("DEEPAGENT_TITLE")
|
|
138
|
+
with pytest.warns(DeprecationWarning, match="LANGSTAGE_TITLE"):
|
|
139
|
+
HostConfig.resolve(env={"DEEPAGENT_TITLE": "Old"}, toml_start=tmp_path)
|
|
140
|
+
|
|
141
|
+
def test_langstage_toml_preferred_in_same_dir(self, isolated_global, tmp_path):
|
|
142
|
+
(tmp_path / "deepagents.toml").write_text("[server]\nport = 1000\n")
|
|
143
|
+
(tmp_path / "langstage.toml").write_text("[server]\nport = 2000\n")
|
|
144
|
+
cfg = HostConfig.resolve(env={}, toml_start=tmp_path)
|
|
145
|
+
assert cfg.port == 2000
|
|
146
|
+
|
|
147
|
+
def test_legacy_toml_still_read(self, isolated_global, tmp_path):
|
|
148
|
+
(tmp_path / "deepagents.toml").write_text("[server]\nport = 1234\n")
|
|
149
|
+
cfg = HostConfig.resolve(env={}, toml_start=tmp_path)
|
|
150
|
+
assert cfg.port == 1234
|
|
151
|
+
|
|
152
|
+
def test_nearest_toml_wins_across_dirs(self, isolated_global, tmp_path):
|
|
153
|
+
# langstage.toml in the parent must NOT beat deepagents.toml in cwd.
|
|
154
|
+
(tmp_path / "langstage.toml").write_text("[server]\nport = 1000\n")
|
|
155
|
+
sub = tmp_path / "sub"
|
|
156
|
+
sub.mkdir()
|
|
157
|
+
(sub / "deepagents.toml").write_text("[server]\nport = 2000\n")
|
|
158
|
+
cfg = HostConfig.resolve(env={}, toml_start=sub)
|
|
159
|
+
assert cfg.port == 2000
|
|
160
|
+
|
|
161
|
+
def test_langstage_config_home_override(self, tmp_path, monkeypatch):
|
|
162
|
+
gdir = tmp_path / "newhome"
|
|
163
|
+
gdir.mkdir()
|
|
164
|
+
(gdir / "config.toml").write_text("[server]\nport = 4321\n")
|
|
165
|
+
monkeypatch.delenv("DEEPAGENTS_CONFIG_HOME", raising=False)
|
|
166
|
+
monkeypatch.setenv("LANGSTAGE_CONFIG_HOME", str(gdir))
|
|
167
|
+
cfg = HostConfig.resolve(env={}, toml_start=tmp_path)
|
|
168
|
+
assert cfg.port == 4321
|
|
169
|
+
|
|
170
|
+
def test_describe_shows_both_vocabularies(self, isolated_global, tmp_path):
|
|
171
|
+
text = HostConfig.resolve(env={}, toml_start=tmp_path).describe()
|
|
172
|
+
assert "LANGSTAGE_AGENT_SPEC" in text
|
|
173
|
+
assert "legacy DEEPAGENT_AGENT_SPEC" in text
|
|
174
|
+
|
|
175
|
+
def test_subclass_legacy_declaration_resolves_canonical_name(
|
|
176
|
+
self, isolated_global, tmp_path
|
|
177
|
+
):
|
|
178
|
+
"""A host still declaring DEEPAGENT_* in its _ENV map picks up the
|
|
179
|
+
LANGSTAGE_* var without any subclass change."""
|
|
180
|
+
from dataclasses import dataclass
|
|
181
|
+
from typing import ClassVar
|
|
182
|
+
|
|
183
|
+
@dataclass
|
|
184
|
+
class OldHost(HostConfig):
|
|
185
|
+
theme: str = "auto"
|
|
186
|
+
_ENV: ClassVar[dict] = {"theme": ("DEEPAGENT_THEME", str)}
|
|
187
|
+
|
|
188
|
+
cfg = OldHost.resolve(env={"LANGSTAGE_THEME": "dark"}, toml_start=tmp_path)
|
|
189
|
+
assert cfg.theme == "dark"
|
|
190
|
+
assert cfg.sources["theme"] == "env:LANGSTAGE_THEME"
|
|
191
|
+
|
|
192
|
+
|
|
114
193
|
class TestFromEnvBackCompat:
|
|
115
194
|
def test_from_env_skips_toml(self, isolated_global, tmp_path, monkeypatch):
|
|
116
195
|
_toml(tmp_path, "[server]\nport = 1234\n")
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"""Host conventions shared across deep-agent surfaces.
|
|
2
|
-
|
|
3
|
-
Agent-spec loading, the shared ``DEEPAGENT_*`` config schema, and a workspace
|
|
4
|
-
wrapper — the plumbing every host (``cowork-dash``, ``deepagent-lab``,
|
|
5
|
-
``deepagent-code``, ``deepagent-vscode``) needs but used to reimplement.
|
|
6
|
-
"""
|
|
7
|
-
from .config import HostConfig, load_toml_config
|
|
8
|
-
from .loader import load_agent_spec
|
|
9
|
-
from .workspace import Workspace
|
|
10
|
-
|
|
11
|
-
__all__ = [
|
|
12
|
-
"load_agent_spec",
|
|
13
|
-
"HostConfig",
|
|
14
|
-
"load_toml_config",
|
|
15
|
-
"Workspace",
|
|
16
|
-
]
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""``python -m langgraph_stream_parser.host`` — print the resolved shared config.
|
|
2
|
-
|
|
3
|
-
Shows each ``DEEPAGENT_*`` value, where it resolved from (default / TOML / env /
|
|
4
|
-
override), and the env var + ``deepagents.toml`` key that set it — so you never
|
|
5
|
-
have to remember the variable names. Hosts can ship their own subclass printer
|
|
6
|
-
for host-specific keys; this covers the shared core.
|
|
7
|
-
"""
|
|
8
|
-
from .config import HostConfig
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def main() -> int:
|
|
12
|
-
print(HostConfig.resolve().describe())
|
|
13
|
-
return 0
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if __name__ == "__main__":
|
|
17
|
-
raise SystemExit(main())
|
|
File without changes
|
{langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/.github/workflows/release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/examples/fastapi_websocket.py
RENAMED
|
File without changes
|
{langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/examples/jupyter_example.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_fastapi_adapter.py
RENAMED
|
File without changes
|
{langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_generic_extractor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_reasoning_display.py
RENAMED
|
File without changes
|
|
File without changes
|
{langgraph_stream_parser-0.2.2 → langgraph_stream_parser-0.3.0}/tests/test_session_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|