deepagent-vscode 0.2.0__tar.gz → 0.2.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.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepagent-vscode
3
+ Version: 0.2.1
4
+ Summary: RENAMED: this package is now 'langstage-vscode' (LangStage family). Installing this pulls the new package.
5
+ Project-URL: Homepage, https://github.com/dkedar7/langstage-vscode
6
+ Author-email: Kedar Dabhadkar <kdabhadk@gmail.com>
7
+ License: MIT
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: langstage-vscode>=0.3.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # deepagent-vscode → langstage-vscode
13
+
14
+ **This package has been renamed to [langstage-vscode](https://pypi.org/project/langstage-vscode/)** — part of the
15
+ LangStage family rename ("every stage for your LangGraph agent").
16
+
17
+ This final `deepagent-vscode` release simply depends on `langstage-vscode`, so existing installs keep
18
+ working: the new package ships a deprecated import-alias for the old module
19
+ name and keeps the old console command as an alias.
20
+
21
+ Please update your dependencies:
22
+
23
+ ```bash
24
+ pip install langstage-vscode
25
+ ```
@@ -0,0 +1,14 @@
1
+ # deepagent-vscode → langstage-vscode
2
+
3
+ **This package has been renamed to [langstage-vscode](https://pypi.org/project/langstage-vscode/)** — part of the
4
+ LangStage family rename ("every stage for your LangGraph agent").
5
+
6
+ This final `deepagent-vscode` release simply depends on `langstage-vscode`, so existing installs keep
7
+ working: the new package ships a deprecated import-alias for the old module
8
+ name and keeps the old console command as an alias.
9
+
10
+ Please update your dependencies:
11
+
12
+ ```bash
13
+ pip install langstage-vscode
14
+ ```
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "deepagent-vscode"
7
+ version = "0.2.1"
8
+ description = "RENAMED: this package is now 'langstage-vscode' (LangStage family). Installing this pulls the new package."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Kedar Dabhadkar", email = "kdabhadk@gmail.com" }]
13
+ dependencies = ["langstage-vscode>=0.3.0"]
14
+
15
+ [project.urls]
16
+ Homepage = "https://github.com/dkedar7/langstage-vscode"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ bypass-selection = true
@@ -1,12 +0,0 @@
1
- # Python
2
- __pycache__/
3
- *.pyc
4
- *.egg-info/
5
- .venv/
6
- build/
7
- dist/
8
-
9
- # Node / VS Code extension
10
- extension/node_modules/
11
- extension/dist/
12
- *.vsix
@@ -1,23 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- ## [0.2.0] - 2026-06-10
6
-
7
- First PyPI release of the sidecar (`pip install deepagent-vscode`).
8
-
9
- ### Added
10
-
11
- - **Family-standard config chain** — the sidecar resolves through the shared `HostConfig`: defaults < `deepagents.toml` (global + project) < `DEEPAGENT_*` env < CLI flags. A project `deepagents.toml` with `[agent] spec = "..."` now just works.
12
- - **`--demo`** — run with the shared keyless echo agent (`langgraph_stream_parser.demo.stub:graph`); no API key needed.
13
- - **`--show-config`** — print each resolved value with its source and the env var / TOML key that sets it.
14
- - **Extension**: an empty `deepagent.agentSpec` setting no longer hard-errors — the extension spawns the sidecar without `--agent` (cwd anchored at the workspace) and lets the config chain resolve.
15
- - README: *One agent, every surface* family table.
16
-
17
- ### Changed
18
-
19
- - `langgraph-stream-parser` pinned `>=0.2.2,<0.3`.
20
-
21
- ## [0.1.0] - 2026-06-04
22
-
23
- Initial version (GitHub only): stdio sidecar bridging LangGraph agents to the `@deepagent` VS Code chat participant, speaking the `langgraph-stream-parser` `event.to_dict()` wire vocabulary.
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Kedar Dabhadkar
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,16 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: deepagent-vscode
3
- Version: 0.2.0
4
- Summary: Python stdio sidecar bridging LangGraph/deepagents agents to the deepagent-vscode chat extension
5
- Author: Kedar Dabhadkar
6
- License-Expression: MIT
7
- License-File: LICENSE
8
- Keywords: agents,ai,deepagents,langgraph,vscode
9
- Requires-Python: >=3.11
10
- Requires-Dist: langgraph-stream-parser<0.3,>=0.2.2
11
- Provides-Extra: demo
12
- Requires-Dist: deepagents>=0.3; extra == 'demo'
13
- Provides-Extra: dev
14
- Requires-Dist: langchain-core>=1.4.0; extra == 'dev'
15
- Requires-Dist: langgraph>=1.1.0; extra == 'dev'
16
- Requires-Dist: pytest>=8; extra == 'dev'
@@ -1,158 +0,0 @@
1
- <p align="center">
2
- <img src="assets/header.svg" alt="deepagent-vscode" width="100%">
3
- </p>
4
-
5
- # deepagent-vscode
6
-
7
- Chat with your own **LangGraph / deepagents** agent from inside VS Code — in the
8
- same chat panel as Copilot — via the `@deepagent` chat participant.
9
-
10
- It has two parts in one repo:
11
-
12
- - **`extension/`** — a TypeScript VS Code extension that registers the
13
- `@deepagent` chat participant and renders agent output in the chat view.
14
- - **`deepagent_vscode/`** — a small Python **stdio sidecar** that loads your
15
- agent and streams its events. Built on
16
- [`langgraph-stream-parser`](https://github.com/dkedar7/langgraph-stream-parser),
17
- so it speaks the same typed event vocabulary as the other deep-agent surfaces
18
- (`cowork-dash`, `deepagent-lab`, `deepagent-code`).
19
-
20
- ```
21
- ┌─ VS Code chat panel ────────────────────────────┐
22
- │ @deepagent (TypeScript extension) │
23
- │ │ spawns │
24
- │ ▼ │
25
- │ python -m deepagent_vscode (stdio sidecar) │
26
- │ │ NDJSON over stdin/stdout │
27
- │ ▼ │
28
- │ your LangGraph / deepagents agent │
29
- └──────────────────────────────────────────────────┘
30
- ```
31
-
32
- > **Status: early.** The extension is not yet on the VS Code Marketplace (run
33
- > it from source for now), and interactive approval of human-in-the-loop
34
- > interrupts is not wired into the chat UI yet (the sidecar already supports
35
- > the round-trip).
36
-
37
- ## One agent, every surface
38
-
39
- deepagent-vscode is the VS Code surface of the **deep-agent family**: write your agent once — any LangGraph `CompiledGraph` — and run it on every surface with the same spec string (`module:attr` or `path/to/file.py:attr`), the same `deepagents.toml` config file, and the same `DEEPAGENT_*` environment variables.
40
-
41
- | Surface | Package | Try it |
42
- |---|---|---|
43
- | Web app | [cowork-dash](https://github.com/dkedar7/cowork-dash) | `cowork-dash run --agent my_agent.py:graph` |
44
- | JupyterLab | [deepagent-lab](https://github.com/dkedar7/deepagent-lab) | `pip install deepagent-lab`, then the chat sidebar in `jupyter lab` |
45
- | Terminal | [deepagent-code](https://github.com/dkedar7/deepagent-code) | `deepagent-code -a my_agent.py:graph` |
46
- | VS Code | deepagent-vscode | **you are here** |
47
- | Reference agent | [deepagent-hermes](https://github.com/dkedar7/deepagent-hermes) | `DEEPAGENT_AGENT_SPEC=deepagent_hermes.agent:graph` on any surface |
48
- | Shared core | [langgraph-stream-parser](https://github.com/dkedar7/langgraph-stream-parser) | typed events + config resolver behind every surface |
49
-
50
- ## Install
51
-
52
- ### Sidecar (Python)
53
-
54
- ```bash
55
- pip install deepagent-vscode
56
- # or, for a quick try with the bundled default agent:
57
- pip install "deepagent-vscode[demo]"
58
- ```
59
-
60
- ### Extension (from source, until it's on the Marketplace)
61
-
62
- ```bash
63
- cd extension
64
- npm install
65
- npm run compile
66
- ```
67
-
68
- Then press **F5** in VS Code (with the `extension/` folder open) to launch an
69
- Extension Development Host with `@deepagent` available.
70
-
71
- ## Configure
72
-
73
- In VS Code settings:
74
-
75
- | Setting | Description | Default |
76
- |---|---|---|
77
- | `deepagent.agentSpec` | Your agent, as `path/to/agent.py:graph` or `module:graph` | _(falls back to `DEEPAGENT_AGENT_SPEC` / `deepagents.toml`)_ |
78
- | `deepagent.pythonPath` | Python interpreter that has `deepagent-vscode` installed | `python` |
79
-
80
- The sidecar resolves its configuration through the family-standard chain —
81
- **defaults < `deepagents.toml` (global + project) < `DEEPAGENT_*` env < CLI
82
- flags** — so a project with `[agent] spec = "my_agent.py:graph"` in its
83
- `deepagents.toml` needs no VS Code setting at all. Inspect the resolved values:
84
-
85
- ```bash
86
- deepagent-vscode-sidecar --show-config
87
- ```
88
-
89
- Your agent is any LangGraph `CompiledGraph` (e.g. from `deepagents`), exported
90
- under the name in the spec:
91
-
92
- ```python
93
- # my_agent.py
94
- from deepagents import create_deep_agent
95
- graph = create_deep_agent(...) # -> deepagent.agentSpec = "my_agent.py:graph"
96
- ```
97
-
98
- ## Usage
99
-
100
- Open the chat panel and start a message with `@deepagent`:
101
-
102
- ```
103
- @deepagent summarize the failing tests in this repo and propose a fix
104
- ```
105
-
106
- The extension streams the agent's content, tool calls, reasoning, and todo
107
- updates into the chat response.
108
-
109
- ## Sidecar protocol
110
-
111
- The extension talks to the sidecar over newline-delimited JSON. You can drive it
112
- directly for testing:
113
-
114
- ```bash
115
- DEEPAGENT_AGENT_SPEC=./my_agent.py:graph python -m deepagent_vscode
116
-
117
- # or with no agent and no API key at all:
118
- python -m deepagent_vscode --demo
119
- ```
120
-
121
- **Commands** (client → sidecar), one JSON object per line:
122
-
123
- ```jsonc
124
- {"type": "message", "session_id": "s1", "content": "hello"}
125
- {"type": "decision", "session_id": "s1", "decisions": [{"type": "approve"}]}
126
- {"type": "shutdown"}
127
- ```
128
-
129
- **Events** (sidecar → client) — the `event.to_dict()` shapes from
130
- `langgraph-stream-parser`, plus a few protocol frames:
131
-
132
- ```jsonc
133
- {"type": "ready"} // emitted once at startup
134
- {"type": "ack", "ref": "message"} // command accepted
135
- {"type": "content", "content": "..."} // assistant text
136
- {"type": "tool_start", "name": "...", ...} // tool call
137
- {"type": "tool_end", "name": "...", ...} // tool result
138
- {"type": "interrupt", "action_requests": [...]} // human-in-the-loop
139
- {"type": "complete"} // turn finished
140
- {"type": "turn_end", "session_id": "s1"}
141
- ```
142
-
143
- ## Development
144
-
145
- ```bash
146
- # Sidecar
147
- pip install -e ".[dev]"
148
- pytest
149
-
150
- # Extension
151
- cd extension
152
- npm install
153
- npm run compile
154
- ```
155
-
156
- ## License
157
-
158
- MIT
@@ -1,81 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1280" height="340" viewBox="0 0 1280 340" role="img" aria-label="deepagent-vscode — chat with your LangGraph and deepagents agent inside VS Code">
2
- <defs>
3
- <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
- <stop offset="0" stop-color="#0B1221"/>
5
- <stop offset="1" stop-color="#0E1B2E"/>
6
- </linearGradient>
7
- <linearGradient id="accent" x1="0" y1="0" x2="1" y2="0">
8
- <stop offset="0" stop-color="#2F9BFF"/>
9
- <stop offset="1" stop-color="#34D6C8"/>
10
- </linearGradient>
11
- <radialGradient id="glow" cx="0.82" cy="0.15" r="0.6">
12
- <stop offset="0" stop-color="#2F9BFF" stop-opacity="0.22"/>
13
- <stop offset="1" stop-color="#2F9BFF" stop-opacity="0"/>
14
- </radialGradient>
15
- <filter id="soft" x="-20%" y="-20%" width="140%" height="140%">
16
- <feDropShadow dx="0" dy="6" stdDeviation="12" flood-color="#000000" flood-opacity="0.35"/>
17
- </filter>
18
- </defs>
19
-
20
- <!-- backdrop -->
21
- <rect width="1280" height="340" rx="20" fill="url(#bg)"/>
22
- <rect width="1280" height="340" rx="20" fill="url(#glow)"/>
23
- <rect x="1" y="1" width="1278" height="338" rx="19" fill="none" stroke="#21314A" stroke-width="1.5"/>
24
-
25
- <!-- ===== left: identity ===== -->
26
- <!-- @deepagent pill -->
27
- <g transform="translate(72,66)">
28
- <rect width="158" height="34" rx="17" fill="#0F2C3A" stroke="url(#accent)" stroke-width="1.5"/>
29
- <circle cx="22" cy="17" r="5" fill="url(#accent)"/>
30
- <text x="40" y="23" font-family="'SF Mono','JetBrains Mono','Consolas',monospace" font-size="16" fill="#9FE9DF">@deepagent</text>
31
- </g>
32
-
33
- <!-- title -->
34
- <text x="70" y="178" font-family="'SF Mono','JetBrains Mono','Consolas',monospace" font-size="62" font-weight="700" fill="#F2F6FC" letter-spacing="-1">deepagent<tspan fill="url(#accent)">-vscode</tspan></text>
35
-
36
- <!-- tagline -->
37
- <text x="72" y="224" font-family="'Segoe UI',Helvetica,Arial,sans-serif" font-size="22" fill="#9DB2C8">Chat with your LangGraph &#183; deepagents agent</text>
38
- <text x="72" y="254" font-family="'Segoe UI',Helvetica,Arial,sans-serif" font-size="22" fill="#9DB2C8">inside VS Code &#8212; right next to Copilot.</text>
39
-
40
- <!-- footer chips -->
41
- <g font-family="'SF Mono','JetBrains Mono','Consolas',monospace" font-size="13" fill="#6F8298">
42
- <text x="72" y="300">chat participant</text>
43
- <text x="214" y="300" fill="#34506A">&#8226;</text>
44
- <text x="236" y="300">python sidecar</text>
45
- <text x="372" y="300" fill="#34506A">&#8226;</text>
46
- <text x="394" y="300">langgraph-stream-parser</text>
47
- </g>
48
-
49
- <!-- ===== right: chat-panel mock ===== -->
50
- <g transform="translate(812,70)" filter="url(#soft)">
51
- <rect width="396" height="200" rx="14" fill="#0F2235" stroke="#23415C" stroke-width="1.5"/>
52
- <!-- title bar -->
53
- <rect width="396" height="38" rx="14" fill="#13314A"/>
54
- <rect y="24" width="396" height="14" fill="#13314A"/>
55
- <circle cx="24" cy="19" r="5" fill="#E06C75"/>
56
- <circle cx="44" cy="19" r="5" fill="#E5C07B"/>
57
- <circle cx="64" cy="19" r="5" fill="#56C28B"/>
58
- <text x="340" y="24" font-family="'SF Mono','Consolas',monospace" font-size="12" fill="#6F8298">CHAT</text>
59
-
60
- <!-- user bubble (right) -->
61
- <g transform="translate(150,58)">
62
- <rect width="220" height="34" rx="10" fill="#1C3A52"/>
63
- <text x="16" y="22" font-family="'Segoe UI',Arial,sans-serif" font-size="13" fill="#C7D6E6">fix the failing test</text>
64
- </g>
65
-
66
- <!-- assistant: @deepagent -->
67
- <g transform="translate(20,108)">
68
- <circle cx="9" cy="9" r="8" fill="url(#accent)"/>
69
- <text x="28" y="13" font-family="'SF Mono','Consolas',monospace" font-size="13" fill="#9FE9DF">@deepagent</text>
70
- <rect x="0" y="28" width="300" height="9" rx="4.5" fill="#27425C"/>
71
- <rect x="0" y="46" width="356" height="9" rx="4.5" fill="#27425C"/>
72
- <rect x="0" y="64" width="210" height="9" rx="4.5" fill="#27425C"/>
73
- <!-- tool chip -->
74
- <g transform="translate(0,82)">
75
- <rect width="150" height="22" rx="6" fill="#0C2A22" stroke="#34D6C8" stroke-width="1"/>
76
- <circle cx="15" cy="11" r="4" fill="#34D6C8"/>
77
- <text x="28" y="15" font-family="'SF Mono','Consolas',monospace" font-size="11" fill="#7FE3D6">run_tests &#10003;</text>
78
- </g>
79
- </g>
80
- </g>
81
- </svg>
@@ -1,11 +0,0 @@
1
- """deepagent-vscode — Python stdio sidecar for the VS Code chat extension.
2
-
3
- The extension spawns ``python -m deepagent_vscode`` (or the
4
- ``deepagent-vscode-sidecar`` console script); the sidecar bridges a
5
- LangGraph/deepagents agent to the chat participant over newline-delimited JSON.
6
- """
7
- from .sidecar import main, run
8
-
9
- __version__ = "0.1.0"
10
-
11
- __all__ = ["main", "run", "__version__"]
@@ -1,3 +0,0 @@
1
- from .sidecar import main
2
-
3
- raise SystemExit(main())
@@ -1,189 +0,0 @@
1
- """stdio sidecar bridging a LangGraph agent to the deepagent-vscode extension.
2
-
3
- Reads newline-delimited JSON commands on stdin, runs the agent through
4
- ``langgraph-stream-parser``, and writes newline-delimited JSON events on
5
- stdout. The events are exactly ``event.to_dict()`` shapes — the same wire
6
- vocabulary every other deep-agent surface (FastAPI WebSocket/SSE, Jupyter, CLI)
7
- emits — so the TS extension's dispatcher renders them the same way.
8
-
9
- Commands (client -> sidecar), one JSON object per line:
10
- {"type": "message", "session_id": "s1", "content": "..."}
11
- {"type": "decision", "session_id": "s1", "decisions": [{"type": "approve"}]}
12
- {"type": "shutdown"}
13
-
14
- Events (sidecar -> client), one JSON object per line:
15
- {"type": "ready"} # once, at startup
16
- {"type": "ack", "ref": "message|decision"} # command accepted
17
- <event_to_dict(...)> # content/tool_start/tool_end/
18
- # reasoning/extraction/interrupt
19
- {"type": "complete"} | {"type": "error", "error": "..."}
20
- {"type": "turn_end", "session_id": "s1"} # one turn finished
21
- """
22
- from __future__ import annotations
23
-
24
- import json
25
- from typing import Any, Callable, Iterable, TextIO
26
-
27
- from langgraph_stream_parser import (
28
- StreamParser,
29
- create_resume_input,
30
- event_to_dict,
31
- load_agent_spec,
32
- prepare_agent_input,
33
- )
34
-
35
- DEFAULT_STREAM_MODE = ["updates", "messages"]
36
- DEFAULT_MAX_RESULT_LEN = 50_000
37
-
38
-
39
- def run(
40
- graph: Any,
41
- stdin: Iterable[str],
42
- stdout: TextIO,
43
- *,
44
- stream_mode: str | list[str] = DEFAULT_STREAM_MODE,
45
- max_result_len: int = DEFAULT_MAX_RESULT_LEN,
46
- ) -> None:
47
- """Drive the command/event loop over the given streams.
48
-
49
- Factored out from ``main`` so it can be tested with in-memory streams and
50
- a fake graph. ``stdin`` is any line iterable; ``stdout`` needs ``write``.
51
- """
52
- mode = list(stream_mode) if isinstance(stream_mode, tuple) else stream_mode
53
-
54
- def emit(obj: dict[str, Any]) -> None:
55
- stdout.write(json.dumps(obj) + "\n")
56
- stdout.flush()
57
-
58
- emit({"type": "ready"})
59
-
60
- for raw in stdin:
61
- line = raw.strip()
62
- if not line:
63
- continue
64
- try:
65
- cmd = json.loads(line)
66
- except json.JSONDecodeError as e:
67
- emit({"type": "error", "error": f"invalid JSON: {e}"})
68
- continue
69
-
70
- ctype = cmd.get("type")
71
- if ctype == "shutdown":
72
- break
73
-
74
- session_id = cmd.get("session_id", "default")
75
- config = {"configurable": {"thread_id": session_id}}
76
-
77
- if ctype == "message":
78
- content = cmd.get("content", "")
79
- if not content:
80
- emit({"type": "error", "error": "message requires 'content'"})
81
- continue
82
- emit({"type": "ack", "ref": "message"})
83
- input_data = prepare_agent_input(message=content)
84
- elif ctype == "decision":
85
- decisions = cmd.get("decisions")
86
- if not isinstance(decisions, list):
87
- emit({"type": "error", "error": "decision requires 'decisions' list"})
88
- continue
89
- emit({"type": "ack", "ref": "decision"})
90
- input_data = create_resume_input(decisions=decisions)
91
- else:
92
- emit({"type": "error", "error": f"unknown command type: {ctype!r}"})
93
- continue
94
-
95
- _run_turn(graph, input_data, config, mode, max_result_len, emit)
96
- emit({"type": "turn_end", "session_id": session_id})
97
-
98
-
99
- def _run_turn(
100
- graph: Any,
101
- input_data: Any,
102
- config: dict[str, Any],
103
- stream_mode: str | list[str],
104
- max_result_len: int,
105
- emit: Callable[[dict[str, Any]], None],
106
- ) -> None:
107
- """Stream one turn; the parser emits the terminal complete/error event."""
108
- parser = StreamParser(stream_mode=stream_mode)
109
- try:
110
- stream = graph.stream(input_data, config=config, stream_mode=stream_mode)
111
- for event in parser.parse(stream):
112
- emit(event_to_dict(event, max_result_len=max_result_len))
113
- except Exception as exc: # noqa: BLE001 — surfaced to the client as an event
114
- emit({"type": "error", "error": f"{type(exc).__name__}: {exc}"})
115
-
116
-
117
- # The keyless echo agent shipped with the shared core — see `--demo`.
118
- DEMO_AGENT_SPEC = "langgraph_stream_parser.demo.stub:graph"
119
-
120
-
121
- def main(argv: list[str] | None = None) -> int:
122
- import argparse
123
- import os
124
- import sys
125
-
126
- from langgraph_stream_parser.host import HostConfig
127
-
128
- parser = argparse.ArgumentParser(prog="deepagent-vscode-sidecar")
129
- parser.add_argument(
130
- "--agent",
131
- default=None,
132
- help="Agent spec 'path.py:var' or 'module:var' "
133
- "(overrides DEEPAGENT_AGENT_SPEC / deepagents.toml [agent].spec).",
134
- )
135
- parser.add_argument(
136
- "--workspace",
137
- default=None,
138
- help="Workspace root (overrides DEEPAGENT_WORKSPACE_ROOT / deepagents.toml).",
139
- )
140
- parser.add_argument(
141
- "--demo",
142
- action="store_true",
143
- help="Run with the built-in keyless demo agent — no API key needed.",
144
- )
145
- parser.add_argument(
146
- "--show-config",
147
- action="store_true",
148
- help="Print the resolved configuration (defaults < deepagents.toml < env < CLI) and exit.",
149
- )
150
- args = parser.parse_args(argv)
151
-
152
- # Same resolution chain as every other deep-agent surface:
153
- # defaults < deepagents.toml < DEEPAGENT_* env < CLI flags.
154
- cfg = HostConfig.resolve(
155
- overrides={"agent_spec": args.agent, "workspace_root": args.workspace}
156
- )
157
-
158
- if args.show_config:
159
- print(cfg.describe())
160
- return 0
161
-
162
- def fail(msg: str) -> int:
163
- sys.stdout.write(json.dumps({"type": "error", "error": msg}) + "\n")
164
- sys.stdout.flush()
165
- return 1
166
-
167
- spec = cfg.agent_spec
168
- if args.demo:
169
- if args.agent:
170
- return fail("--demo and --agent are mutually exclusive")
171
- spec = DEMO_AGENT_SPEC
172
- if not spec:
173
- return fail(
174
- "no agent spec (pass --agent or --demo, set DEEPAGENT_AGENT_SPEC, "
175
- "or set [agent].spec in deepagents.toml)"
176
- )
177
-
178
- os.environ.setdefault("DEEPAGENT_WORKSPACE_ROOT", str(cfg.workspace_root))
179
- try:
180
- graph = load_agent_spec(spec)
181
- except Exception as exc: # noqa: BLE001
182
- return fail(f"failed to load agent {spec!r}: {type(exc).__name__}: {exc}")
183
-
184
- run(graph, sys.stdin, sys.stdout)
185
- return 0
186
-
187
-
188
- if __name__ == "__main__":
189
- raise SystemExit(main())
@@ -1,58 +0,0 @@
1
- {
2
- "name": "deepagent-vscode",
3
- "version": "0.1.0",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "deepagent-vscode",
9
- "version": "0.1.0",
10
- "devDependencies": {
11
- "@types/node": "^20.0.0",
12
- "@types/vscode": "^1.95.0",
13
- "typescript": "^5.4.0"
14
- },
15
- "engines": {
16
- "vscode": "^1.95.0"
17
- }
18
- },
19
- "node_modules/@types/node": {
20
- "version": "20.19.41",
21
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
22
- "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
23
- "dev": true,
24
- "license": "MIT",
25
- "dependencies": {
26
- "undici-types": "~6.21.0"
27
- }
28
- },
29
- "node_modules/@types/vscode": {
30
- "version": "1.120.0",
31
- "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.120.0.tgz",
32
- "integrity": "sha512-feaT4Rst+FkTch5zz/ZbNCxoIvo55YU80Be2kiL7OJcod4+CUYf2lUBPdIJzozNnSEMq1VRTGrWEcCGFB3fBmA==",
33
- "dev": true,
34
- "license": "MIT"
35
- },
36
- "node_modules/typescript": {
37
- "version": "5.9.3",
38
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
39
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
40
- "dev": true,
41
- "license": "Apache-2.0",
42
- "bin": {
43
- "tsc": "bin/tsc",
44
- "tsserver": "bin/tsserver"
45
- },
46
- "engines": {
47
- "node": ">=14.17"
48
- }
49
- },
50
- "node_modules/undici-types": {
51
- "version": "6.21.0",
52
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
53
- "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
54
- "dev": true,
55
- "license": "MIT"
56
- }
57
- }
58
- }
@@ -1,49 +0,0 @@
1
- {
2
- "name": "deepagent-vscode",
3
- "displayName": "Deep Agent",
4
- "description": "Chat with your LangGraph / deepagents agent from VS Code, alongside Copilot.",
5
- "version": "0.2.0",
6
- "publisher": "dkedar7",
7
- "engines": {
8
- "vscode": "^1.95.0"
9
- },
10
- "categories": ["AI", "Chat", "Other"],
11
- "activationEvents": [],
12
- "main": "./dist/extension.js",
13
- "contributes": {
14
- "chatParticipants": [
15
- {
16
- "id": "deepagent.agent",
17
- "name": "deepagent",
18
- "fullName": "Deep Agent",
19
- "description": "Run your LangGraph/deepagents agent",
20
- "isSticky": true
21
- }
22
- ],
23
- "configuration": {
24
- "title": "Deep Agent",
25
- "properties": {
26
- "deepagent.agentSpec": {
27
- "type": "string",
28
- "default": "",
29
- "description": "Agent spec 'path/to/agent.py:graph' or 'module:graph' to run. Leave empty to fall back to DEEPAGENT_AGENT_SPEC or the project's deepagents.toml."
30
- },
31
- "deepagent.pythonPath": {
32
- "type": "string",
33
- "default": "python",
34
- "description": "Python interpreter used to launch the deepagent-vscode sidecar (must have deepagent-vscode installed)."
35
- }
36
- }
37
- }
38
- },
39
- "scripts": {
40
- "compile": "tsc -p ./",
41
- "watch": "tsc -watch -p ./",
42
- "vscode:prepublish": "npm run compile"
43
- },
44
- "devDependencies": {
45
- "@types/node": "^20.0.0",
46
- "@types/vscode": "^1.95.0",
47
- "typescript": "^5.4.0"
48
- }
49
- }
@@ -1,161 +0,0 @@
1
- import * as vscode from 'vscode';
2
- import { spawn, ChildProcess } from 'child_process';
3
- import * as readline from 'readline';
4
-
5
- /**
6
- * Deep Agent VS Code chat participant.
7
- *
8
- * Registers `@deepagent` in the chat panel (alongside Copilot) and bridges each
9
- * turn to the Python `deepagent-vscode` sidecar over stdio. The sidecar emits
10
- * newline-delimited JSON events — the langgraph-stream-parser `event.to_dict()`
11
- * wire vocabulary — which the dispatcher below maps onto the chat response.
12
- */
13
- export function activate(context: vscode.ExtensionContext) {
14
- const participant = vscode.chat.createChatParticipant('deepagent.agent', handler);
15
- participant.iconPath = new vscode.ThemeIcon('robot');
16
- context.subscriptions.push(participant);
17
- }
18
-
19
- export function deactivate() {}
20
-
21
- interface AgentEvent {
22
- type: string;
23
- [key: string]: unknown;
24
- }
25
-
26
- async function handler(
27
- request: vscode.ChatRequest,
28
- _context: vscode.ChatContext,
29
- stream: vscode.ChatResponseStream,
30
- token: vscode.CancellationToken,
31
- ): Promise<void> {
32
- const config = vscode.workspace.getConfiguration('deepagent');
33
- const agentSpec = config.get<string>('agentSpec') ?? '';
34
- const python = config.get<string>('pythonPath') || 'python';
35
-
36
- const workspace =
37
- vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? process.cwd();
38
-
39
- // When the setting is empty, spawn without --agent and let the sidecar
40
- // resolve the family-standard chain (deepagents.toml < DEEPAGENT_* env).
41
- // If nothing resolves anywhere, the sidecar emits a clean `error` event
42
- // that the dispatcher renders in the chat.
43
- const args = ['-m', 'deepagent_vscode', '--workspace', workspace];
44
- if (agentSpec) {
45
- args.push('--agent', agentSpec);
46
- }
47
-
48
- const proc = spawn(python, args, {
49
- // cwd anchors the sidecar's deepagents.toml walk-up at the workspace.
50
- cwd: workspace,
51
- env: { ...process.env, DEEPAGENT_WORKSPACE_ROOT: workspace },
52
- });
53
-
54
- token.onCancellationRequested(() => proc.kill());
55
-
56
- try {
57
- await runTurn(proc, request.prompt, stream);
58
- } catch (err) {
59
- stream.markdown(`\n\n❌ ${err instanceof Error ? err.message : String(err)}`);
60
- } finally {
61
- proc.kill();
62
- }
63
- }
64
-
65
- /**
66
- * Send one user message to the sidecar and pump its events into the chat
67
- * stream until the turn ends.
68
- */
69
- function runTurn(
70
- proc: ChildProcess,
71
- prompt: string,
72
- stream: vscode.ChatResponseStream,
73
- ): Promise<void> {
74
- return new Promise<void>((resolve, reject) => {
75
- if (!proc.stdout || !proc.stdin) {
76
- reject(new Error('sidecar has no stdio pipes'));
77
- return;
78
- }
79
-
80
- const rl = readline.createInterface({ input: proc.stdout });
81
- let started = false;
82
-
83
- rl.on('line', (line: string) => {
84
- const text = line.trim();
85
- if (!text) return;
86
- let event: AgentEvent;
87
- try {
88
- event = JSON.parse(text) as AgentEvent;
89
- } catch {
90
- return; // ignore non-JSON noise
91
- }
92
-
93
- // Send the message once the sidecar reports it's ready.
94
- if (event.type === 'ready' && !started) {
95
- started = true;
96
- proc.stdin!.write(
97
- JSON.stringify({ type: 'message', session_id: 'vscode', content: prompt }) + '\n',
98
- );
99
- return;
100
- }
101
-
102
- dispatch(event, stream);
103
-
104
- if (event.type === 'turn_end') {
105
- rl.close();
106
- resolve();
107
- }
108
- });
109
-
110
- proc.on('error', reject);
111
- proc.on('exit', () => resolve());
112
- });
113
- }
114
-
115
- /** Map one sidecar event onto the chat response stream. */
116
- function dispatch(event: AgentEvent, stream: vscode.ChatResponseStream): void {
117
- switch (event.type) {
118
- case 'content':
119
- stream.markdown(String(event.content ?? ''));
120
- break;
121
- case 'reasoning':
122
- stream.markdown(`\n\n*${String(event.content ?? '')}*\n\n`);
123
- break;
124
- case 'tool_start':
125
- stream.progress(`Running \`${String(event.name ?? 'tool')}\`…`);
126
- break;
127
- case 'tool_end': {
128
- const status = event.status === 'error' ? '❌' : '✓';
129
- stream.markdown(`\n\n${status} \`${String(event.name ?? 'tool')}\`\n`);
130
- break;
131
- }
132
- case 'extraction':
133
- if (event.extracted_type === 'todos' && Array.isArray(event.data)) {
134
- stream.markdown('\n\n**Tasks**\n');
135
- for (const item of event.data as Array<Record<string, unknown>>) {
136
- const done = item.status === 'completed';
137
- const content = String(item.content ?? item.task ?? '');
138
- stream.markdown(`- ${done ? '[x]' : '[ ]'} ${content}\n`);
139
- }
140
- }
141
- break;
142
- case 'interrupt': {
143
- // HITL: v0 surfaces the requested action. The decision round-trip
144
- // (sending {"type":"decision",...} back to the sidecar) is the next
145
- // increment — it needs a confirmation affordance in the chat UI.
146
- const actions = (event.action_requests as Array<Record<string, unknown>>) ?? [];
147
- const tool = actions[0]?.tool ?? 'an action';
148
- stream.markdown(
149
- `\n\n⚠️ The agent wants to run **${String(tool)}** and is waiting for ` +
150
- 'approval. Interactive approval is not wired up yet in this build.\n',
151
- );
152
- break;
153
- }
154
- case 'error':
155
- stream.markdown(`\n\n❌ ${String(event.error ?? 'unknown error')}\n`);
156
- break;
157
- // ready / ack / complete / turn_end / usage: no direct UI output.
158
- default:
159
- break;
160
- }
161
- }
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "commonjs",
4
- "target": "ES2020",
5
- "outDir": "dist",
6
- "lib": ["ES2020"],
7
- "sourceMap": true,
8
- "rootDir": "src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true
12
- },
13
- "include": ["src"],
14
- "exclude": ["node_modules", "dist"]
15
- }
@@ -1,36 +0,0 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "deepagent-vscode"
7
- version = "0.2.0"
8
- description = "Python stdio sidecar bridging LangGraph/deepagents agents to the deepagent-vscode chat extension"
9
- license = "MIT"
10
- requires-python = ">=3.11"
11
- authors = [
12
- {name = "Kedar Dabhadkar"}
13
- ]
14
- keywords = ["langgraph", "deepagents", "vscode", "agents", "ai"]
15
- dependencies = [
16
- "langgraph-stream-parser>=0.2.2,<0.3",
17
- ]
18
-
19
- [project.optional-dependencies]
20
- demo = [
21
- "deepagents>=0.3",
22
- ]
23
- dev = [
24
- "pytest>=8",
25
- "langgraph>=1.1.0",
26
- "langchain-core>=1.4.0",
27
- ]
28
-
29
- [project.scripts]
30
- deepagent-vscode-sidecar = "deepagent_vscode.sidecar:main"
31
-
32
- [tool.hatch.build.targets.wheel]
33
- packages = ["deepagent_vscode"]
34
-
35
- [tool.pytest.ini_options]
36
- testpaths = ["tests"]
@@ -1,215 +0,0 @@
1
- """Tests for the stdio sidecar command/event loop."""
2
- import io
3
- import json
4
- from dataclasses import dataclass
5
- from typing import Any
6
-
7
- from langchain_core.messages import AIMessage, ToolMessage
8
-
9
- from deepagent_vscode.sidecar import main, run
10
-
11
-
12
- # ── Fakes / fixtures ─────────────────────────────────────────────────
13
-
14
-
15
- class FakeGraph:
16
- """Sync LangGraph-ish fake yielding canned single-mode 'updates' chunks."""
17
-
18
- def __init__(self, chunks_per_call: list[list[Any]]):
19
- self._chunks = chunks_per_call
20
- self._i = 0
21
- self.calls: list[dict] = []
22
-
23
- def stream(self, input_data, config=None, stream_mode="updates"):
24
- self.calls.append({"input": input_data, "config": config, "stream_mode": stream_mode})
25
- chunks = self._chunks[self._i]
26
- self._i += 1
27
- for c in chunks:
28
- yield c
29
-
30
-
31
- @dataclass
32
- class MockInterrupt:
33
- value: Any
34
- resumable: bool = True
35
-
36
-
37
- CONTENT = {"agent": {"messages": [AIMessage(content="Hello there")]}}
38
- TOOLCALL = {"agent": {"messages": [AIMessage(
39
- content="", tool_calls=[{"id": "c1", "name": "search", "args": {"q": "x"}}],
40
- )]}}
41
- TOOLRESULT = {"tools": {"messages": [ToolMessage(
42
- content="result ok", name="search", tool_call_id="c1",
43
- )]}}
44
- INTERRUPT = {"__interrupt__": (MockInterrupt(value={
45
- "action_requests": [{"name": "bash", "args": {"command": "ls"}, "tool_call_id": "c1"}],
46
- "review_configs": [{"allowed_decisions": ["approve", "reject"]}],
47
- }),)}
48
-
49
-
50
- def drive(graph, commands, **kw):
51
- """Feed JSON commands through run() and return parsed event dicts."""
52
- stdin = io.StringIO("".join(json.dumps(c) + "\n" for c in commands))
53
- stdout = io.StringIO()
54
- run(graph, stdin, stdout, stream_mode="updates", **kw)
55
- return [json.loads(line) for line in stdout.getvalue().splitlines() if line.strip()]
56
-
57
-
58
- # ── Tests ────────────────────────────────────────────────────────────
59
-
60
-
61
- def test_ready_is_first_event():
62
- events = drive(FakeGraph([]), [{"type": "shutdown"}])
63
- assert events[0] == {"type": "ready"}
64
-
65
-
66
- def test_message_turn_emits_content_and_terminals():
67
- graph = FakeGraph([[CONTENT]])
68
- events = drive(graph, [
69
- {"type": "message", "session_id": "s", "content": "hi"},
70
- {"type": "shutdown"},
71
- ])
72
- types = [e["type"] for e in events]
73
- assert types[0] == "ready"
74
- assert {"type": "ack", "ref": "message"} in events
75
- assert "content" in types
76
- assert "complete" in types
77
- assert any(e["type"] == "turn_end" and e["session_id"] == "s" for e in events)
78
- # thread_id wired from session_id
79
- assert graph.calls[0]["config"] == {"configurable": {"thread_id": "s"}}
80
-
81
-
82
- def test_tool_lifecycle():
83
- graph = FakeGraph([[TOOLCALL, TOOLRESULT]])
84
- events = drive(graph, [
85
- {"type": "message", "session_id": "s", "content": "search"},
86
- {"type": "shutdown"},
87
- ])
88
- types = [e["type"] for e in events]
89
- assert "tool_start" in types
90
- assert "tool_end" in types
91
-
92
-
93
- def test_interrupt_then_decision_resumes():
94
- graph = FakeGraph([[TOOLCALL, INTERRUPT], [TOOLRESULT]])
95
- events = drive(graph, [
96
- {"type": "message", "session_id": "s", "content": "run it"},
97
- {"type": "decision", "session_id": "s", "decisions": [{"type": "approve"}]},
98
- {"type": "shutdown"},
99
- ])
100
- types = [e["type"] for e in events]
101
- assert "interrupt" in types
102
- assert {"type": "ack", "ref": "decision"} in events
103
- assert "tool_end" in types
104
- # interrupt action_requests normalized to a 'tool' key
105
- interrupt = next(e for e in events if e["type"] == "interrupt")
106
- assert interrupt["action_requests"][0]["tool"] == "bash"
107
- # second call resumes with a Command
108
- from langgraph.types import Command
109
- assert isinstance(graph.calls[1]["input"], Command)
110
-
111
-
112
- def test_invalid_json_reported():
113
- stdin = io.StringIO("not json\n")
114
- stdout = io.StringIO()
115
- run(FakeGraph([]), stdin, stdout, stream_mode="updates")
116
- events = [json.loads(line) for line in stdout.getvalue().splitlines() if line.strip()]
117
- assert any(e["type"] == "error" and "invalid JSON" in e["error"] for e in events)
118
-
119
-
120
- # ── main(): config resolution + --demo / --show-config ───────────────
121
-
122
-
123
- def _isolate_config(monkeypatch, tmp_path):
124
- """Point cwd + the global config home at an empty tmp dir and strip
125
- DEEPAGENT_* env so main() resolves from pure defaults."""
126
- monkeypatch.chdir(tmp_path)
127
- monkeypatch.setenv("DEEPAGENTS_CONFIG_HOME", str(tmp_path))
128
- for var in ("DEEPAGENT_AGENT_SPEC", "DEEPAGENT_WORKSPACE_ROOT"):
129
- monkeypatch.delenv(var, raising=False)
130
-
131
-
132
- def test_main_show_config(monkeypatch, tmp_path, capsys):
133
- _isolate_config(monkeypatch, tmp_path)
134
- assert main(["--show-config"]) == 0
135
- assert "DEEPAGENT_AGENT_SPEC" in capsys.readouterr().out
136
-
137
-
138
- def test_main_no_spec_emits_error(monkeypatch, tmp_path, capsys):
139
- _isolate_config(monkeypatch, tmp_path)
140
- assert main([]) == 1
141
- err = json.loads(capsys.readouterr().out.strip())
142
- assert err["type"] == "error"
143
- assert "deepagents.toml" in err["error"]
144
-
145
-
146
- def test_main_demo_conflicts_with_agent(monkeypatch, tmp_path, capsys):
147
- _isolate_config(monkeypatch, tmp_path)
148
- assert main(["--demo", "--agent", "x.py:g"]) == 1
149
- err = json.loads(capsys.readouterr().out.strip())
150
- assert "mutually exclusive" in err["error"]
151
-
152
-
153
- def test_main_toml_supplies_agent_spec(monkeypatch, tmp_path, capsys):
154
- """The deepagents.toml resolver is actually wired in: [agent].spec from a
155
- project file reaches load_agent_spec with no flags or env."""
156
- _isolate_config(monkeypatch, tmp_path)
157
- (tmp_path / "deepagents.toml").write_text(
158
- '[agent]\nspec = "langgraph_stream_parser.demo.stub:graph"\n'
159
- )
160
- monkeypatch.setattr(
161
- "sys.stdin", io.StringIO(json.dumps({"type": "shutdown"}) + "\n")
162
- )
163
- assert main([]) == 0
164
- events = [json.loads(line) for line in capsys.readouterr().out.splitlines() if line.strip()]
165
- assert events[0] == {"type": "ready"}
166
-
167
-
168
- def test_main_demo_end_to_end(monkeypatch, tmp_path, capsys):
169
- """--demo answers a real message turn through the stub agent — no keys."""
170
- _isolate_config(monkeypatch, tmp_path)
171
- commands = (
172
- json.dumps({"type": "message", "session_id": "s", "content": "hi demo"})
173
- + "\n"
174
- + json.dumps({"type": "shutdown"})
175
- + "\n"
176
- )
177
- monkeypatch.setattr("sys.stdin", io.StringIO(commands))
178
- assert main(["--demo"]) == 0
179
- events = [json.loads(line) for line in capsys.readouterr().out.splitlines() if line.strip()]
180
- types = [e["type"] for e in events]
181
- assert "ready" in types and "complete" in types and "turn_end" in types
182
- content = "".join(e.get("content", "") for e in events if e["type"] == "content")
183
- assert "hi demo" in content
184
-
185
-
186
- def test_unknown_command_reported():
187
- events = drive(FakeGraph([]), [{"type": "nope"}, {"type": "shutdown"}])
188
- assert any(e["type"] == "error" and "unknown command" in e["error"] for e in events)
189
-
190
-
191
- def test_empty_message_reported():
192
- events = drive(FakeGraph([]), [
193
- {"type": "message", "content": ""},
194
- {"type": "shutdown"},
195
- ])
196
- assert any(e["type"] == "error" and "content" in e["error"] for e in events)
197
-
198
-
199
- def test_decision_without_list_reported():
200
- events = drive(FakeGraph([]), [{"type": "decision"}, {"type": "shutdown"}])
201
- assert any(e["type"] == "error" and "decisions" in e["error"] for e in events)
202
-
203
-
204
- def test_graph_error_surfaced():
205
- class BoomGraph:
206
- def stream(self, *a, **k):
207
- raise RuntimeError("kaboom")
208
- yield # pragma: no cover
209
-
210
- events = drive(BoomGraph(), [
211
- {"type": "message", "content": "go"},
212
- {"type": "shutdown"},
213
- ])
214
- err = [e for e in events if e["type"] == "error"]
215
- assert err and "kaboom" in err[-1]["error"]