python-delphi-lsp 1.1.1__py3-none-any.whl → 2.0.0__py3-none-any.whl
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.
- {delphiast → delphi_lsp}/__init__.py +2 -0
- delphi_lsp/_version.py +1 -0
- delphi_lsp/agent_cli.py +242 -0
- delphi_lsp/agent_context.py +2096 -0
- {delphiast → delphi_lsp}/agent_layers.py +2 -2
- delphi_lsp/agent_protocol.py +390 -0
- delphi_lsp/agent_relations.py +859 -0
- delphi_lsp/agent_templates.py +579 -0
- delphi_lsp/agent_workspace.py +661 -0
- {delphiast → delphi_lsp}/lsp_server.py +270 -48
- {delphiast → delphi_lsp}/project_discovery.py +183 -40
- {delphiast → delphi_lsp}/project_indexer.py +6 -0
- delphi_lsp/source_reader.py +17 -0
- {delphiast → delphi_lsp}/workspace.py +15 -1
- python_delphi_lsp-2.0.0.dist-info/METADATA +324 -0
- python_delphi_lsp-2.0.0.dist-info/RECORD +32 -0
- python_delphi_lsp-2.0.0.dist-info/entry_points.txt +3 -0
- python_delphi_lsp-2.0.0.dist-info/top_level.txt +1 -0
- delphiast/agent_cli.py +0 -113
- delphiast/agent_templates.py +0 -180
- delphiast/source_reader.py +0 -15
- python_delphi_lsp-1.1.1.dist-info/METADATA +0 -434
- python_delphi_lsp-1.1.1.dist-info/RECORD +0 -27
- python_delphi_lsp-1.1.1.dist-info/entry_points.txt +0 -3
- python_delphi_lsp-1.1.1.dist-info/top_level.txt +0 -1
- {delphiast → delphi_lsp}/binary.py +0 -0
- {delphiast → delphi_lsp}/comment_builder.py +0 -0
- {delphiast → delphi_lsp}/consts.py +0 -0
- {delphiast → delphi_lsp}/grammar.py +0 -0
- {delphiast → delphi_lsp}/lark_builder.py +0 -0
- {delphiast → delphi_lsp}/lark_tokens.py +0 -0
- {delphiast → delphi_lsp}/nodes.py +0 -0
- {delphiast → delphi_lsp}/parser.py +0 -0
- {delphiast → delphi_lsp}/preprocessor.py +0 -0
- {delphiast → delphi_lsp}/semantic.py +0 -0
- {delphiast → delphi_lsp}/semantic_builder.py +0 -0
- {delphiast → delphi_lsp}/writer.py +0 -0
- {python_delphi_lsp-1.1.1.dist-info → python_delphi_lsp-2.0.0.dist-info}/WHEEL +0 -0
- {python_delphi_lsp-1.1.1.dist-info → python_delphi_lsp-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from ._version import __version__
|
|
1
2
|
from .binary import BinarySerializer, dumps, loads
|
|
2
3
|
from .consts import AttributeName, SyntaxNodeType
|
|
3
4
|
from .nodes import (
|
|
@@ -60,6 +61,7 @@ from .project_indexer import (
|
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
__all__ = [
|
|
64
|
+
'__version__',
|
|
63
65
|
'AttributeName',
|
|
64
66
|
'BinarySerializer',
|
|
65
67
|
'CommentNode',
|
delphi_lsp/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.0"
|
delphi_lsp/agent_cli.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import BinaryIO, TextIO
|
|
9
|
+
|
|
10
|
+
from .agent_context import AgentContext
|
|
11
|
+
from .agent_layers import build_codebase_index, layer_payload, render_layer
|
|
12
|
+
from .agent_protocol import AgentProtocolError
|
|
13
|
+
from .agent_templates import install_opencode_support, install_skill
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_MAX_WORKER_RECORD_BYTES = 1024 * 1024
|
|
17
|
+
_INVALID_JSON_MESSAGE = "Invalid JSON request."
|
|
18
|
+
_INVALID_ENCODING_MESSAGE = "Invalid UTF-8 request."
|
|
19
|
+
_REQUEST_TOO_LARGE_MESSAGE = "Request exceeds the 1 MiB limit."
|
|
20
|
+
_INTERNAL_ERROR_MESSAGE = "Internal request error."
|
|
21
|
+
_SOURCE_UNAVAILABLE_MESSAGE = "Selected source is unavailable."
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
25
|
+
parser = argparse.ArgumentParser(
|
|
26
|
+
prog="delphi-lsp-agent",
|
|
27
|
+
description="Agent-facing Delphi/Object Pascal codebase navigation helpers.",
|
|
28
|
+
)
|
|
29
|
+
subcommands = parser.add_subparsers(dest="command", required=True)
|
|
30
|
+
|
|
31
|
+
view = subcommands.add_parser("view", help="Render a layered codebase view.")
|
|
32
|
+
view.add_argument("--root", type=Path, default=Path("."))
|
|
33
|
+
view.add_argument("--project-file", type=Path)
|
|
34
|
+
view.add_argument(
|
|
35
|
+
"--layer",
|
|
36
|
+
required=True,
|
|
37
|
+
choices=[
|
|
38
|
+
"overview",
|
|
39
|
+
"projects",
|
|
40
|
+
"units",
|
|
41
|
+
"unit",
|
|
42
|
+
"symbols",
|
|
43
|
+
"symbol",
|
|
44
|
+
"implementation",
|
|
45
|
+
"references",
|
|
46
|
+
"problems",
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
view.add_argument("--query", default="")
|
|
50
|
+
view.add_argument("--format", default="markdown", choices=["markdown", "json"])
|
|
51
|
+
view.add_argument("--deep-projects", action="store_true", help="Deep-parse project dependencies for the projects layer.")
|
|
52
|
+
view.set_defaults(func=_view)
|
|
53
|
+
|
|
54
|
+
index = subcommands.add_parser("index", help="Materialize a JSON codebase index.")
|
|
55
|
+
index.add_argument("--root", type=Path, default=Path("."))
|
|
56
|
+
index.add_argument("--project-file", type=Path)
|
|
57
|
+
index.add_argument("--out", type=Path, default=Path(".delphi-lsp") / "agent-index" / "index.json")
|
|
58
|
+
index.set_defaults(func=_index)
|
|
59
|
+
|
|
60
|
+
skill = subcommands.add_parser("skill", help="Install agent skill templates.")
|
|
61
|
+
skill_commands = skill.add_subparsers(dest="skill_command", required=True)
|
|
62
|
+
skill_install = skill_commands.add_parser("install", help="Install .agents skill.")
|
|
63
|
+
skill_install.add_argument("--target", type=Path, default=Path("."))
|
|
64
|
+
skill_install.add_argument("--force", action="store_true")
|
|
65
|
+
skill_install.set_defaults(func=_skill_install)
|
|
66
|
+
|
|
67
|
+
opencode = subcommands.add_parser("opencode", help="Install opencode integration.")
|
|
68
|
+
opencode_commands = opencode.add_subparsers(dest="opencode_command", required=True)
|
|
69
|
+
opencode_install = opencode_commands.add_parser("install", help="Install .agents skill and opencode plugin.")
|
|
70
|
+
opencode_install.add_argument("--target", type=Path, default=Path("."))
|
|
71
|
+
opencode_install.add_argument("--python", default=sys.executable)
|
|
72
|
+
opencode_install.add_argument("--force", action="store_true")
|
|
73
|
+
opencode_install.add_argument("--write-config", action="store_true")
|
|
74
|
+
opencode_install.set_defaults(func=_opencode_install)
|
|
75
|
+
|
|
76
|
+
worker = subcommands.add_parser("worker", help="Serve Protocol v2 NDJSON requests.")
|
|
77
|
+
worker.add_argument("--root", type=Path, required=True)
|
|
78
|
+
worker.add_argument("--project-file", type=Path)
|
|
79
|
+
worker.set_defaults(func=_worker)
|
|
80
|
+
|
|
81
|
+
return parser
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main(argv: list[str] | None = None) -> int:
|
|
85
|
+
parser = build_parser()
|
|
86
|
+
args = parser.parse_args(argv)
|
|
87
|
+
try:
|
|
88
|
+
args.func(args)
|
|
89
|
+
if not getattr(sys.stdout, "closed", False):
|
|
90
|
+
sys.stdout.flush()
|
|
91
|
+
except BrokenPipeError:
|
|
92
|
+
_discard_broken_stdout()
|
|
93
|
+
os._exit(1)
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _discard_broken_stdout() -> None:
|
|
98
|
+
stdout = sys.stdout
|
|
99
|
+
try:
|
|
100
|
+
stdout_fd = stdout.fileno()
|
|
101
|
+
except (AttributeError, OSError, ValueError):
|
|
102
|
+
stdout_fd = None
|
|
103
|
+
if stdout_fd is not None:
|
|
104
|
+
devnull_fd = os.open(os.devnull, os.O_WRONLY)
|
|
105
|
+
try:
|
|
106
|
+
os.dup2(devnull_fd, stdout_fd)
|
|
107
|
+
finally:
|
|
108
|
+
os.close(devnull_fd)
|
|
109
|
+
replacement = open(os.devnull, "w", encoding=getattr(stdout, "encoding", None) or "utf-8")
|
|
110
|
+
sys.stdout = replacement
|
|
111
|
+
sys.__stdout__ = replacement
|
|
112
|
+
try:
|
|
113
|
+
stdout.close()
|
|
114
|
+
except (BrokenPipeError, OSError, ValueError):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _view(args: argparse.Namespace) -> None:
|
|
119
|
+
index = build_codebase_index(args.root, project_file=args.project_file, index_projects=args.deep_projects)
|
|
120
|
+
sys.stdout.write(render_layer(index, args.layer, query=args.query, output_format=args.format))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _index(args: argparse.Namespace) -> None:
|
|
124
|
+
index = build_codebase_index(args.root, project_file=args.project_file, index_projects=True)
|
|
125
|
+
payload = {
|
|
126
|
+
"overview": layer_payload(index, "overview"),
|
|
127
|
+
"projects": layer_payload(index, "projects"),
|
|
128
|
+
"problems": layer_payload(index, "problems"),
|
|
129
|
+
}
|
|
130
|
+
args.out.parent.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
args.out.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
132
|
+
print(args.out)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _skill_install(args: argparse.Namespace) -> None:
|
|
136
|
+
skill_path = install_skill(args.target, force=args.force)
|
|
137
|
+
print(skill_path)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _opencode_install(args: argparse.Namespace) -> None:
|
|
141
|
+
skill_path, plugin_path, config_path = install_opencode_support(
|
|
142
|
+
args.target,
|
|
143
|
+
python_executable=args.python,
|
|
144
|
+
force=args.force,
|
|
145
|
+
write_config=args.write_config,
|
|
146
|
+
)
|
|
147
|
+
print(skill_path)
|
|
148
|
+
print(plugin_path)
|
|
149
|
+
if config_path is not None:
|
|
150
|
+
print(config_path)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _worker(args: argparse.Namespace) -> None:
|
|
154
|
+
context = AgentContext.open(args.root, args.project_file)
|
|
155
|
+
try:
|
|
156
|
+
_serve_worker(context, sys.stdin.buffer, sys.stdout.buffer, sys.stderr)
|
|
157
|
+
finally:
|
|
158
|
+
try:
|
|
159
|
+
sys.stdout.close()
|
|
160
|
+
except (BrokenPipeError, OSError) as error:
|
|
161
|
+
raise BrokenPipeError from error
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _serve_worker(context: AgentContext, input_stream: BinaryIO, output_stream: BinaryIO, error_stream: TextIO) -> None:
|
|
165
|
+
discarding_oversize_record = False
|
|
166
|
+
while True:
|
|
167
|
+
record = input_stream.readline(_MAX_WORKER_RECORD_BYTES + 1)
|
|
168
|
+
if not record:
|
|
169
|
+
return
|
|
170
|
+
if discarding_oversize_record:
|
|
171
|
+
if record.endswith(b"\n"):
|
|
172
|
+
discarding_oversize_record = False
|
|
173
|
+
continue
|
|
174
|
+
if not record.endswith(b"\n") and len(record) > _MAX_WORKER_RECORD_BYTES:
|
|
175
|
+
if record.endswith(b"\r") and input_stream.read(1) == b"\n":
|
|
176
|
+
record = record[:-1]
|
|
177
|
+
else:
|
|
178
|
+
discarding_oversize_record = True
|
|
179
|
+
_write_worker_message(
|
|
180
|
+
output_stream,
|
|
181
|
+
_worker_error("request_too_large", _REQUEST_TOO_LARGE_MESSAGE),
|
|
182
|
+
)
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
record = record.rstrip(b"\r\n")
|
|
186
|
+
if len(record) > _MAX_WORKER_RECORD_BYTES:
|
|
187
|
+
_write_worker_message(
|
|
188
|
+
output_stream,
|
|
189
|
+
_worker_error("request_too_large", _REQUEST_TOO_LARGE_MESSAGE),
|
|
190
|
+
)
|
|
191
|
+
continue
|
|
192
|
+
try:
|
|
193
|
+
text = record.decode("utf-8")
|
|
194
|
+
except UnicodeDecodeError:
|
|
195
|
+
_write_worker_message(
|
|
196
|
+
output_stream,
|
|
197
|
+
_worker_error("invalid_encoding", _INVALID_ENCODING_MESSAGE),
|
|
198
|
+
)
|
|
199
|
+
continue
|
|
200
|
+
if not text.strip():
|
|
201
|
+
continue
|
|
202
|
+
try:
|
|
203
|
+
request = json.loads(text)
|
|
204
|
+
except json.JSONDecodeError:
|
|
205
|
+
_write_worker_message(output_stream, _worker_error("invalid_json", _INVALID_JSON_MESSAGE))
|
|
206
|
+
continue
|
|
207
|
+
try:
|
|
208
|
+
response = context.handle(request)
|
|
209
|
+
message = response.to_mapping()
|
|
210
|
+
except BrokenPipeError:
|
|
211
|
+
raise
|
|
212
|
+
except AgentProtocolError as error:
|
|
213
|
+
message = _SOURCE_UNAVAILABLE_MESSAGE if error.code == "source_unavailable" else error.message
|
|
214
|
+
message = _worker_error(error.code, message)
|
|
215
|
+
except Exception as error:
|
|
216
|
+
error_stream.write(f"{type(error).__name__}\n")
|
|
217
|
+
error_stream.flush()
|
|
218
|
+
message = _worker_error("internal_error", _INTERNAL_ERROR_MESSAGE)
|
|
219
|
+
_write_worker_message(output_stream, message)
|
|
220
|
+
def _worker_error(code: str, message: str) -> dict[str, object]:
|
|
221
|
+
return {"schema": 2, "error": {"code": code, "message": message}}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _write_worker_message(output_stream: BinaryIO, message: object) -> None:
|
|
225
|
+
serialized = json.dumps(
|
|
226
|
+
message,
|
|
227
|
+
ensure_ascii=False,
|
|
228
|
+
sort_keys=True,
|
|
229
|
+
separators=(",", ":"),
|
|
230
|
+
allow_nan=False,
|
|
231
|
+
).encode("utf-8")
|
|
232
|
+
try:
|
|
233
|
+
output_stream.write(serialized + b"\n")
|
|
234
|
+
output_stream.flush()
|
|
235
|
+
except BrokenPipeError:
|
|
236
|
+
raise
|
|
237
|
+
except OSError as error:
|
|
238
|
+
raise BrokenPipeError from error
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
raise SystemExit(main())
|