@delta-infra/cli 1.0.79 → 1.0.81
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.
- package/README.md +427 -437
- package/checksums.txt +5 -5
- package/package.json +2 -13
- package/scripts/install-wizard.js +625 -616
- package/scripts/install.js +417 -417
- package/scripts/run.js +65 -65
- package/skills/delta-sandbox/SKILL.md +356 -356
- package/skills/delta-sandbox/agents/openai.yaml +4 -4
- package/skills/delta-sandbox/references/commands.md +56 -56
- package/skills/delta-sandbox/references/lifecycle.md +92 -92
- package/skills/delta-sandbox/references/recipes.md +133 -133
- package/skills/delta-science/SKILL.md +149 -170
- package/skills/delta-science/agents/openai.yaml +4 -4
- package/skills/delta-science/references/antbo.md +100 -0
- package/skills/delta-science/references/cli-contract.md +74 -75
- package/skills/delta-science/references/delta-bo.md +90 -0
- package/skills/{gsasii/references/endpoints.md → delta-science/references/gsasii.md} +116 -130
- package/skills/{lammps/references/endpoints.md → delta-science/references/lammps.md} +66 -61
- package/skills/{ldm-bo/references/endpoints.md → delta-science/references/ldm-bo.md} +127 -124
- package/skills/delta-science/references/ldm.md +72 -0
- package/skills/{pubchem/references/endpoints.md → delta-science/references/pubchem.md} +127 -122
- package/skills/{pymatgen/references/endpoints.md → delta-science/references/pymatgen.md} +68 -68
- package/skills/{rdkit/references/endpoints.md → delta-science/references/rdkit.md} +139 -133
- package/skills/delta-science/references/routing-index.md +36 -0
- package/skills/delta-science/references/strbo.md +87 -0
- package/skills/{synbo-service/references/endpoints.md → delta-science/references/synbo.md} +197 -157
- package/skills/delta-science/references/workflows.md +58 -41
- package/skills/delta-science/scripts/catalog.py +92 -0
- package/skills/delta-science/scripts/invoke.py +310 -227
- package/skills/delta-shared/SKILL.md +95 -95
- package/skills/delta-shared/agents/openai.yaml +4 -4
- package/skills/manifest.json +3 -1
- package/skills/antbo-ldm-guard/SKILL.md +0 -58
- package/skills/antbo-ldm-guard/agents/openai.yaml +0 -4
- package/skills/antbo-service/SKILL.md +0 -79
- package/skills/antbo-service/agents/openai.yaml +0 -4
- package/skills/antbo-service/references/operations.md +0 -45
- package/skills/delta-bo/SKILL.md +0 -36
- package/skills/delta-bo/agents/openai.yaml +0 -4
- package/skills/delta-bo/references/endpoints.md +0 -263
- package/skills/delta-science/references/generated-catalog.md +0 -48
- package/skills/delta-science/references/operation-contracts.md +0 -137
- package/skills/delta-science/references/services.md +0 -112
- package/skills/delta-science/scripts/generated_catalog.py +0 -69
- package/skills/gsasii/SKILL.md +0 -42
- package/skills/gsasii/agents/openai.yaml +0 -4
- package/skills/lammps/SKILL.md +0 -39
- package/skills/lammps/agents/openai.yaml +0 -4
- package/skills/ldm-bo/SKILL.md +0 -35
- package/skills/ldm-bo/agents/openai.yaml +0 -4
- package/skills/pubchem/SKILL.md +0 -93
- package/skills/pubchem/agents/openai.yaml +0 -4
- package/skills/pubchem/references/operations.md +0 -41
- package/skills/pymatgen/SKILL.md +0 -54
- package/skills/pymatgen/agents/openai.yaml +0 -4
- package/skills/rdkit/SKILL.md +0 -112
- package/skills/rdkit/agents/openai.yaml +0 -4
- package/skills/rdkit/references/operations.md +0 -64
- package/skills/science-tools.json +0 -127
- package/skills/synbo-service/SKILL.md +0 -36
- package/skills/synbo-service/agents/openai.yaml +0 -4
|
@@ -1,263 +1,346 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Invoke one Delta Science operation strictly through delta-cli."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import json
|
|
8
|
-
import os
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
import shutil
|
|
11
|
-
import subprocess
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Invoke one Delta Science operation strictly through delta-cli."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
12
|
import sys
|
|
13
13
|
import time
|
|
14
14
|
from typing import Any
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from generated_catalog import LEGACY_ENDPOINTS, LEGACY_TOOL_ALIASES
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
|
|
20
16
|
SKILL_DIR = Path(__file__).resolve().parents[1]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
17
|
+
_CLI_ERROR_TYPES = {
|
|
18
|
+
2: "validation",
|
|
19
|
+
3: "auth",
|
|
20
|
+
4: "permission",
|
|
21
|
+
5: "not_found",
|
|
22
|
+
6: "network",
|
|
23
|
+
7: "api",
|
|
24
|
+
10: "internal",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _json_object(raw: str | None, flag: str) -> dict[str, Any] | None:
|
|
29
|
+
if raw is None:
|
|
30
|
+
return None
|
|
31
|
+
try:
|
|
32
|
+
value = json.loads(raw)
|
|
33
|
+
except json.JSONDecodeError as exc:
|
|
34
|
+
raise ValueError(f"{flag} is not valid JSON: {exc}") from exc
|
|
35
|
+
if not isinstance(value, dict):
|
|
36
|
+
raise ValueError(f"{flag} must decode to a JSON object")
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _normalize_synbo_payload(
|
|
41
|
+
tool: str,
|
|
42
|
+
endpoint: str,
|
|
43
|
+
data: dict[str, Any] | None,
|
|
44
|
+
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
|
|
45
|
+
"""Normalize SynBO's discrete condition contract before any remote request."""
|
|
46
|
+
canonical_tool = tool.removesuffix("-service")
|
|
47
|
+
if canonical_tool != "synbo" or endpoint not in {"initialize", "optimize"}:
|
|
48
|
+
return data, None
|
|
49
|
+
if data is None:
|
|
50
|
+
raise ValueError(f"synbo/{endpoint} requires --data-json")
|
|
51
|
+
|
|
52
|
+
condition_dict = data.get("condition_dict")
|
|
53
|
+
if not isinstance(condition_dict, dict) or not condition_dict:
|
|
54
|
+
raise ValueError("SynBO condition_dict must be a non-empty object")
|
|
55
|
+
|
|
56
|
+
normalized_conditions: dict[str, list[str]] = {}
|
|
57
|
+
for name, raw_values in condition_dict.items():
|
|
58
|
+
if not isinstance(raw_values, list) or not raw_values:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"SynBO condition_dict.{name} must be a non-empty explicit list; "
|
|
61
|
+
"range objects are not supported"
|
|
62
|
+
)
|
|
63
|
+
values = [str(value) for value in raw_values]
|
|
64
|
+
if any(value == "" for value in values):
|
|
65
|
+
raise ValueError(f"SynBO condition_dict.{name} contains an empty value")
|
|
66
|
+
if len(set(values)) != len(values):
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"SynBO condition_dict.{name} contains duplicate values after "
|
|
69
|
+
"string normalization"
|
|
70
|
+
)
|
|
71
|
+
normalized_conditions[str(name)] = values
|
|
72
|
+
|
|
73
|
+
normalized = dict(data)
|
|
74
|
+
normalized["condition_dict"] = normalized_conditions
|
|
75
|
+
normalization = {
|
|
76
|
+
"condition_values": "strings",
|
|
77
|
+
"condition_space_size": _condition_space_size(normalized_conditions),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if endpoint == "initialize":
|
|
81
|
+
return normalized, normalization
|
|
82
|
+
|
|
83
|
+
metrics = normalized.get("opt_metrics")
|
|
84
|
+
if not isinstance(metrics, list) or not metrics:
|
|
85
|
+
raise ValueError("SynBO optimize requires a non-empty opt_metrics list")
|
|
86
|
+
metric_names = [str(metric) for metric in metrics]
|
|
87
|
+
normalized["opt_metrics"] = metric_names
|
|
88
|
+
|
|
89
|
+
previous_results = normalized.get("previous_results")
|
|
90
|
+
if not isinstance(previous_results, list) or not previous_results:
|
|
91
|
+
raise ValueError("SynBO optimize requires non-empty previous_results")
|
|
92
|
+
|
|
93
|
+
normalized_rows: list[dict[str, Any]] = []
|
|
94
|
+
for index, raw_row in enumerate(previous_results):
|
|
95
|
+
if not isinstance(raw_row, dict):
|
|
96
|
+
raise ValueError(f"SynBO previous_results[{index}] must be an object")
|
|
97
|
+
row = dict(raw_row)
|
|
98
|
+
for condition_name, allowed_values in normalized_conditions.items():
|
|
99
|
+
if condition_name not in row:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"SynBO previous_results[{index}] is missing condition "
|
|
102
|
+
f"{condition_name!r}"
|
|
103
|
+
)
|
|
104
|
+
value = str(row[condition_name])
|
|
105
|
+
if value not in allowed_values:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"SynBO previous_results[{index}].{condition_name}={value!r} "
|
|
108
|
+
"is not present in condition_dict"
|
|
109
|
+
)
|
|
110
|
+
row[condition_name] = value
|
|
111
|
+
for metric_name in metric_names:
|
|
112
|
+
metric_value = row.get(metric_name)
|
|
113
|
+
if (
|
|
114
|
+
metric_name not in row
|
|
115
|
+
or isinstance(metric_value, bool)
|
|
116
|
+
or not isinstance(metric_value, (int, float))
|
|
117
|
+
):
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"SynBO previous_results[{index}].{metric_name} must be numeric"
|
|
120
|
+
)
|
|
121
|
+
normalized_rows.append(row)
|
|
122
|
+
|
|
123
|
+
normalized["previous_results"] = normalized_rows
|
|
124
|
+
normalized.setdefault("accuracy", "tiny")
|
|
125
|
+
normalized.setdefault("device", "cpu")
|
|
126
|
+
normalized.setdefault("surrogate_model", "RF")
|
|
127
|
+
normalized.setdefault("acq_func", "UCB")
|
|
128
|
+
normalization["defaults"] = {
|
|
129
|
+
key: normalized[key]
|
|
130
|
+
for key in ("accuracy", "device", "surrogate_model", "acq_func")
|
|
131
|
+
}
|
|
132
|
+
return normalized, normalization
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _condition_space_size(condition_dict: dict[str, list[str]]) -> int:
|
|
136
|
+
size = 1
|
|
137
|
+
for values in condition_dict.values():
|
|
138
|
+
size *= len(values)
|
|
139
|
+
return size
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _runtime_config() -> dict[str, Any]:
|
|
143
|
+
path = SKILL_DIR / "runtime.local.json"
|
|
144
|
+
if not path.is_file():
|
|
145
|
+
return {}
|
|
146
|
+
try:
|
|
147
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
148
|
+
except (OSError, json.JSONDecodeError):
|
|
149
|
+
return {}
|
|
150
|
+
return value if isinstance(value, dict) else {}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _resolve_cli(explicit: str | None, runtime: dict[str, Any]) -> str:
|
|
154
|
+
candidates: list[str | None] = [
|
|
155
|
+
explicit,
|
|
156
|
+
os.environ.get("DELTA_CLI_PATH"),
|
|
157
|
+
runtime.get("cli_path"),
|
|
158
|
+
shutil.which("delta-cli"),
|
|
159
|
+
shutil.which("delta-cli.exe"),
|
|
160
|
+
]
|
|
161
|
+
package_root = SKILL_DIR.parents[1]
|
|
162
|
+
exe_name = "delta-cli.exe" if os.name == "nt" else "delta-cli"
|
|
163
|
+
candidates.append(str(package_root / "bin" / exe_name))
|
|
164
|
+
for candidate in candidates:
|
|
165
|
+
if candidate and Path(candidate).is_file():
|
|
166
|
+
return str(Path(candidate).resolve())
|
|
167
|
+
raise FileNotFoundError(
|
|
168
|
+
"delta-cli not found; install @delta-infra/cli, set DELTA_CLI_PATH, "
|
|
169
|
+
"or add cli_path to delta-science/runtime.local.json"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
115
173
|
def _unwrap(stdout: str) -> tuple[Any, int]:
|
|
116
|
-
try:
|
|
117
|
-
root = json.loads(stdout)
|
|
118
|
-
except json.JSONDecodeError as exc:
|
|
119
|
-
raise RuntimeError(f"CLI stdout is not JSON: {exc}") from exc
|
|
120
|
-
if not isinstance(root, dict):
|
|
121
|
-
raise RuntimeError("CLI stdout root is not an object")
|
|
122
|
-
if root.get("ok") is not True:
|
|
123
|
-
raise RuntimeError(json.dumps(root.get("error", root), ensure_ascii=False))
|
|
124
|
-
if "data" not in root:
|
|
125
|
-
raise RuntimeError("CLI success envelope is missing data")
|
|
126
|
-
|
|
127
|
-
value: Any = root["data"]
|
|
128
|
-
depth = 0
|
|
129
|
-
while isinstance(value, dict):
|
|
130
|
-
if "status_code" in value and "data" in value:
|
|
131
|
-
status_code = value.get("status_code")
|
|
132
|
-
if not isinstance(status_code, int) or not 200 <= status_code < 300:
|
|
133
|
-
raise RuntimeError(json.dumps(value, ensure_ascii=False))
|
|
134
|
-
value = value["data"]
|
|
135
|
-
elif (
|
|
136
|
-
"code" in value
|
|
137
|
-
and "data" in value
|
|
138
|
-
and set(value).intersection({"message", "msg"})
|
|
139
|
-
):
|
|
140
|
-
code = value.get("code")
|
|
141
|
-
if code not in (0, "0", None):
|
|
142
|
-
raise RuntimeError(json.dumps(value, ensure_ascii=False))
|
|
143
|
-
value = value["data"]
|
|
144
|
-
else:
|
|
145
|
-
break
|
|
146
|
-
depth += 1
|
|
147
|
-
if depth > 4:
|
|
148
|
-
raise RuntimeError("too many nested service envelopes")
|
|
149
|
-
if isinstance(value, dict) and value.get("valid") is False:
|
|
150
|
-
raise RuntimeError(json.dumps({
|
|
151
|
-
"valid": False,
|
|
152
|
-
"error": value.get("error"),
|
|
153
|
-
"warnings": value.get("warnings"),
|
|
154
|
-
}, ensure_ascii=False))
|
|
155
|
-
return value, depth
|
|
174
|
+
try:
|
|
175
|
+
root = json.loads(stdout)
|
|
176
|
+
except json.JSONDecodeError as exc:
|
|
177
|
+
raise RuntimeError(f"CLI stdout is not JSON: {exc}") from exc
|
|
178
|
+
if not isinstance(root, dict):
|
|
179
|
+
raise RuntimeError("CLI stdout root is not an object")
|
|
180
|
+
if root.get("ok") is not True:
|
|
181
|
+
raise RuntimeError(json.dumps(root.get("error", root), ensure_ascii=False))
|
|
182
|
+
if "data" not in root:
|
|
183
|
+
raise RuntimeError("CLI success envelope is missing data")
|
|
184
|
+
|
|
185
|
+
value: Any = root["data"]
|
|
186
|
+
depth = 0
|
|
187
|
+
while isinstance(value, dict):
|
|
188
|
+
if "status_code" in value and "data" in value:
|
|
189
|
+
status_code = value.get("status_code")
|
|
190
|
+
if not isinstance(status_code, int) or not 200 <= status_code < 300:
|
|
191
|
+
raise RuntimeError(json.dumps(value, ensure_ascii=False))
|
|
192
|
+
value = value["data"]
|
|
193
|
+
elif (
|
|
194
|
+
"code" in value
|
|
195
|
+
and "data" in value
|
|
196
|
+
and set(value).intersection({"message", "msg"})
|
|
197
|
+
):
|
|
198
|
+
code = value.get("code")
|
|
199
|
+
if code not in (0, "0", None):
|
|
200
|
+
raise RuntimeError(json.dumps(value, ensure_ascii=False))
|
|
201
|
+
value = value["data"]
|
|
202
|
+
else:
|
|
203
|
+
break
|
|
204
|
+
depth += 1
|
|
205
|
+
if depth > 4:
|
|
206
|
+
raise RuntimeError("too many nested service envelopes")
|
|
207
|
+
if isinstance(value, dict) and value.get("valid") is False:
|
|
208
|
+
raise RuntimeError(json.dumps({
|
|
209
|
+
"valid": False,
|
|
210
|
+
"error": value.get("error"),
|
|
211
|
+
"warnings": value.get("warnings"),
|
|
212
|
+
}, ensure_ascii=False))
|
|
213
|
+
return value, depth
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _validate_native(tool: str, endpoint: str, native: Any) -> None:
|
|
217
|
+
canonical_tool = tool.removesuffix("-service")
|
|
218
|
+
if canonical_tool == "synbo" and endpoint == "optimize":
|
|
219
|
+
if not isinstance(native, dict):
|
|
220
|
+
raise RuntimeError("SynBO optimize returned a non-object result")
|
|
221
|
+
recommendations = native.get("recommendations")
|
|
222
|
+
if not isinstance(recommendations, list) or not recommendations:
|
|
223
|
+
raise RuntimeError("SynBO optimize returned no recommendations")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _parse_cli_error(completed: subprocess.CompletedProcess[str]) -> tuple[str, str]:
|
|
227
|
+
detail = completed.stderr.strip() or completed.stdout.strip()
|
|
228
|
+
error_type = _CLI_ERROR_TYPES.get(completed.returncode, "cli")
|
|
229
|
+
try:
|
|
230
|
+
envelope = json.loads(detail)
|
|
231
|
+
error = envelope.get("error", {}) if isinstance(envelope, dict) else {}
|
|
232
|
+
if isinstance(error, dict):
|
|
233
|
+
error_type = str(error.get("type") or error_type)
|
|
234
|
+
message = error.get("message")
|
|
235
|
+
if message:
|
|
236
|
+
detail = str(message)
|
|
237
|
+
except (AttributeError, json.JSONDecodeError):
|
|
238
|
+
pass
|
|
239
|
+
return error_type, detail
|
|
156
240
|
|
|
157
241
|
|
|
158
242
|
def _parser() -> argparse.ArgumentParser:
|
|
159
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
160
|
-
parser.add_argument("--tool", required=True)
|
|
161
|
-
parser.add_argument("--endpoint", required=True)
|
|
162
|
-
body = parser.add_mutually_exclusive_group()
|
|
163
|
-
body.add_argument("--data-json")
|
|
243
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
244
|
+
parser.add_argument("--tool", required=True)
|
|
245
|
+
parser.add_argument("--endpoint", required=True)
|
|
246
|
+
body = parser.add_mutually_exclusive_group()
|
|
247
|
+
body.add_argument("--data-json")
|
|
164
248
|
body.add_argument("--params-json")
|
|
165
249
|
parser.add_argument("--cli")
|
|
166
250
|
parser.add_argument("--science-base-url")
|
|
167
|
-
parser.add_argument(
|
|
168
|
-
"--catalog-profile",
|
|
169
|
-
choices=("auto", "canonical", "legacy"),
|
|
170
|
-
default="auto",
|
|
171
|
-
)
|
|
172
251
|
parser.add_argument("--timeout", type=float, default=120.0)
|
|
173
|
-
return parser
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def main() -> int:
|
|
252
|
+
return parser
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def main() -> int:
|
|
177
256
|
args = _parser().parse_args()
|
|
178
257
|
started = time.perf_counter()
|
|
179
258
|
stage = "arguments"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
data = _json_object(args.data_json, "--data-json")
|
|
185
|
-
params = _json_object(args.params_json, "--params-json")
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
259
|
+
cli_exit_code: int | None = None
|
|
260
|
+
error_type = "unknown"
|
|
261
|
+
normalization: dict[str, Any] | None = None
|
|
262
|
+
try:
|
|
263
|
+
data = _json_object(args.data_json, "--data-json")
|
|
264
|
+
params = _json_object(args.params_json, "--params-json")
|
|
265
|
+
data, normalization = _normalize_synbo_payload(
|
|
266
|
+
args.tool, args.endpoint, data
|
|
267
|
+
)
|
|
268
|
+
runtime = _runtime_config()
|
|
269
|
+
stage = "cli-resolution"
|
|
270
|
+
cli = _resolve_cli(args.cli, runtime)
|
|
271
|
+
|
|
272
|
+
env = os.environ.copy()
|
|
191
273
|
base_url = args.science_base_url or runtime.get("science_base_url")
|
|
192
274
|
if base_url:
|
|
193
275
|
env["DELTA_INFRA_SCIENCE_BASE_URL"] = str(base_url)
|
|
194
|
-
configured_base_url = _cli_science_base_url(cli, env, args.timeout)
|
|
195
|
-
requested_profile = str(runtime.get("catalog_profile", args.catalog_profile))
|
|
196
|
-
if requested_profile not in {"auto", "canonical", "legacy"}:
|
|
197
|
-
raise ValueError("catalog_profile must be auto, canonical, or legacy")
|
|
198
|
-
profile = _catalog_profile(configured_base_url, requested_profile)
|
|
199
|
-
resolved_tool, resolved_endpoint = _resolve_catalog_names(
|
|
200
|
-
args.tool, args.endpoint, profile
|
|
201
|
-
)
|
|
202
276
|
|
|
203
277
|
command = [
|
|
204
278
|
cli, "science", "invoke",
|
|
205
|
-
"--tool",
|
|
206
|
-
"--endpoint",
|
|
279
|
+
"--tool", args.tool,
|
|
280
|
+
"--endpoint", args.endpoint,
|
|
207
281
|
]
|
|
208
|
-
if data is not None:
|
|
209
|
-
command.extend(["--data", json.dumps(data, ensure_ascii=False, separators=(",", ":"))])
|
|
210
|
-
if params is not None:
|
|
211
|
-
command.extend(["--params", json.dumps(params, ensure_ascii=False, separators=(",", ":"))])
|
|
212
|
-
|
|
213
|
-
stage = "delta-cli"
|
|
282
|
+
if data is not None:
|
|
283
|
+
command.extend(["--data", json.dumps(data, ensure_ascii=False, separators=(",", ":"))])
|
|
284
|
+
if params is not None:
|
|
285
|
+
command.extend(["--params", json.dumps(params, ensure_ascii=False, separators=(",", ":"))])
|
|
286
|
+
|
|
287
|
+
stage = "delta-cli"
|
|
214
288
|
completed = subprocess.run(
|
|
215
|
-
command,
|
|
216
|
-
capture_output=True,
|
|
217
|
-
text=True,
|
|
218
|
-
encoding="utf-8",
|
|
219
|
-
errors="replace",
|
|
220
|
-
env=env,
|
|
221
|
-
timeout=args.timeout,
|
|
222
|
-
check=False,
|
|
289
|
+
command,
|
|
290
|
+
capture_output=True,
|
|
291
|
+
text=True,
|
|
292
|
+
encoding="utf-8",
|
|
293
|
+
errors="replace",
|
|
294
|
+
env=env,
|
|
295
|
+
timeout=args.timeout,
|
|
296
|
+
check=False,
|
|
223
297
|
)
|
|
224
298
|
if completed.returncode != 0:
|
|
225
|
-
|
|
299
|
+
cli_exit_code = completed.returncode
|
|
300
|
+
error_type, detail = _parse_cli_error(completed)
|
|
226
301
|
raise RuntimeError(f"exit_code={completed.returncode}: {detail}")
|
|
227
302
|
stage = "response-validation"
|
|
228
303
|
native, depth = _unwrap(completed.stdout)
|
|
229
|
-
|
|
230
|
-
|
|
304
|
+
_validate_native(args.tool, args.endpoint, native)
|
|
305
|
+
result = {
|
|
306
|
+
"ok": True,
|
|
231
307
|
"transport": "delta-cli",
|
|
232
308
|
"tool": args.tool,
|
|
233
309
|
"endpoint": args.endpoint,
|
|
234
|
-
"resolved_tool": resolved_tool,
|
|
235
|
-
"resolved_endpoint": resolved_endpoint,
|
|
236
|
-
"catalog_profile": profile,
|
|
237
310
|
"elapsed_seconds": round(time.perf_counter() - started, 3),
|
|
238
|
-
"envelope_depth": depth,
|
|
239
|
-
"native": native,
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
except
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
311
|
+
"envelope_depth": depth,
|
|
312
|
+
"native": native,
|
|
313
|
+
}
|
|
314
|
+
if normalization is not None:
|
|
315
|
+
result["request_normalization"] = normalization
|
|
316
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
317
|
+
return 0
|
|
318
|
+
except subprocess.TimeoutExpired:
|
|
319
|
+
error_type = "timeout"
|
|
320
|
+
error = f"timeout after {args.timeout}s"
|
|
321
|
+
except ValueError as exc:
|
|
322
|
+
error_type = "validation"
|
|
323
|
+
error = str(exc)
|
|
324
|
+
except OSError as exc:
|
|
325
|
+
error_type = "cli_resolution"
|
|
326
|
+
error = str(exc)
|
|
327
|
+
except RuntimeError as exc:
|
|
328
|
+
if error_type == "unknown":
|
|
329
|
+
error_type = "response_validation"
|
|
330
|
+
error = str(exc)
|
|
331
|
+
print(json.dumps({
|
|
332
|
+
"ok": False,
|
|
249
333
|
"transport": "delta-cli",
|
|
250
334
|
"tool": args.tool,
|
|
251
335
|
"endpoint": args.endpoint,
|
|
252
|
-
"resolved_tool": resolved_tool,
|
|
253
|
-
"resolved_endpoint": resolved_endpoint,
|
|
254
|
-
"catalog_profile": profile,
|
|
255
336
|
"stage": stage,
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
337
|
+
"error_type": error_type,
|
|
338
|
+
"cli_exit_code": cli_exit_code,
|
|
339
|
+
"elapsed_seconds": round(time.perf_counter() - started, 3),
|
|
340
|
+
"error": error[:2000],
|
|
341
|
+
}, ensure_ascii=False))
|
|
342
|
+
return 1
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
sys.exit(main())
|