env2llm 0.1.1__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 (47) hide show
  1. env2llm/__init__.py +33 -0
  2. env2llm/__main__.py +3 -0
  3. env2llm/_runtime_health.py +29 -0
  4. env2llm/artifact_layout.py +1 -0
  5. env2llm/bootstrap.py +84 -0
  6. env2llm/bridge.py +225 -0
  7. env2llm/cli.py +57 -0
  8. env2llm/doql/__init__.py +48 -0
  9. env2llm/doql/context_blocks.py +234 -0
  10. env2llm/doql/models.py +134 -0
  11. env2llm/doql/parse.py +482 -0
  12. env2llm/doql/render.py +49 -0
  13. env2llm/doql/runtime.py +206 -0
  14. env2llm/doql_context.py +26 -0
  15. env2llm/env.py +72 -0
  16. env2llm/formats/__init__.py +59 -0
  17. env2llm/formats/doql_less.py +11 -0
  18. env2llm/formats/json_fmt.py +13 -0
  19. env2llm/formats/markdown.py +52 -0
  20. env2llm/formats/yaml_fmt.py +13 -0
  21. env2llm/generate.py +202 -0
  22. env2llm/ir.py +274 -0
  23. env2llm/layout.py +266 -0
  24. env2llm/policy/__init__.py +4 -0
  25. env2llm/policy/invoice.py +52 -0
  26. env2llm/policy/process.py +337 -0
  27. env2llm/policy/validations.py +73 -0
  28. env2llm/registry.py +230 -0
  29. env2llm/render/__init__.py +3 -0
  30. env2llm/render/doql/__init__.py +5 -0
  31. env2llm/render/doql/blocks.py +300 -0
  32. env2llm/render/doql/helpers.py +45 -0
  33. env2llm/render/doql/render.py +48 -0
  34. env2llm/runtimes.py +174 -0
  35. env2llm/sources.py +67 -0
  36. env2llm/system_map_bridge.py +1 -0
  37. env2llm/system_map_generator.py +1 -0
  38. env2llm/system_map_ir.py +1 -0
  39. env2llm/system_map_models.py +48 -0
  40. env2llm/system_map_render.py +1 -0
  41. env2llm/system_map_runtimes.py +1 -0
  42. env2llm-0.1.1.dist-info/METADATA +121 -0
  43. env2llm-0.1.1.dist-info/RECORD +47 -0
  44. env2llm-0.1.1.dist-info/WHEEL +5 -0
  45. env2llm-0.1.1.dist-info/entry_points.txt +2 -0
  46. env2llm-0.1.1.dist-info/licenses/LICENSE +201 -0
  47. env2llm-0.1.1.dist-info/top_level.txt +1 -0
env2llm/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """
2
+ env2llm — environment introspection and LLM context generation.
3
+
4
+ Builds machine-readable maps of available services, artifacts, commands,
5
+ and environment variables (default: ``environment.doql.less``).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from env2llm.bootstrap import ensure_environment_map, project_artifact_root
11
+ from env2llm.env import collect_environment, mask_secret, merge_environment
12
+ from env2llm.formats import render_format, SUPPORTED_FORMATS
13
+ from env2llm.generate import generate_system_map
14
+ from env2llm.ir import SystemMapIR
15
+ from env2llm.layout import resolve_registry_path, write_registry
16
+ from env2llm.registry import refresh_doql_registry
17
+
18
+ __all__ = [
19
+ "SystemMapIR",
20
+ "SUPPORTED_FORMATS",
21
+ "collect_environment",
22
+ "ensure_environment_map",
23
+ "generate_system_map",
24
+ "mask_secret",
25
+ "merge_environment",
26
+ "project_artifact_root",
27
+ "refresh_doql_registry",
28
+ "render_format",
29
+ "resolve_registry_path",
30
+ "write_registry",
31
+ ]
32
+
33
+ __version__ = "0.1.1"
env2llm/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from env2llm.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,29 @@
1
+ """Minimal runtime routing helpers (no HTTP health probes)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ _WORKER_ACTIONS = frozenset(
6
+ {
7
+ "send_invoice",
8
+ "generate_invoice",
9
+ "send_email",
10
+ "generate_report",
11
+ "crm_update",
12
+ "notify_slack",
13
+ "notify_telegram",
14
+ "notify_teams",
15
+ "generate_code",
16
+ }
17
+ )
18
+
19
+
20
+ def runtime_id_for_intent(intent: str | None) -> str | None:
21
+ if not intent:
22
+ return None
23
+ if intent.startswith("mullm_"):
24
+ return "delegate:mullm"
25
+ if intent.startswith("system_"):
26
+ return "orchestrator:nlp-service"
27
+ if intent in _WORKER_ACTIONS:
28
+ return "executor:worker"
29
+ return None
@@ -0,0 +1 @@
1
+ from env2llm.layout import *
env2llm/bootstrap.py ADDED
@@ -0,0 +1,84 @@
1
+ """Bootstrap environment map files for a project directory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Any, Mapping
8
+
9
+ from env2llm.env import collect_environment, merge_environment
10
+ from env2llm.formats import default_output_name, render_format
11
+ from env2llm.generate import generate_system_map
12
+ from env2llm.layout import ensure_layout, write_registry
13
+ from env2llm.policy.invoice import apply_invoice_policies
14
+ from env2llm.policy.process import apply_process_policies
15
+ from env2llm.registry import merge_registry_observations
16
+
17
+
18
+ def project_artifact_root(project_dir: Path | str) -> Path:
19
+ return Path(project_dir).resolve() / ".nlp2dsl"
20
+
21
+
22
+ def ensure_environment_map(
23
+ project_dir: Path | str,
24
+ *,
25
+ project_id: str | None = None,
26
+ output_format: str = "doql.less",
27
+ environment: Mapping[str, str] | None = None,
28
+ client: Any | None = None,
29
+ merge_existing: bool = True,
30
+ attachment: bool | None = None,
31
+ auto_execute: bool | None = None,
32
+ ) -> Path:
33
+ """
34
+ Generate and write the environment map for LLM/orchestrator context.
35
+
36
+ Default output: ``.nlp2dsl/registry/environment.doql.less``.
37
+ Other formats: yaml, json, markdown (see ``env2llm.formats``).
38
+ """
39
+ root = Path(project_dir).resolve()
40
+ project = project_id or root.name
41
+ artifact_root = project_artifact_root(root)
42
+ ensure_layout(artifact_root)
43
+
44
+ env = merge_environment(collect_environment(), environment)
45
+ ir = generate_system_map(
46
+ root,
47
+ example_id=project,
48
+ environment=env,
49
+ client=client,
50
+ )
51
+
52
+ if auto_execute is None:
53
+ auto_execute = os.environ.get("NLP2DSL_AUTO_EXECUTE", "1").strip().lower() in (
54
+ "1",
55
+ "true",
56
+ "yes",
57
+ )
58
+ if auto_execute:
59
+ ir.conversation.sync_auto_execute = True
60
+
61
+ registry_path = artifact_root / "registry" / "environment.doql.less"
62
+ if merge_existing and registry_path.is_file():
63
+ merge_registry_observations(ir, registry_path)
64
+
65
+ repo_root = root.parent.parent if root.parent.name == "examples" else root.parent
66
+ apply_process_policies(ir, example_id=project, repo_root=repo_root)
67
+ apply_invoice_policies(ir, example_id=project, attachment=attachment)
68
+
69
+ content = render_format(ir, output_format)
70
+ out_name = default_output_name(output_format)
71
+ if out_name == "environment.doql.less":
72
+ path = write_registry(artifact_root, content)
73
+ else:
74
+ out_dir = artifact_root / "registry"
75
+ out_dir.mkdir(parents=True, exist_ok=True)
76
+ path = out_dir / out_name
77
+ path.write_text(content, encoding="utf-8")
78
+ mirror = artifact_root / out_name
79
+ if mirror != path:
80
+ mirror.write_text(content, encoding="utf-8")
81
+
82
+ os.environ.setdefault("ENV2LLM_CONTEXT", str(path))
83
+ os.environ.setdefault("NLP2DSL_DOQL_CONTEXT", str(path))
84
+ return path
env2llm/bridge.py ADDED
@@ -0,0 +1,225 @@
1
+ """Bridge: static DoqlTaskContext (today) ↔ SystemMapIR (target)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from env2llm.doql import DoqlArtifact, DoqlCommand, DoqlTaskContext, load_doql_context
8
+ from env2llm.ir import (
9
+ AccessGrantIR,
10
+ ArtifactSpecIR,
11
+ CommandSchemaIR,
12
+ ConversationPolicyIR,
13
+ FieldSpec,
14
+ MimeTypeSpec,
15
+ ProcessAccessScopeIR,
16
+ ProcessPathsIR,
17
+ ProcessPolicyIR,
18
+ ProtocolSpec,
19
+ ResourceSpecIR,
20
+ RuntimeSpecIR,
21
+ SystemMapIR,
22
+ )
23
+ from env2llm.runtimes import build_runtimes_for_example, load_example_profile, resolve_command_runtime
24
+ from env2llm.policy.process import apply_process_policies
25
+
26
+ # Bootstrap field schemas when services.yaml omits required/optional
27
+ _COMMAND_FIELDS: dict[str, tuple[list[str], list[str]]] = {
28
+ "send_invoice": (["amount", "to"], ["currency", "attachment_path"]),
29
+ "generate_invoice": (["amount", "to"], ["currency", "output_path"]),
30
+ "send_email": (["to"], ["subject", "body"]),
31
+ "generate_report": (["report_type"], ["format"]),
32
+ "crm_update": (["record_id"], ["fields"]),
33
+ }
34
+
35
+
36
+ def _mime_for_artifact(art: DoqlArtifact) -> MimeTypeSpec | None:
37
+ path = art.path.lower()
38
+ if path.endswith(".pdf"):
39
+ return MimeTypeSpec(type="application/pdf", schema_ref="InvoiceDocument")
40
+ if path.endswith(".json"):
41
+ return MimeTypeSpec(type="application/json")
42
+ if path.endswith(".txt"):
43
+ return MimeTypeSpec(type="text/plain", schema_ref="InvoiceMetadata")
44
+ return None
45
+
46
+
47
+ def _process_from_ctx(ctx) -> ProcessPolicyIR:
48
+ proc = getattr(ctx, "process", None)
49
+ if proc is None:
50
+ return ProcessPolicyIR()
51
+ if isinstance(proc, ProcessPolicyIR):
52
+ return proc
53
+ return ProcessPolicyIR(
54
+ mode=getattr(proc, "mode", "balanced"),
55
+ nlp_parser=getattr(proc, "nlp_parser", "auto"),
56
+ nlp_confidence_min=float(getattr(proc, "nlp_confidence_min", 0.5)),
57
+ nlp_enrich_missing=bool(getattr(proc, "nlp_enrich_missing", False)),
58
+ llm_reasoning=getattr(proc, "llm_reasoning", "shallow"),
59
+ llm_temperature=getattr(proc, "llm_temperature", None),
60
+ autonomous_enabled=bool(getattr(proc, "autonomous_enabled", True)),
61
+ autonomous_max_rounds=int(getattr(proc, "autonomous_max_rounds", 8)),
62
+ ask_user=getattr(proc, "ask_user", "when_exhausted"),
63
+ intract_gate=bool(getattr(proc, "intract_gate", False)),
64
+ intract_enforce_clarification=bool(getattr(proc, "intract_enforce_clarification", False)),
65
+ access=ProcessAccessScopeIR(
66
+ agent=str(getattr(proc, "agent", "") or getattr(getattr(proc, "access", None), "agent", "")),
67
+ allow_resource_areas=list(
68
+ getattr(proc, "allow_resource_areas", None)
69
+ or getattr(getattr(proc, "access", None), "allow_resource_areas", [])
70
+ or []
71
+ ),
72
+ deny_resource_areas=list(
73
+ getattr(proc, "deny_resource_areas", None)
74
+ or getattr(getattr(proc, "access", None), "deny_resource_areas", [])
75
+ or []
76
+ ),
77
+ ),
78
+ paths=ProcessPathsIR(
79
+ read=list(getattr(proc, "paths_read", None) or getattr(getattr(proc, "paths", None), "read", []) or []),
80
+ write=list(getattr(proc, "paths_write", None) or getattr(getattr(proc, "paths", None), "write", []) or []),
81
+ ),
82
+ )
83
+
84
+
85
+ def task_context_to_system_map(ctx: DoqlTaskContext, *, example_dir: Path | str | None = None) -> SystemMapIR:
86
+ """Convert hardcoded/bootstrap context into SystemMapIR (migration helper)."""
87
+ profile = None
88
+ if example_dir is not None:
89
+ root = Path(example_dir).resolve()
90
+ repo_root = root.parent.parent if root.parent.name == "examples" else root.parent
91
+ profile = load_example_profile(ctx.example_name, repo_root)
92
+
93
+ commands: list[CommandSchemaIR] = []
94
+ for cmd in ctx.commands or []:
95
+ commands.append(_command_to_ir(cmd, profile=profile))
96
+ if not commands and ctx.capabilities:
97
+ for name in ctx.capabilities:
98
+ commands.append(
99
+ CommandSchemaIR(
100
+ name=name,
101
+ runtime=resolve_command_runtime(name, profile=profile),
102
+ protocol=ProtocolSpec(name="workflow/run", transport="backend→worker"),
103
+ )
104
+ )
105
+
106
+ runtimes: list[RuntimeSpecIR] = []
107
+ if ctx.runtimes:
108
+ runtimes = [
109
+ RuntimeSpecIR(
110
+ id=r.id,
111
+ kind=r.kind if r.kind in (
112
+ "orchestrator", "gateway", "worker", "llm", "database", "cache", "mock", "external"
113
+ ) else "worker",
114
+ url=r.url or None,
115
+ uri=r.uri or None,
116
+ health=r.health or None,
117
+ docker_profile=r.docker_profile or None,
118
+ model=r.model or None,
119
+ roles=list(r.roles),
120
+ status=r.status if r.status in ("available", "unavailable", "unknown") else "unknown",
121
+ )
122
+ for r in ctx.runtimes
123
+ ]
124
+ elif example_dir is not None:
125
+ runtimes = build_runtimes_for_example(
126
+ ctx.example_name,
127
+ example_dir=example_dir,
128
+ environment=ctx.environment,
129
+ )
130
+
131
+ ir = SystemMapIR(
132
+ example_id=ctx.example_name,
133
+ environment=dict(ctx.environment),
134
+ data=dict(ctx.data),
135
+ runtimes=runtimes,
136
+ commands=commands,
137
+ resources=[
138
+ ResourceSpecIR(
139
+ id=r.id,
140
+ title=r.title,
141
+ connector=r.connector,
142
+ uri_patterns=list(r.uri_patterns),
143
+ )
144
+ for r in ctx.resources
145
+ ],
146
+ access=[
147
+ AccessGrantIR(
148
+ agent=a.agent,
149
+ resource_area=a.resource_area,
150
+ actions=list(a.actions),
151
+ effect=a.effect if a.effect in ("allow", "deny", "approval") else "allow",
152
+ )
153
+ for a in ctx.access
154
+ ],
155
+ artifacts=[
156
+ ArtifactSpecIR(
157
+ path=a.path,
158
+ kind=a.kind,
159
+ mime=_mime_for_artifact(a),
160
+ values=dict(a.values),
161
+ )
162
+ for a in ctx.artifacts
163
+ ],
164
+ capabilities=list(ctx.capabilities),
165
+ workflow_history=dict(ctx.workflow_history),
166
+ conversation=ConversationPolicyIR(
167
+ autofill=ctx.autofill,
168
+ attachment_required=ctx.attachment_required,
169
+ generate_invoice_if_missing=ctx.generate_invoice_if_missing,
170
+ sync_auto_execute=ctx.sync_auto_execute,
171
+ strict_pdf=ctx.strict_pdf,
172
+ ),
173
+ process=_process_from_ctx(ctx),
174
+ metadata={"source": "doql_context.bootstrap"},
175
+ )
176
+ if example_dir is not None:
177
+ root = Path(example_dir).resolve()
178
+ repo_root = root.parent.parent if root.parent.name == "examples" else root.parent
179
+ apply_process_policies(ir, example_id=ctx.example_name, repo_root=repo_root)
180
+ if ctx.validations:
181
+ ir.validations = list(ctx.validations)
182
+ return ir
183
+
184
+
185
+ def _command_to_ir(cmd: DoqlCommand, *, profile: dict | None = None) -> CommandSchemaIR:
186
+ required = list(cmd.required)
187
+ optional = list(cmd.optional)
188
+ if not required and cmd.name in _COMMAND_FIELDS:
189
+ required, optional = _COMMAND_FIELDS[cmd.name]
190
+ fields = [
191
+ *[FieldSpec(name=n, required=True) for n in required],
192
+ *[FieldSpec(name=n, required=False) for n in optional],
193
+ ]
194
+ protocol_name = "workflow/run"
195
+ transport = cmd.transport
196
+ if cmd.transport == "nlp-service/system":
197
+ protocol_name = "propact:shell"
198
+ elif "notify" in cmd.name:
199
+ protocol_name = "workflow/run"
200
+ runtime_id = cmd.runtime or resolve_command_runtime(cmd.name, profile=profile)
201
+ if runtime_id == "orchestrator:nlp-service":
202
+ transport = "nlp-service/system"
203
+ elif runtime_id == "delegate:mullm":
204
+ transport = "nlp-service→mullm"
205
+ elif runtime_id == "executor:worker":
206
+ transport = "gateway:backend→executor:worker"
207
+ return CommandSchemaIR(
208
+ name=cmd.name,
209
+ description=cmd.description,
210
+ runtime=runtime_id,
211
+ protocol=ProtocolSpec(
212
+ name=protocol_name,
213
+ transport=transport,
214
+ endpoint=cmd.endpoint,
215
+ ),
216
+ fields=fields,
217
+ input_model=f"{''.join(p.title() for p in cmd.name.split('_'))}Config",
218
+ )
219
+
220
+
221
+ def doql_file_to_system_map(path: Path | str) -> SystemMapIR:
222
+ """Parse environment.doql.less → SystemMapIR (round-trip via DoqlTaskContext)."""
223
+ path = Path(path)
224
+ ctx = load_doql_context(path)
225
+ return task_context_to_system_map(ctx, example_dir=path.parent.parent)
env2llm/cli.py ADDED
@@ -0,0 +1,57 @@
1
+ """CLI for env2llm — generate environment maps for LLM context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from env2llm.bootstrap import ensure_environment_map
10
+ from env2llm.formats import SUPPORTED_FORMATS, normalize_format
11
+
12
+
13
+ def build_parser() -> argparse.ArgumentParser:
14
+ parser = argparse.ArgumentParser(
15
+ prog="env2llm",
16
+ description="Generate environment maps (services, artifacts, env) for LLM decision-making.",
17
+ )
18
+ parser.add_argument(
19
+ "project_dir",
20
+ nargs="?",
21
+ default=".",
22
+ help="Project or example directory (default: cwd)",
23
+ )
24
+ parser.add_argument(
25
+ "--project-id",
26
+ default=None,
27
+ help="Logical project id (default: directory name)",
28
+ )
29
+ parser.add_argument(
30
+ "--format",
31
+ "-f",
32
+ default="doql.less",
33
+ help=f"Output format: {', '.join(sorted(SUPPORTED_FORMATS))}",
34
+ )
35
+ parser.add_argument(
36
+ "--no-merge",
37
+ action="store_true",
38
+ help="Do not merge observations from an existing registry file",
39
+ )
40
+ return parser
41
+
42
+
43
+ def main(argv: list[str] | None = None) -> int:
44
+ args = build_parser().parse_args(argv)
45
+ fmt = normalize_format(args.format)
46
+ path = ensure_environment_map(
47
+ Path(args.project_dir),
48
+ project_id=args.project_id,
49
+ output_format=fmt,
50
+ merge_existing=not args.no_merge,
51
+ )
52
+ print(path)
53
+ return 0
54
+
55
+
56
+ if __name__ == "__main__":
57
+ raise SystemExit(main())
@@ -0,0 +1,48 @@
1
+ """DOQL package — models, parse, render, runtime (split from doql_context)."""
2
+
3
+ from .models import (
4
+ DoqlAccess,
5
+ DoqlArtifact,
6
+ DoqlCommand,
7
+ DoqlProcessPolicy,
8
+ DoqlResource,
9
+ DoqlRuntime,
10
+ DoqlTaskContext,
11
+ )
12
+ from .parse import (
13
+ collect_task_context,
14
+ enrich_task_context_from_client,
15
+ load_doql_context,
16
+ load_platform_map,
17
+ parse_fixture_metadata,
18
+ )
19
+ from .render import render_doql_context, write_doql_context
20
+ from .runtime import (
21
+ autofill_entities,
22
+ context_inline_payload,
23
+ load_doql_inline_from_env,
24
+ merge_inline_context,
25
+ resolve_doql_context_path,
26
+ )
27
+
28
+ __all__ = [
29
+ "DoqlAccess",
30
+ "DoqlArtifact",
31
+ "DoqlCommand",
32
+ "DoqlProcessPolicy",
33
+ "DoqlResource",
34
+ "DoqlRuntime",
35
+ "DoqlTaskContext",
36
+ "autofill_entities",
37
+ "collect_task_context",
38
+ "context_inline_payload",
39
+ "enrich_task_context_from_client",
40
+ "load_doql_context",
41
+ "load_doql_inline_from_env",
42
+ "load_platform_map",
43
+ "merge_inline_context",
44
+ "parse_fixture_metadata",
45
+ "render_doql_context",
46
+ "resolve_doql_context_path",
47
+ "write_doql_context",
48
+ ]