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.
- deepagent_vscode-0.2.1/PKG-INFO +25 -0
- deepagent_vscode-0.2.1/README.md +14 -0
- deepagent_vscode-0.2.1/pyproject.toml +19 -0
- deepagent_vscode-0.2.0/.gitignore +0 -12
- deepagent_vscode-0.2.0/CHANGELOG.md +0 -23
- deepagent_vscode-0.2.0/LICENSE +0 -21
- deepagent_vscode-0.2.0/PKG-INFO +0 -16
- deepagent_vscode-0.2.0/README.md +0 -158
- deepagent_vscode-0.2.0/assets/header.svg +0 -81
- deepagent_vscode-0.2.0/deepagent_vscode/__init__.py +0 -11
- deepagent_vscode-0.2.0/deepagent_vscode/__main__.py +0 -3
- deepagent_vscode-0.2.0/deepagent_vscode/sidecar.py +0 -189
- deepagent_vscode-0.2.0/extension/package-lock.json +0 -58
- deepagent_vscode-0.2.0/extension/package.json +0 -49
- deepagent_vscode-0.2.0/extension/src/extension.ts +0 -161
- deepagent_vscode-0.2.0/extension/tsconfig.json +0 -15
- deepagent_vscode-0.2.0/pyproject.toml +0 -36
- deepagent_vscode-0.2.0/tests/test_sidecar.py +0 -215
|
@@ -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,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.
|
deepagent_vscode-0.2.0/LICENSE
DELETED
|
@@ -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.
|
deepagent_vscode-0.2.0/PKG-INFO
DELETED
|
@@ -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'
|
deepagent_vscode-0.2.0/README.md
DELETED
|
@@ -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 · 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 — 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">•</text>
|
|
44
|
-
<text x="236" y="300">python sidecar</text>
|
|
45
|
-
<text x="372" y="300" fill="#34506A">•</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 ✓</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,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"]
|