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.
Files changed (39) hide show
  1. {delphiast → delphi_lsp}/__init__.py +2 -0
  2. delphi_lsp/_version.py +1 -0
  3. delphi_lsp/agent_cli.py +242 -0
  4. delphi_lsp/agent_context.py +2096 -0
  5. {delphiast → delphi_lsp}/agent_layers.py +2 -2
  6. delphi_lsp/agent_protocol.py +390 -0
  7. delphi_lsp/agent_relations.py +859 -0
  8. delphi_lsp/agent_templates.py +579 -0
  9. delphi_lsp/agent_workspace.py +661 -0
  10. {delphiast → delphi_lsp}/lsp_server.py +270 -48
  11. {delphiast → delphi_lsp}/project_discovery.py +183 -40
  12. {delphiast → delphi_lsp}/project_indexer.py +6 -0
  13. delphi_lsp/source_reader.py +17 -0
  14. {delphiast → delphi_lsp}/workspace.py +15 -1
  15. python_delphi_lsp-2.0.0.dist-info/METADATA +324 -0
  16. python_delphi_lsp-2.0.0.dist-info/RECORD +32 -0
  17. python_delphi_lsp-2.0.0.dist-info/entry_points.txt +3 -0
  18. python_delphi_lsp-2.0.0.dist-info/top_level.txt +1 -0
  19. delphiast/agent_cli.py +0 -113
  20. delphiast/agent_templates.py +0 -180
  21. delphiast/source_reader.py +0 -15
  22. python_delphi_lsp-1.1.1.dist-info/METADATA +0 -434
  23. python_delphi_lsp-1.1.1.dist-info/RECORD +0 -27
  24. python_delphi_lsp-1.1.1.dist-info/entry_points.txt +0 -3
  25. python_delphi_lsp-1.1.1.dist-info/top_level.txt +0 -1
  26. {delphiast → delphi_lsp}/binary.py +0 -0
  27. {delphiast → delphi_lsp}/comment_builder.py +0 -0
  28. {delphiast → delphi_lsp}/consts.py +0 -0
  29. {delphiast → delphi_lsp}/grammar.py +0 -0
  30. {delphiast → delphi_lsp}/lark_builder.py +0 -0
  31. {delphiast → delphi_lsp}/lark_tokens.py +0 -0
  32. {delphiast → delphi_lsp}/nodes.py +0 -0
  33. {delphiast → delphi_lsp}/parser.py +0 -0
  34. {delphiast → delphi_lsp}/preprocessor.py +0 -0
  35. {delphiast → delphi_lsp}/semantic.py +0 -0
  36. {delphiast → delphi_lsp}/semantic_builder.py +0 -0
  37. {delphiast → delphi_lsp}/writer.py +0 -0
  38. {python_delphi_lsp-1.1.1.dist-info → python_delphi_lsp-2.0.0.dist-info}/WHEEL +0 -0
  39. {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"
@@ -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())