plato-sdk-v2 2.0.64__py3-none-any.whl → 2.3.4__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/__init__.py +0 -9
- plato/_sims_generator/__init__.py +19 -4
- plato/_sims_generator/instruction.py +203 -0
- plato/_sims_generator/templates/instruction/helpers.py.jinja +161 -0
- plato/_sims_generator/templates/instruction/init.py.jinja +43 -0
- plato/agents/__init__.py +99 -430
- plato/agents/base.py +145 -0
- plato/agents/build.py +61 -0
- plato/agents/config.py +160 -0
- plato/agents/logging.py +515 -0
- plato/agents/runner.py +191 -0
- plato/agents/trajectory.py +266 -0
- plato/chronos/models/__init__.py +1 -1
- plato/sims/cli.py +299 -123
- plato/sims/registry.py +77 -4
- plato/v1/cli/agent.py +88 -84
- plato/v1/cli/pm.py +84 -44
- plato/v1/cli/sandbox.py +241 -61
- plato/v1/cli/ssh.py +16 -4
- plato/v1/cli/verify.py +685 -0
- plato/v1/cli/world.py +3 -0
- plato/v1/flow_executor.py +21 -17
- plato/v1/models/env.py +11 -11
- plato/v1/sdk.py +2 -2
- plato/v1/sync_env.py +11 -11
- plato/v1/sync_flow_executor.py +21 -17
- plato/v1/sync_sdk.py +4 -2
- plato/v2/__init__.py +2 -0
- plato/v2/async_/environment.py +31 -0
- plato/v2/async_/session.py +72 -4
- plato/v2/sync/environment.py +31 -0
- plato/v2/sync/session.py +72 -4
- plato/worlds/README.md +71 -56
- plato/worlds/__init__.py +56 -18
- plato/worlds/base.py +578 -93
- plato/worlds/config.py +276 -74
- plato/worlds/runner.py +475 -80
- {plato_sdk_v2-2.0.64.dist-info → plato_sdk_v2-2.3.4.dist-info}/METADATA +3 -3
- {plato_sdk_v2-2.0.64.dist-info → plato_sdk_v2-2.3.4.dist-info}/RECORD +41 -36
- {plato_sdk_v2-2.0.64.dist-info → plato_sdk_v2-2.3.4.dist-info}/entry_points.txt +1 -0
- plato/agents/callback.py +0 -246
- plato/world/__init__.py +0 -44
- plato/world/base.py +0 -267
- plato/world/config.py +0 -139
- plato/world/types.py +0 -47
- {plato_sdk_v2-2.0.64.dist-info → plato_sdk_v2-2.3.4.dist-info}/WHEEL +0 -0
plato/sims/cli.py
CHANGED
|
@@ -241,9 +241,11 @@ def _get_function_info(
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
# Extract parameters
|
|
244
|
+
params_list = result["params"]
|
|
245
|
+
assert isinstance(params_list, list) # type narrowing for ty
|
|
244
246
|
for param_name, param in sig.parameters.items():
|
|
245
247
|
if param_name == "client":
|
|
246
|
-
|
|
248
|
+
params_list.append("client.httpx")
|
|
247
249
|
continue
|
|
248
250
|
|
|
249
251
|
# Get type from hints or annotation
|
|
@@ -251,10 +253,10 @@ def _get_function_info(
|
|
|
251
253
|
type_str = _format_type_annotation(param_type)
|
|
252
254
|
|
|
253
255
|
if param.default is inspect.Parameter.empty:
|
|
254
|
-
|
|
256
|
+
params_list.append(f"{param_name}: {type_str}")
|
|
255
257
|
else:
|
|
256
258
|
default_repr = repr(param.default) if param.default is not None else "None"
|
|
257
|
-
|
|
259
|
+
params_list.append(f"{param_name}: {type_str} = {default_repr}")
|
|
258
260
|
|
|
259
261
|
# Check if this is the body parameter
|
|
260
262
|
if param_name == "body" and param_type is not inspect.Parameter.empty:
|
|
@@ -725,7 +727,7 @@ def _format_response_structure(
|
|
|
725
727
|
return lines
|
|
726
728
|
|
|
727
729
|
|
|
728
|
-
def cmd_info(sim_name: str) -> None:
|
|
730
|
+
def cmd_info(sim_name: str, job_id: str | None = None) -> None:
|
|
729
731
|
"""Show detailed information about a sim."""
|
|
730
732
|
try:
|
|
731
733
|
info = registry.get_sim_info(sim_name)
|
|
@@ -750,6 +752,15 @@ def cmd_info(sim_name: str) -> None:
|
|
|
750
752
|
except ImportError:
|
|
751
753
|
pass
|
|
752
754
|
|
|
755
|
+
# Handle instruction-based sims differently
|
|
756
|
+
if info.sim_type == "instruction":
|
|
757
|
+
_cmd_info_instruction(info, job_id)
|
|
758
|
+
else:
|
|
759
|
+
_cmd_info_api(info)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _cmd_info_api(info) -> None:
|
|
763
|
+
"""Show info for API-based sim."""
|
|
753
764
|
if info.auth:
|
|
754
765
|
print(f"Auth Type: {info.auth.type}")
|
|
755
766
|
|
|
@@ -768,6 +779,71 @@ def cmd_info(sim_name: str) -> None:
|
|
|
768
779
|
print(f" client = await {info.name}.AsyncClient.create({base_url_example})")
|
|
769
780
|
|
|
770
781
|
|
|
782
|
+
def _cmd_info_instruction(info, job_id: str | None) -> None:
|
|
783
|
+
"""Show info for instruction-based sim."""
|
|
784
|
+
print("Type: Instruction-based (no API client)")
|
|
785
|
+
print()
|
|
786
|
+
|
|
787
|
+
# Show available services
|
|
788
|
+
if info.services:
|
|
789
|
+
print("Services:")
|
|
790
|
+
for name, svc in info.services.items():
|
|
791
|
+
port = svc.get("port", "?")
|
|
792
|
+
desc = svc.get("description", "")
|
|
793
|
+
if job_id:
|
|
794
|
+
url = f"https://{job_id}--{port}.connect.plato.so"
|
|
795
|
+
print(f" {name}: {url}")
|
|
796
|
+
if desc:
|
|
797
|
+
print(f" {desc}")
|
|
798
|
+
else:
|
|
799
|
+
print(f" {name}: port {port}")
|
|
800
|
+
if desc:
|
|
801
|
+
print(f" {desc}")
|
|
802
|
+
print()
|
|
803
|
+
|
|
804
|
+
# Show env vars
|
|
805
|
+
if info.env_vars:
|
|
806
|
+
print("Environment Variables:")
|
|
807
|
+
for name, var_config in info.env_vars.items():
|
|
808
|
+
desc = var_config.get("description", "")
|
|
809
|
+
if "template" in var_config:
|
|
810
|
+
print(f" {name}: (from service URL)")
|
|
811
|
+
elif "default" in var_config:
|
|
812
|
+
print(f" {name}={var_config['default']}")
|
|
813
|
+
else:
|
|
814
|
+
print(f" {name}")
|
|
815
|
+
if desc:
|
|
816
|
+
print(f" {desc}")
|
|
817
|
+
print()
|
|
818
|
+
|
|
819
|
+
# Show instructions
|
|
820
|
+
if info.instructions:
|
|
821
|
+
instructions = info.instructions
|
|
822
|
+
if job_id and info.services:
|
|
823
|
+
# Build service URLs from job_id and replace placeholders
|
|
824
|
+
for svc_name, svc_config in info.services.items():
|
|
825
|
+
port = svc_config.get("port", 80)
|
|
826
|
+
svc_url = f"https://{job_id}--{port}.connect.plato.so"
|
|
827
|
+
instructions = instructions.replace(f"{{service:{svc_name}}}", svc_url)
|
|
828
|
+
|
|
829
|
+
print("Instructions:")
|
|
830
|
+
print(instructions)
|
|
831
|
+
print()
|
|
832
|
+
|
|
833
|
+
# Show usage
|
|
834
|
+
print("Usage:")
|
|
835
|
+
print(f" from plato.sims import {info.name}")
|
|
836
|
+
print()
|
|
837
|
+
print(" # Get service URLs from job ID")
|
|
838
|
+
print(f" service_urls = {info.name}.get_service_urls(job_id)")
|
|
839
|
+
print()
|
|
840
|
+
print(" # Get formatted instructions")
|
|
841
|
+
print(f" instructions = {info.name}.get_instructions(service_urls)")
|
|
842
|
+
print()
|
|
843
|
+
print(" # Get environment variables to set")
|
|
844
|
+
print(f" env_vars = {info.name}.get_env_vars(service_urls)")
|
|
845
|
+
|
|
846
|
+
|
|
771
847
|
def cmd_endpoints(
|
|
772
848
|
sim_name: str,
|
|
773
849
|
spec_name: str | None = None,
|
|
@@ -950,144 +1026,204 @@ def cmd_publish(
|
|
|
950
1026
|
pkg_dir = build_dir / "src" / "plato" / "sims" / service_name
|
|
951
1027
|
pkg_dir.mkdir(parents=True)
|
|
952
1028
|
|
|
953
|
-
# Find OpenAPI spec
|
|
954
1029
|
config_dir = config_file.parent
|
|
955
|
-
spec_file = None
|
|
956
1030
|
|
|
957
|
-
#
|
|
1031
|
+
# Check for instruction-based sim first
|
|
1032
|
+
instructions_file = None
|
|
958
1033
|
if specs_dir:
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
break
|
|
1034
|
+
instructions_path = config_dir / specs_dir / "instructions.yaml"
|
|
1035
|
+
if instructions_path.exists():
|
|
1036
|
+
instructions_file = instructions_path
|
|
1037
|
+
if not instructions_file:
|
|
1038
|
+
instructions_path = config_dir / "instructions.yaml"
|
|
1039
|
+
if instructions_path.exists():
|
|
1040
|
+
instructions_file = instructions_path
|
|
1041
|
+
|
|
1042
|
+
if instructions_file:
|
|
1043
|
+
# Generate instruction-based SDK
|
|
1044
|
+
print(f"Using instructions config: {instructions_file}")
|
|
1045
|
+
print("Generating instruction-based SDK...")
|
|
1046
|
+
try:
|
|
1047
|
+
import plato
|
|
1048
|
+
from plato._sims_generator import InstructionConfig, InstructionGenerator
|
|
975
1049
|
|
|
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)
|
|
1050
|
+
generator_version = getattr(plato, "__version__", None)
|
|
1051
|
+
print(f" Generator version: {generator_version}")
|
|
982
1052
|
|
|
983
|
-
|
|
1053
|
+
instruction_config = InstructionConfig.from_yaml(instructions_file)
|
|
1054
|
+
# Override version from plato-config.yml if set
|
|
1055
|
+
instruction_config.version = version
|
|
1056
|
+
|
|
1057
|
+
generator = InstructionGenerator(
|
|
1058
|
+
config=instruction_config,
|
|
1059
|
+
output_path=pkg_dir,
|
|
1060
|
+
package_name=service_name,
|
|
1061
|
+
generator_version=generator_version,
|
|
1062
|
+
)
|
|
1063
|
+
generator.generate()
|
|
1064
|
+
print(f" Generated instruction-based SDK to: {pkg_dir}")
|
|
1065
|
+
|
|
1066
|
+
except ImportError as e:
|
|
1067
|
+
print(f"Error: Missing dependency for SDK generation: {e}", file=sys.stderr)
|
|
1068
|
+
print("Install with: uv add plato-sdk-v2", file=sys.stderr)
|
|
1069
|
+
sys.exit(1)
|
|
1070
|
+
except Exception as e:
|
|
1071
|
+
print(f"Error generating instruction SDK: {e}", file=sys.stderr)
|
|
1072
|
+
sys.exit(1)
|
|
984
1073
|
|
|
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
1074
|
else:
|
|
992
|
-
#
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
search_dirs.append(config_dir / specs_dir)
|
|
996
|
-
search_dirs.append(config_dir)
|
|
1075
|
+
# Generate OpenAPI-based SDK
|
|
1076
|
+
# Find OpenAPI spec
|
|
1077
|
+
spec_file = None
|
|
997
1078
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1079
|
+
# Priority 1: specs_dir (new format)
|
|
1080
|
+
if specs_dir:
|
|
1081
|
+
specs_path = config_dir / specs_dir
|
|
1082
|
+
for candidate in ["openapi.json", "openapi.yaml", "openapi.yml"]:
|
|
1083
|
+
candidate_path = specs_path / candidate
|
|
1001
1084
|
if candidate_path.exists():
|
|
1002
|
-
|
|
1085
|
+
spec_file = candidate_path
|
|
1086
|
+
break
|
|
1087
|
+
# Priority 2: spec_path (legacy format)
|
|
1088
|
+
elif spec_path:
|
|
1089
|
+
spec_file = config_dir / spec_path
|
|
1090
|
+
# Priority 3: Try common locations in root
|
|
1091
|
+
else:
|
|
1092
|
+
for candidate in ["openapi.yaml", "openapi.yml", "openapi.json", "spec.yaml", "spec.json"]:
|
|
1093
|
+
candidate_path = config_dir / candidate
|
|
1094
|
+
if candidate_path.exists():
|
|
1095
|
+
spec_file = candidate_path
|
|
1003
1096
|
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
1097
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1098
|
+
if not spec_file or not spec_file.exists():
|
|
1099
|
+
print(
|
|
1100
|
+
"Error: OpenAPI spec or instructions.yaml not found. "
|
|
1101
|
+
"Set 'sdk.specs_dir' or 'sdk.spec_path' in plato-config.yml",
|
|
1102
|
+
file=sys.stderr,
|
|
1103
|
+
)
|
|
1104
|
+
sys.exit(1)
|
|
1016
1105
|
|
|
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)
|
|
1106
|
+
print(f"Using OpenAPI spec: {spec_file}")
|
|
1050
1107
|
|
|
1051
|
-
# Load auth config
|
|
1052
|
-
|
|
1053
|
-
|
|
1108
|
+
# Load auth config if provided
|
|
1109
|
+
auth_yaml = None
|
|
1110
|
+
if auth_config_path:
|
|
1111
|
+
auth_file = config_dir / auth_config_path
|
|
1112
|
+
if auth_file.exists():
|
|
1113
|
+
auth_yaml = auth_file
|
|
1054
1114
|
else:
|
|
1055
|
-
#
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
)
|
|
1115
|
+
# Try specs_dir first, then root
|
|
1116
|
+
search_dirs = []
|
|
1117
|
+
if specs_dir:
|
|
1118
|
+
search_dirs.append(config_dir / specs_dir)
|
|
1119
|
+
search_dirs.append(config_dir)
|
|
1120
|
+
|
|
1121
|
+
for search_dir in search_dirs:
|
|
1122
|
+
for candidate in ["auth.yaml", "auth.yml"]:
|
|
1123
|
+
candidate_path = search_dir / candidate
|
|
1124
|
+
if candidate_path.exists():
|
|
1125
|
+
auth_yaml = candidate_path
|
|
1126
|
+
break
|
|
1127
|
+
if auth_yaml:
|
|
1128
|
+
break
|
|
1060
1129
|
|
|
1061
|
-
#
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1130
|
+
# Generate SDK code
|
|
1131
|
+
print("Generating SDK code...")
|
|
1132
|
+
try:
|
|
1133
|
+
import plato
|
|
1134
|
+
from plato._sims_generator import AuthConfig, PythonGenerator, parse_openapi
|
|
1135
|
+
|
|
1136
|
+
# Get current plato-sdk-v2 version
|
|
1137
|
+
generator_version = getattr(plato, "__version__", None)
|
|
1138
|
+
print(f" Generator version: {generator_version}")
|
|
1139
|
+
|
|
1140
|
+
# Check if .generator-version exists and matches
|
|
1141
|
+
if specs_dir:
|
|
1142
|
+
generator_version_file = config_dir / specs_dir / ".generator-version"
|
|
1143
|
+
if generator_version_file.exists():
|
|
1144
|
+
expected_version = generator_version_file.read_text().strip()
|
|
1145
|
+
if expected_version and generator_version and expected_version != generator_version:
|
|
1146
|
+
print(
|
|
1147
|
+
f"Error: Generator version mismatch. "
|
|
1148
|
+
f"Expected {expected_version} (from .generator-version), "
|
|
1149
|
+
f"but running {generator_version}",
|
|
1150
|
+
file=sys.stderr,
|
|
1151
|
+
)
|
|
1152
|
+
print(
|
|
1153
|
+
f" Run: uvx --from 'plato-sdk-v2=={expected_version}' plato sims publish",
|
|
1154
|
+
file=sys.stderr,
|
|
1155
|
+
)
|
|
1156
|
+
print(
|
|
1157
|
+
f" Or update .generator-version to {generator_version} to use current version",
|
|
1158
|
+
file=sys.stderr,
|
|
1159
|
+
)
|
|
1160
|
+
sys.exit(1)
|
|
1161
|
+
|
|
1162
|
+
# Write/update .generator-version to specs dir
|
|
1163
|
+
if generator_version:
|
|
1164
|
+
generator_version_file.write_text(f"{generator_version}\n")
|
|
1165
|
+
print(f" Updated {generator_version_file}")
|
|
1166
|
+
|
|
1167
|
+
# Load spec (spec_file is guaranteed non-None after check above)
|
|
1168
|
+
assert spec_file is not None
|
|
1169
|
+
with open(spec_file) as f:
|
|
1170
|
+
if spec_file.suffix == ".json":
|
|
1171
|
+
spec = json.load(f)
|
|
1172
|
+
else:
|
|
1173
|
+
spec = yaml.safe_load(f)
|
|
1174
|
+
|
|
1175
|
+
# Load auth config
|
|
1176
|
+
if auth_yaml and auth_yaml.exists():
|
|
1177
|
+
auth = AuthConfig.from_yaml(auth_yaml)
|
|
1178
|
+
else:
|
|
1179
|
+
# Default auth config
|
|
1180
|
+
auth = AuthConfig(
|
|
1181
|
+
type="basic",
|
|
1182
|
+
env_prefix=service_name.upper(),
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
# Parse and generate
|
|
1186
|
+
api = parse_openapi(spec)
|
|
1187
|
+
print(f" Parsed {len(api.endpoints)} endpoints")
|
|
1188
|
+
|
|
1189
|
+
generator = PythonGenerator(
|
|
1190
|
+
api=api,
|
|
1191
|
+
output_path=pkg_dir,
|
|
1192
|
+
spec=spec,
|
|
1193
|
+
package_name=service_name,
|
|
1194
|
+
auth_config=auth,
|
|
1195
|
+
generator_version=generator_version,
|
|
1196
|
+
)
|
|
1197
|
+
generator.generate()
|
|
1198
|
+
print(f" Generated to: {pkg_dir}")
|
|
1075
1199
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1200
|
+
except ImportError as e:
|
|
1201
|
+
print(f"Error: Missing dependency for SDK generation: {e}", file=sys.stderr)
|
|
1202
|
+
print("Install with: uv add plato-sdk-v2", file=sys.stderr)
|
|
1203
|
+
sys.exit(1)
|
|
1204
|
+
except Exception as e:
|
|
1205
|
+
print(f"Error generating SDK: {e}", file=sys.stderr)
|
|
1206
|
+
sys.exit(1)
|
|
1083
1207
|
|
|
1084
1208
|
# Create pyproject.toml with namespace package structure
|
|
1209
|
+
# Instruction sims need pyyaml; API sims need httpx and pydantic
|
|
1210
|
+
if instructions_file:
|
|
1211
|
+
dependencies = '["pyyaml>=6.0.0"]'
|
|
1212
|
+
# Include yaml file in package data
|
|
1213
|
+
extra_config = f"""
|
|
1214
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
1215
|
+
"src/plato/sims/{service_name}/instructions.yaml" = "plato/sims/{service_name}/instructions.yaml"
|
|
1216
|
+
"""
|
|
1217
|
+
else:
|
|
1218
|
+
dependencies = '["httpx>=0.25.0", "pydantic>=2.0.0"]'
|
|
1219
|
+
extra_config = ""
|
|
1220
|
+
|
|
1085
1221
|
pyproject_content = f'''[project]
|
|
1086
1222
|
name = "{package_name}"
|
|
1087
1223
|
version = "{version}"
|
|
1088
1224
|
description = "{description}"
|
|
1089
1225
|
requires-python = ">=3.10"
|
|
1090
|
-
dependencies =
|
|
1226
|
+
dependencies = {dependencies}
|
|
1091
1227
|
|
|
1092
1228
|
[build-system]
|
|
1093
1229
|
requires = ["hatchling"]
|
|
@@ -1095,11 +1231,38 @@ build-backend = "hatchling.build"
|
|
|
1095
1231
|
|
|
1096
1232
|
[tool.hatch.build.targets.wheel]
|
|
1097
1233
|
packages = ["src/plato"]
|
|
1098
|
-
'''
|
|
1234
|
+
{extra_config}'''
|
|
1099
1235
|
(build_dir / "pyproject.toml").write_text(pyproject_content)
|
|
1100
1236
|
|
|
1101
1237
|
# Create README
|
|
1102
|
-
|
|
1238
|
+
if instructions_file:
|
|
1239
|
+
readme_content = f"""# {package_name}
|
|
1240
|
+
|
|
1241
|
+
Auto-generated instruction-based SDK for the {service_name} simulator.
|
|
1242
|
+
|
|
1243
|
+
## Installation
|
|
1244
|
+
|
|
1245
|
+
```bash
|
|
1246
|
+
uv add {package_name} --index-url https://plato.so/api/v2/pypi/{repo}/simple/
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
## Usage
|
|
1250
|
+
|
|
1251
|
+
```python
|
|
1252
|
+
from plato.sims.{service_name} import get_instructions, get_service_urls, setup_env
|
|
1253
|
+
|
|
1254
|
+
# Get service URLs from job ID
|
|
1255
|
+
service_urls = get_service_urls(job_id)
|
|
1256
|
+
|
|
1257
|
+
# Get formatted instructions
|
|
1258
|
+
instructions = get_instructions(service_urls)
|
|
1259
|
+
|
|
1260
|
+
# Set up environment variables
|
|
1261
|
+
setup_env(service_urls)
|
|
1262
|
+
```
|
|
1263
|
+
"""
|
|
1264
|
+
else:
|
|
1265
|
+
readme_content = f"""# {package_name}
|
|
1103
1266
|
|
|
1104
1267
|
Auto-generated SDK for the {service_name} simulator.
|
|
1105
1268
|
|
|
@@ -1228,9 +1391,22 @@ def main(args: list[str] | None = None) -> None:
|
|
|
1228
1391
|
elif command == "info":
|
|
1229
1392
|
if len(args) < 2:
|
|
1230
1393
|
print("Error: sim name required", file=sys.stderr)
|
|
1231
|
-
print("Usage: plato sims info <sim_name>", file=sys.stderr)
|
|
1394
|
+
print("Usage: plato sims info <sim_name> [--job-id JOB_ID]", file=sys.stderr)
|
|
1232
1395
|
sys.exit(1)
|
|
1233
|
-
|
|
1396
|
+
|
|
1397
|
+
sim_name = args[1]
|
|
1398
|
+
job_id = None
|
|
1399
|
+
|
|
1400
|
+
# Parse optional --job-id flag
|
|
1401
|
+
i = 2
|
|
1402
|
+
while i < len(args):
|
|
1403
|
+
if args[i] == "--job-id" and i + 1 < len(args):
|
|
1404
|
+
job_id = args[i + 1]
|
|
1405
|
+
i += 2
|
|
1406
|
+
else:
|
|
1407
|
+
i += 1
|
|
1408
|
+
|
|
1409
|
+
cmd_info(sim_name, job_id)
|
|
1234
1410
|
|
|
1235
1411
|
elif command == "endpoints":
|
|
1236
1412
|
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,64 @@ 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
|
+
|
|
190
|
+
# Try to load full config from bundled instructions.yaml
|
|
191
|
+
env_vars_config: dict[str, dict[str, Any]] = {}
|
|
192
|
+
instructions_text = ""
|
|
193
|
+
title = name.title()
|
|
194
|
+
description = mod.__doc__
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
config_text = importlib.resources.files(mod.__name__).joinpath("instructions.yaml").read_text()
|
|
198
|
+
config_data = yaml.safe_load(config_text)
|
|
199
|
+
env_vars_config = config_data.get("env_vars", {})
|
|
200
|
+
instructions_text = config_data.get("instructions", "")
|
|
201
|
+
title = config_data.get("title", name.title())
|
|
202
|
+
description = config_data.get("description", mod.__doc__)
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
return SimInfo(
|
|
207
|
+
name=name,
|
|
208
|
+
title=title,
|
|
209
|
+
version=version,
|
|
210
|
+
description=description,
|
|
211
|
+
auth=None, # Instruction sims don't use auth config
|
|
212
|
+
base_url_suffix=None,
|
|
213
|
+
sim_type="instruction",
|
|
214
|
+
services=services,
|
|
215
|
+
env_vars=env_vars_config,
|
|
216
|
+
instructions=instructions_text,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def _load_instruction_sim_from_file(self, name: str, instructions_path: Path) -> SimInfo:
|
|
220
|
+
"""Load instruction sim info from instructions.yaml file."""
|
|
221
|
+
with open(instructions_path) as f:
|
|
222
|
+
data = yaml.safe_load(f)
|
|
223
|
+
|
|
224
|
+
return SimInfo(
|
|
225
|
+
name=name,
|
|
226
|
+
title=data.get("title", name.title()),
|
|
227
|
+
version=data.get("version", "unknown"),
|
|
228
|
+
description=data.get("description"),
|
|
229
|
+
auth=None,
|
|
230
|
+
base_url_suffix=None,
|
|
231
|
+
sim_type="instruction",
|
|
232
|
+
services=data.get("services", {}),
|
|
233
|
+
env_vars=data.get("env_vars", {}),
|
|
234
|
+
instructions=data.get("instructions", ""),
|
|
235
|
+
)
|
|
236
|
+
|
|
164
237
|
def _load_auth(self, auth_path: Path) -> AuthRequirement:
|
|
165
238
|
"""Load auth requirements from auth.yaml."""
|
|
166
239
|
with open(auth_path) as f:
|