plato-sdk-v2 2.1.16__py3-none-any.whl → 2.1.17__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.
- plato/_sims_generator/__init__.py +19 -4
- plato/_sims_generator/instruction.py +202 -0
- plato/_sims_generator/templates/instruction/helpers.py.jinja +161 -0
- plato/_sims_generator/templates/instruction/init.py.jinja +43 -0
- plato/sims/cli.py +293 -120
- plato/sims/registry.py +78 -4
- plato/v1/cli/pm.py +9 -2
- plato/v1/cli/sandbox.py +6 -1
- plato/worlds/__init__.py +21 -2
- plato/worlds/base.py +33 -0
- plato/worlds/config.py +97 -7
- plato/worlds/runner.py +120 -1
- {plato_sdk_v2-2.1.16.dist-info → plato_sdk_v2-2.1.17.dist-info}/METADATA +1 -1
- {plato_sdk_v2-2.1.16.dist-info → plato_sdk_v2-2.1.17.dist-info}/RECORD +16 -13
- {plato_sdk_v2-2.1.16.dist-info → plato_sdk_v2-2.1.17.dist-info}/WHEEL +0 -0
- {plato_sdk_v2-2.1.16.dist-info → plato_sdk_v2-2.1.17.dist-info}/entry_points.txt +0 -0
plato/sims/cli.py
CHANGED
|
@@ -725,7 +725,7 @@ def _format_response_structure(
|
|
|
725
725
|
return lines
|
|
726
726
|
|
|
727
727
|
|
|
728
|
-
def cmd_info(sim_name: str) -> None:
|
|
728
|
+
def cmd_info(sim_name: str, job_id: str | None = None) -> None:
|
|
729
729
|
"""Show detailed information about a sim."""
|
|
730
730
|
try:
|
|
731
731
|
info = registry.get_sim_info(sim_name)
|
|
@@ -750,6 +750,15 @@ def cmd_info(sim_name: str) -> None:
|
|
|
750
750
|
except ImportError:
|
|
751
751
|
pass
|
|
752
752
|
|
|
753
|
+
# Handle instruction-based sims differently
|
|
754
|
+
if info.sim_type == "instruction":
|
|
755
|
+
_cmd_info_instruction(info, job_id)
|
|
756
|
+
else:
|
|
757
|
+
_cmd_info_api(info)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _cmd_info_api(info) -> None:
|
|
761
|
+
"""Show info for API-based sim."""
|
|
753
762
|
if info.auth:
|
|
754
763
|
print(f"Auth Type: {info.auth.type}")
|
|
755
764
|
|
|
@@ -768,6 +777,71 @@ def cmd_info(sim_name: str) -> None:
|
|
|
768
777
|
print(f" client = await {info.name}.AsyncClient.create({base_url_example})")
|
|
769
778
|
|
|
770
779
|
|
|
780
|
+
def _cmd_info_instruction(info, job_id: str | None) -> None:
|
|
781
|
+
"""Show info for instruction-based sim."""
|
|
782
|
+
print("Type: Instruction-based (no API client)")
|
|
783
|
+
print()
|
|
784
|
+
|
|
785
|
+
# Show available services
|
|
786
|
+
if info.services:
|
|
787
|
+
print("Services:")
|
|
788
|
+
for name, svc in info.services.items():
|
|
789
|
+
port = svc.get("port", "?")
|
|
790
|
+
desc = svc.get("description", "")
|
|
791
|
+
if job_id:
|
|
792
|
+
url = f"https://{job_id}--{port}.connect.plato.so"
|
|
793
|
+
print(f" {name}: {url}")
|
|
794
|
+
if desc:
|
|
795
|
+
print(f" {desc}")
|
|
796
|
+
else:
|
|
797
|
+
print(f" {name}: port {port}")
|
|
798
|
+
if desc:
|
|
799
|
+
print(f" {desc}")
|
|
800
|
+
print()
|
|
801
|
+
|
|
802
|
+
# Show env vars
|
|
803
|
+
if info.env_vars:
|
|
804
|
+
print("Environment Variables:")
|
|
805
|
+
for name, var_config in info.env_vars.items():
|
|
806
|
+
desc = var_config.get("description", "")
|
|
807
|
+
if "template" in var_config:
|
|
808
|
+
print(f" {name}: (from service URL)")
|
|
809
|
+
elif "default" in var_config:
|
|
810
|
+
print(f" {name}={var_config['default']}")
|
|
811
|
+
else:
|
|
812
|
+
print(f" {name}")
|
|
813
|
+
if desc:
|
|
814
|
+
print(f" {desc}")
|
|
815
|
+
print()
|
|
816
|
+
|
|
817
|
+
# Show instructions
|
|
818
|
+
if info.instructions:
|
|
819
|
+
instructions = info.instructions
|
|
820
|
+
if job_id and info.services:
|
|
821
|
+
# Build service URLs from job_id and replace placeholders
|
|
822
|
+
for svc_name, svc_config in info.services.items():
|
|
823
|
+
port = svc_config.get("port", 80)
|
|
824
|
+
svc_url = f"https://{job_id}--{port}.connect.plato.so"
|
|
825
|
+
instructions = instructions.replace(f"{{service:{svc_name}}}", svc_url)
|
|
826
|
+
|
|
827
|
+
print("Instructions:")
|
|
828
|
+
print(instructions)
|
|
829
|
+
print()
|
|
830
|
+
|
|
831
|
+
# Show usage
|
|
832
|
+
print("Usage:")
|
|
833
|
+
print(f" from plato.sims import {info.name}")
|
|
834
|
+
print()
|
|
835
|
+
print(" # Get service URLs from job ID")
|
|
836
|
+
print(f" service_urls = {info.name}.get_service_urls(job_id)")
|
|
837
|
+
print()
|
|
838
|
+
print(" # Get formatted instructions")
|
|
839
|
+
print(f" instructions = {info.name}.get_instructions(service_urls)")
|
|
840
|
+
print()
|
|
841
|
+
print(" # Get environment variables to set")
|
|
842
|
+
print(f" env_vars = {info.name}.get_env_vars(service_urls)")
|
|
843
|
+
|
|
844
|
+
|
|
771
845
|
def cmd_endpoints(
|
|
772
846
|
sim_name: str,
|
|
773
847
|
spec_name: str | None = None,
|
|
@@ -950,144 +1024,203 @@ def cmd_publish(
|
|
|
950
1024
|
pkg_dir = build_dir / "src" / "plato" / "sims" / service_name
|
|
951
1025
|
pkg_dir.mkdir(parents=True)
|
|
952
1026
|
|
|
953
|
-
# Find OpenAPI spec
|
|
954
1027
|
config_dir = config_file.parent
|
|
955
|
-
spec_file = None
|
|
956
1028
|
|
|
957
|
-
#
|
|
1029
|
+
# Check for instruction-based sim first
|
|
1030
|
+
instructions_file = None
|
|
958
1031
|
if specs_dir:
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
break
|
|
1032
|
+
instructions_path = config_dir / specs_dir / "instructions.yaml"
|
|
1033
|
+
if instructions_path.exists():
|
|
1034
|
+
instructions_file = instructions_path
|
|
1035
|
+
if not instructions_file:
|
|
1036
|
+
instructions_path = config_dir / "instructions.yaml"
|
|
1037
|
+
if instructions_path.exists():
|
|
1038
|
+
instructions_file = instructions_path
|
|
1039
|
+
|
|
1040
|
+
if instructions_file:
|
|
1041
|
+
# Generate instruction-based SDK
|
|
1042
|
+
print(f"Using instructions config: {instructions_file}")
|
|
1043
|
+
print("Generating instruction-based SDK...")
|
|
1044
|
+
try:
|
|
1045
|
+
import plato
|
|
1046
|
+
from plato._sims_generator import InstructionConfig, InstructionGenerator
|
|
975
1047
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
"Error: OpenAPI spec not found. Set 'sdk.specs_dir' or 'sdk.spec_path' in plato-config.yml",
|
|
979
|
-
file=sys.stderr,
|
|
980
|
-
)
|
|
981
|
-
sys.exit(1)
|
|
1048
|
+
generator_version = getattr(plato, "__version__", None)
|
|
1049
|
+
print(f" Generator version: {generator_version}")
|
|
982
1050
|
|
|
983
|
-
|
|
1051
|
+
instruction_config = InstructionConfig.from_yaml(instructions_file)
|
|
1052
|
+
# Override version from plato-config.yml if set
|
|
1053
|
+
instruction_config.version = version
|
|
1054
|
+
|
|
1055
|
+
generator = InstructionGenerator(
|
|
1056
|
+
config=instruction_config,
|
|
1057
|
+
output_path=pkg_dir,
|
|
1058
|
+
package_name=service_name,
|
|
1059
|
+
generator_version=generator_version,
|
|
1060
|
+
)
|
|
1061
|
+
generator.generate()
|
|
1062
|
+
print(f" Generated instruction-based SDK to: {pkg_dir}")
|
|
1063
|
+
|
|
1064
|
+
except ImportError as e:
|
|
1065
|
+
print(f"Error: Missing dependency for SDK generation: {e}", file=sys.stderr)
|
|
1066
|
+
print("Install with: uv add plato-sdk-v2", file=sys.stderr)
|
|
1067
|
+
sys.exit(1)
|
|
1068
|
+
except Exception as e:
|
|
1069
|
+
print(f"Error generating instruction SDK: {e}", file=sys.stderr)
|
|
1070
|
+
sys.exit(1)
|
|
984
1071
|
|
|
985
|
-
# Load auth config if provided
|
|
986
|
-
auth_yaml = None
|
|
987
|
-
if auth_config_path:
|
|
988
|
-
auth_file = config_dir / auth_config_path
|
|
989
|
-
if auth_file.exists():
|
|
990
|
-
auth_yaml = auth_file
|
|
991
1072
|
else:
|
|
992
|
-
#
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
search_dirs.append(config_dir / specs_dir)
|
|
996
|
-
search_dirs.append(config_dir)
|
|
1073
|
+
# Generate OpenAPI-based SDK
|
|
1074
|
+
# Find OpenAPI spec
|
|
1075
|
+
spec_file = None
|
|
997
1076
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1077
|
+
# Priority 1: specs_dir (new format)
|
|
1078
|
+
if specs_dir:
|
|
1079
|
+
specs_path = config_dir / specs_dir
|
|
1080
|
+
for candidate in ["openapi.json", "openapi.yaml", "openapi.yml"]:
|
|
1081
|
+
candidate_path = specs_path / candidate
|
|
1001
1082
|
if candidate_path.exists():
|
|
1002
|
-
|
|
1083
|
+
spec_file = candidate_path
|
|
1084
|
+
break
|
|
1085
|
+
# Priority 2: spec_path (legacy format)
|
|
1086
|
+
elif spec_path:
|
|
1087
|
+
spec_file = config_dir / spec_path
|
|
1088
|
+
# Priority 3: Try common locations in root
|
|
1089
|
+
else:
|
|
1090
|
+
for candidate in ["openapi.yaml", "openapi.yml", "openapi.json", "spec.yaml", "spec.json"]:
|
|
1091
|
+
candidate_path = config_dir / candidate
|
|
1092
|
+
if candidate_path.exists():
|
|
1093
|
+
spec_file = candidate_path
|
|
1003
1094
|
break
|
|
1004
|
-
if auth_yaml:
|
|
1005
|
-
break
|
|
1006
|
-
|
|
1007
|
-
# Generate SDK code
|
|
1008
|
-
print("Generating SDK code...")
|
|
1009
|
-
try:
|
|
1010
|
-
import plato
|
|
1011
|
-
from plato._sims_generator import AuthConfig, PythonGenerator, parse_openapi
|
|
1012
1095
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1096
|
+
if not spec_file or not spec_file.exists():
|
|
1097
|
+
print(
|
|
1098
|
+
"Error: OpenAPI spec or instructions.yaml not found. "
|
|
1099
|
+
"Set 'sdk.specs_dir' or 'sdk.spec_path' in plato-config.yml",
|
|
1100
|
+
file=sys.stderr,
|
|
1101
|
+
)
|
|
1102
|
+
sys.exit(1)
|
|
1016
1103
|
|
|
1017
|
-
|
|
1018
|
-
if specs_dir:
|
|
1019
|
-
generator_version_file = config_dir / specs_dir / ".generator-version"
|
|
1020
|
-
if generator_version_file.exists():
|
|
1021
|
-
expected_version = generator_version_file.read_text().strip()
|
|
1022
|
-
if expected_version and generator_version and expected_version != generator_version:
|
|
1023
|
-
print(
|
|
1024
|
-
f"Error: Generator version mismatch. "
|
|
1025
|
-
f"Expected {expected_version} (from .generator-version), "
|
|
1026
|
-
f"but running {generator_version}",
|
|
1027
|
-
file=sys.stderr,
|
|
1028
|
-
)
|
|
1029
|
-
print(
|
|
1030
|
-
f" Run: uvx --from 'plato-sdk-v2=={expected_version}' plato sims publish",
|
|
1031
|
-
file=sys.stderr,
|
|
1032
|
-
)
|
|
1033
|
-
print(
|
|
1034
|
-
f" Or update .generator-version to {generator_version} to use current version",
|
|
1035
|
-
file=sys.stderr,
|
|
1036
|
-
)
|
|
1037
|
-
sys.exit(1)
|
|
1038
|
-
|
|
1039
|
-
# Write/update .generator-version to specs dir
|
|
1040
|
-
if generator_version:
|
|
1041
|
-
generator_version_file.write_text(f"{generator_version}\n")
|
|
1042
|
-
print(f" Updated {generator_version_file}")
|
|
1043
|
-
|
|
1044
|
-
# Load spec
|
|
1045
|
-
with open(spec_file) as f:
|
|
1046
|
-
if spec_file.suffix == ".json":
|
|
1047
|
-
spec = json.load(f)
|
|
1048
|
-
else:
|
|
1049
|
-
spec = yaml.safe_load(f)
|
|
1104
|
+
print(f"Using OpenAPI spec: {spec_file}")
|
|
1050
1105
|
|
|
1051
|
-
# Load auth config
|
|
1052
|
-
|
|
1053
|
-
|
|
1106
|
+
# Load auth config if provided
|
|
1107
|
+
auth_yaml = None
|
|
1108
|
+
if auth_config_path:
|
|
1109
|
+
auth_file = config_dir / auth_config_path
|
|
1110
|
+
if auth_file.exists():
|
|
1111
|
+
auth_yaml = auth_file
|
|
1054
1112
|
else:
|
|
1055
|
-
#
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
)
|
|
1113
|
+
# Try specs_dir first, then root
|
|
1114
|
+
search_dirs = []
|
|
1115
|
+
if specs_dir:
|
|
1116
|
+
search_dirs.append(config_dir / specs_dir)
|
|
1117
|
+
search_dirs.append(config_dir)
|
|
1118
|
+
|
|
1119
|
+
for search_dir in search_dirs:
|
|
1120
|
+
for candidate in ["auth.yaml", "auth.yml"]:
|
|
1121
|
+
candidate_path = search_dir / candidate
|
|
1122
|
+
if candidate_path.exists():
|
|
1123
|
+
auth_yaml = candidate_path
|
|
1124
|
+
break
|
|
1125
|
+
if auth_yaml:
|
|
1126
|
+
break
|
|
1060
1127
|
|
|
1061
|
-
#
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1128
|
+
# Generate SDK code
|
|
1129
|
+
print("Generating SDK code...")
|
|
1130
|
+
try:
|
|
1131
|
+
import plato
|
|
1132
|
+
from plato._sims_generator import AuthConfig, PythonGenerator, parse_openapi
|
|
1133
|
+
|
|
1134
|
+
# Get current plato-sdk-v2 version
|
|
1135
|
+
generator_version = getattr(plato, "__version__", None)
|
|
1136
|
+
print(f" Generator version: {generator_version}")
|
|
1137
|
+
|
|
1138
|
+
# Check if .generator-version exists and matches
|
|
1139
|
+
if specs_dir:
|
|
1140
|
+
generator_version_file = config_dir / specs_dir / ".generator-version"
|
|
1141
|
+
if generator_version_file.exists():
|
|
1142
|
+
expected_version = generator_version_file.read_text().strip()
|
|
1143
|
+
if expected_version and generator_version and expected_version != generator_version:
|
|
1144
|
+
print(
|
|
1145
|
+
f"Error: Generator version mismatch. "
|
|
1146
|
+
f"Expected {expected_version} (from .generator-version), "
|
|
1147
|
+
f"but running {generator_version}",
|
|
1148
|
+
file=sys.stderr,
|
|
1149
|
+
)
|
|
1150
|
+
print(
|
|
1151
|
+
f" Run: uvx --from 'plato-sdk-v2=={expected_version}' plato sims publish",
|
|
1152
|
+
file=sys.stderr,
|
|
1153
|
+
)
|
|
1154
|
+
print(
|
|
1155
|
+
f" Or update .generator-version to {generator_version} to use current version",
|
|
1156
|
+
file=sys.stderr,
|
|
1157
|
+
)
|
|
1158
|
+
sys.exit(1)
|
|
1159
|
+
|
|
1160
|
+
# Write/update .generator-version to specs dir
|
|
1161
|
+
if generator_version:
|
|
1162
|
+
generator_version_file.write_text(f"{generator_version}\n")
|
|
1163
|
+
print(f" Updated {generator_version_file}")
|
|
1164
|
+
|
|
1165
|
+
# Load spec
|
|
1166
|
+
with open(spec_file) as f:
|
|
1167
|
+
if spec_file.suffix == ".json":
|
|
1168
|
+
spec = json.load(f)
|
|
1169
|
+
else:
|
|
1170
|
+
spec = yaml.safe_load(f)
|
|
1171
|
+
|
|
1172
|
+
# Load auth config
|
|
1173
|
+
if auth_yaml and auth_yaml.exists():
|
|
1174
|
+
auth = AuthConfig.from_yaml(auth_yaml)
|
|
1175
|
+
else:
|
|
1176
|
+
# Default auth config
|
|
1177
|
+
auth = AuthConfig(
|
|
1178
|
+
type="basic",
|
|
1179
|
+
env_prefix=service_name.upper(),
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
# Parse and generate
|
|
1183
|
+
api = parse_openapi(spec)
|
|
1184
|
+
print(f" Parsed {len(api.endpoints)} endpoints")
|
|
1185
|
+
|
|
1186
|
+
generator = PythonGenerator(
|
|
1187
|
+
api=api,
|
|
1188
|
+
output_path=pkg_dir,
|
|
1189
|
+
spec=spec,
|
|
1190
|
+
package_name=service_name,
|
|
1191
|
+
auth_config=auth,
|
|
1192
|
+
generator_version=generator_version,
|
|
1193
|
+
)
|
|
1194
|
+
generator.generate()
|
|
1195
|
+
print(f" Generated to: {pkg_dir}")
|
|
1075
1196
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1197
|
+
except ImportError as e:
|
|
1198
|
+
print(f"Error: Missing dependency for SDK generation: {e}", file=sys.stderr)
|
|
1199
|
+
print("Install with: uv add plato-sdk-v2", file=sys.stderr)
|
|
1200
|
+
sys.exit(1)
|
|
1201
|
+
except Exception as e:
|
|
1202
|
+
print(f"Error generating SDK: {e}", file=sys.stderr)
|
|
1203
|
+
sys.exit(1)
|
|
1083
1204
|
|
|
1084
1205
|
# Create pyproject.toml with namespace package structure
|
|
1206
|
+
# Instruction sims need pyyaml; API sims need httpx and pydantic
|
|
1207
|
+
if instructions_file:
|
|
1208
|
+
dependencies = '["pyyaml>=6.0.0"]'
|
|
1209
|
+
# Include yaml file in package data
|
|
1210
|
+
extra_config = f"""
|
|
1211
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
1212
|
+
"src/plato/sims/{service_name}/instructions.yaml" = "plato/sims/{service_name}/instructions.yaml"
|
|
1213
|
+
"""
|
|
1214
|
+
else:
|
|
1215
|
+
dependencies = '["httpx>=0.25.0", "pydantic>=2.0.0"]'
|
|
1216
|
+
extra_config = ""
|
|
1217
|
+
|
|
1085
1218
|
pyproject_content = f'''[project]
|
|
1086
1219
|
name = "{package_name}"
|
|
1087
1220
|
version = "{version}"
|
|
1088
1221
|
description = "{description}"
|
|
1089
1222
|
requires-python = ">=3.10"
|
|
1090
|
-
dependencies =
|
|
1223
|
+
dependencies = {dependencies}
|
|
1091
1224
|
|
|
1092
1225
|
[build-system]
|
|
1093
1226
|
requires = ["hatchling"]
|
|
@@ -1095,11 +1228,38 @@ build-backend = "hatchling.build"
|
|
|
1095
1228
|
|
|
1096
1229
|
[tool.hatch.build.targets.wheel]
|
|
1097
1230
|
packages = ["src/plato"]
|
|
1098
|
-
'''
|
|
1231
|
+
{extra_config}'''
|
|
1099
1232
|
(build_dir / "pyproject.toml").write_text(pyproject_content)
|
|
1100
1233
|
|
|
1101
1234
|
# Create README
|
|
1102
|
-
|
|
1235
|
+
if instructions_file:
|
|
1236
|
+
readme_content = f"""# {package_name}
|
|
1237
|
+
|
|
1238
|
+
Auto-generated instruction-based SDK for the {service_name} simulator.
|
|
1239
|
+
|
|
1240
|
+
## Installation
|
|
1241
|
+
|
|
1242
|
+
```bash
|
|
1243
|
+
uv add {package_name} --index-url https://plato.so/api/v2/pypi/{repo}/simple/
|
|
1244
|
+
```
|
|
1245
|
+
|
|
1246
|
+
## Usage
|
|
1247
|
+
|
|
1248
|
+
```python
|
|
1249
|
+
from plato.sims.{service_name} import get_instructions, get_service_urls, setup_env
|
|
1250
|
+
|
|
1251
|
+
# Get service URLs from job ID
|
|
1252
|
+
service_urls = get_service_urls(job_id)
|
|
1253
|
+
|
|
1254
|
+
# Get formatted instructions
|
|
1255
|
+
instructions = get_instructions(service_urls)
|
|
1256
|
+
|
|
1257
|
+
# Set up environment variables
|
|
1258
|
+
setup_env(service_urls)
|
|
1259
|
+
```
|
|
1260
|
+
"""
|
|
1261
|
+
else:
|
|
1262
|
+
readme_content = f"""# {package_name}
|
|
1103
1263
|
|
|
1104
1264
|
Auto-generated SDK for the {service_name} simulator.
|
|
1105
1265
|
|
|
@@ -1228,9 +1388,22 @@ def main(args: list[str] | None = None) -> None:
|
|
|
1228
1388
|
elif command == "info":
|
|
1229
1389
|
if len(args) < 2:
|
|
1230
1390
|
print("Error: sim name required", file=sys.stderr)
|
|
1231
|
-
print("Usage: plato sims info <sim_name>", file=sys.stderr)
|
|
1391
|
+
print("Usage: plato sims info <sim_name> [--job-id JOB_ID]", file=sys.stderr)
|
|
1232
1392
|
sys.exit(1)
|
|
1233
|
-
|
|
1393
|
+
|
|
1394
|
+
sim_name = args[1]
|
|
1395
|
+
job_id = None
|
|
1396
|
+
|
|
1397
|
+
# Parse optional --job-id flag
|
|
1398
|
+
i = 2
|
|
1399
|
+
while i < len(args):
|
|
1400
|
+
if args[i] == "--job-id" and i + 1 < len(args):
|
|
1401
|
+
job_id = args[i + 1]
|
|
1402
|
+
i += 2
|
|
1403
|
+
else:
|
|
1404
|
+
i += 1
|
|
1405
|
+
|
|
1406
|
+
cmd_info(sim_name, job_id)
|
|
1234
1407
|
|
|
1235
1408
|
elif command == "endpoints":
|
|
1236
1409
|
if len(args) < 2:
|
plato/sims/registry.py
CHANGED
|
@@ -38,6 +38,11 @@ class SimInfo:
|
|
|
38
38
|
description: str | None
|
|
39
39
|
auth: AuthRequirement | None
|
|
40
40
|
base_url_suffix: str | None # e.g., "/api/v1" for EspoCRM
|
|
41
|
+
# New fields for instruction-based sims
|
|
42
|
+
sim_type: str = "api" # "api" or "instruction"
|
|
43
|
+
services: dict[str, dict[str, Any]] | None = None # {"main": {"port": 4566, "description": "..."}}
|
|
44
|
+
env_vars: dict[str, dict[str, Any]] | None = None # {"AWS_ENDPOINT_URL": {"template": "...", "description": "..."}}
|
|
45
|
+
instructions: str | None = None # Markdown instructions template
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
class SimsRegistry:
|
|
@@ -76,8 +81,9 @@ class SimsRegistry:
|
|
|
76
81
|
# Try to import the module to verify it's a valid sim
|
|
77
82
|
mod = importlib.import_module(modname)
|
|
78
83
|
|
|
79
|
-
# Check if it looks like
|
|
80
|
-
|
|
84
|
+
# Check if it looks like an API sim (has Client or AsyncClient)
|
|
85
|
+
# or an instruction sim (has SERVICES)
|
|
86
|
+
if hasattr(mod, "Client") or hasattr(mod, "AsyncClient") or hasattr(mod, "SERVICES"):
|
|
81
87
|
self._installed_sims[short_name] = mod
|
|
82
88
|
except ImportError:
|
|
83
89
|
continue
|
|
@@ -98,8 +104,10 @@ class SimsRegistry:
|
|
|
98
104
|
# Check local specs dir if provided
|
|
99
105
|
if self.specs_dir and self.specs_dir.exists():
|
|
100
106
|
for d in self.specs_dir.iterdir():
|
|
101
|
-
if d.is_dir()
|
|
102
|
-
|
|
107
|
+
if d.is_dir():
|
|
108
|
+
# Check for API sim (auth.yaml) or instruction sim (instructions.yaml)
|
|
109
|
+
if (d / "auth.yaml").exists() or (d / "instructions.yaml").exists():
|
|
110
|
+
sims.add(d.name)
|
|
103
111
|
|
|
104
112
|
return sorted(sims)
|
|
105
113
|
|
|
@@ -120,6 +128,11 @@ class SimsRegistry:
|
|
|
120
128
|
except Exception:
|
|
121
129
|
version = getattr(mod, "__version__", "unknown")
|
|
122
130
|
|
|
131
|
+
# Check if this is an instruction-based sim
|
|
132
|
+
if hasattr(mod, "SERVICES"):
|
|
133
|
+
return self._get_instruction_sim_info(name, mod, version)
|
|
134
|
+
|
|
135
|
+
# It's an API-based sim
|
|
123
136
|
# Try to load auth config from package
|
|
124
137
|
auth = None
|
|
125
138
|
try:
|
|
@@ -141,12 +154,18 @@ class SimsRegistry:
|
|
|
141
154
|
description=mod.__doc__,
|
|
142
155
|
auth=auth,
|
|
143
156
|
base_url_suffix=base_url_suffix,
|
|
157
|
+
sim_type="api",
|
|
144
158
|
)
|
|
145
159
|
|
|
146
160
|
# Fall back to local specs dir
|
|
147
161
|
if self.specs_dir:
|
|
148
162
|
sim_dir = self.specs_dir / name
|
|
149
163
|
auth_path = sim_dir / "auth.yaml"
|
|
164
|
+
instructions_path = sim_dir / "instructions.yaml"
|
|
165
|
+
|
|
166
|
+
# Check for instruction-based sim first
|
|
167
|
+
if instructions_path.exists():
|
|
168
|
+
return self._load_instruction_sim_from_file(name, instructions_path)
|
|
150
169
|
|
|
151
170
|
if auth_path.exists():
|
|
152
171
|
auth = self._load_auth(auth_path)
|
|
@@ -157,10 +176,65 @@ class SimsRegistry:
|
|
|
157
176
|
description=None,
|
|
158
177
|
auth=auth,
|
|
159
178
|
base_url_suffix=None,
|
|
179
|
+
sim_type="api",
|
|
160
180
|
)
|
|
161
181
|
|
|
162
182
|
raise ValueError(f"Sim '{name}' not found")
|
|
163
183
|
|
|
184
|
+
def _get_instruction_sim_info(self, name: str, mod: Any, version: str) -> SimInfo:
|
|
185
|
+
"""Get SimInfo for an instruction-based sim from its module."""
|
|
186
|
+
import importlib.resources
|
|
187
|
+
|
|
188
|
+
services = getattr(mod, "SERVICES", {})
|
|
189
|
+
env_prefix = getattr(mod, "ENV_PREFIX", "")
|
|
190
|
+
|
|
191
|
+
# Try to load full config from bundled instructions.yaml
|
|
192
|
+
env_vars_config: dict[str, dict[str, Any]] = {}
|
|
193
|
+
instructions_text = ""
|
|
194
|
+
title = name.title()
|
|
195
|
+
description = mod.__doc__
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
config_text = importlib.resources.files(mod.__name__).joinpath("instructions.yaml").read_text()
|
|
199
|
+
config_data = yaml.safe_load(config_text)
|
|
200
|
+
env_vars_config = config_data.get("env_vars", {})
|
|
201
|
+
instructions_text = config_data.get("instructions", "")
|
|
202
|
+
title = config_data.get("title", name.title())
|
|
203
|
+
description = config_data.get("description", mod.__doc__)
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
return SimInfo(
|
|
208
|
+
name=name,
|
|
209
|
+
title=title,
|
|
210
|
+
version=version,
|
|
211
|
+
description=description,
|
|
212
|
+
auth=None, # Instruction sims don't use auth config
|
|
213
|
+
base_url_suffix=None,
|
|
214
|
+
sim_type="instruction",
|
|
215
|
+
services=services,
|
|
216
|
+
env_vars=env_vars_config,
|
|
217
|
+
instructions=instructions_text,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _load_instruction_sim_from_file(self, name: str, instructions_path: Path) -> SimInfo:
|
|
221
|
+
"""Load instruction sim info from instructions.yaml file."""
|
|
222
|
+
with open(instructions_path) as f:
|
|
223
|
+
data = yaml.safe_load(f)
|
|
224
|
+
|
|
225
|
+
return SimInfo(
|
|
226
|
+
name=name,
|
|
227
|
+
title=data.get("title", name.title()),
|
|
228
|
+
version=data.get("version", "unknown"),
|
|
229
|
+
description=data.get("description"),
|
|
230
|
+
auth=None,
|
|
231
|
+
base_url_suffix=None,
|
|
232
|
+
sim_type="instruction",
|
|
233
|
+
services=data.get("services", {}),
|
|
234
|
+
env_vars=data.get("env_vars", {}),
|
|
235
|
+
instructions=data.get("instructions", ""),
|
|
236
|
+
)
|
|
237
|
+
|
|
164
238
|
def _load_auth(self, auth_path: Path) -> AuthRequirement:
|
|
165
239
|
"""Load auth requirements from auth.yaml."""
|
|
166
240
|
with open(auth_path) as f:
|
plato/v1/cli/pm.py
CHANGED
|
@@ -7,14 +7,17 @@ import re
|
|
|
7
7
|
import shutil
|
|
8
8
|
import tempfile
|
|
9
9
|
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
11
12
|
import httpx
|
|
12
13
|
import typer
|
|
14
|
+
from rich.table import Table
|
|
13
15
|
|
|
14
16
|
# UUID pattern for detecting artifact IDs in sim:artifact notation
|
|
15
17
|
UUID_PATTERN = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
pass
|
|
18
21
|
|
|
19
22
|
from plato._generated.api.v1.env import get_simulator_by_name, get_simulators
|
|
20
23
|
from plato._generated.api.v1.organization import get_organization_members
|
|
@@ -390,6 +393,8 @@ def review_base(
|
|
|
390
393
|
|
|
391
394
|
# Launch Playwright browser and login
|
|
392
395
|
console.print("[cyan]Launching browser and logging in...[/cyan]")
|
|
396
|
+
from playwright.async_api import async_playwright
|
|
397
|
+
|
|
393
398
|
playwright = await async_playwright().start()
|
|
394
399
|
browser = await playwright.chromium.launch(headless=False)
|
|
395
400
|
|
|
@@ -755,6 +760,8 @@ def review_data(
|
|
|
755
760
|
|
|
756
761
|
console.print("[cyan]Launching Chrome with EnvGen Recorder extension...[/cyan]")
|
|
757
762
|
|
|
763
|
+
from playwright.async_api import async_playwright
|
|
764
|
+
|
|
758
765
|
playwright = await async_playwright().start()
|
|
759
766
|
|
|
760
767
|
browser = await playwright.chromium.launch_persistent_context(
|
plato/v1/cli/sandbox.py
CHANGED
|
@@ -13,13 +13,16 @@ import tempfile
|
|
|
13
13
|
import time
|
|
14
14
|
from datetime import datetime, timezone
|
|
15
15
|
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
16
17
|
from urllib.parse import quote
|
|
17
18
|
|
|
18
19
|
import typer
|
|
19
20
|
import yaml
|
|
20
|
-
from playwright.async_api import async_playwright
|
|
21
21
|
from rich.logging import RichHandler
|
|
22
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
pass
|
|
25
|
+
|
|
23
26
|
# UUID pattern for detecting artifact IDs in colon notation
|
|
24
27
|
UUID_PATTERN = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE)
|
|
25
28
|
|
|
@@ -1534,6 +1537,8 @@ def sandbox_flow(
|
|
|
1534
1537
|
console.print(f"[cyan]Flow name: {flow_name}[/cyan]")
|
|
1535
1538
|
|
|
1536
1539
|
async def _run():
|
|
1540
|
+
from playwright.async_api import async_playwright
|
|
1541
|
+
|
|
1537
1542
|
browser = None
|
|
1538
1543
|
try:
|
|
1539
1544
|
async with async_playwright() as p:
|