Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions src/poly/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,43 @@ def _create_parser(cls) -> ArgumentParser:
)
init_parser.add_argument("--debug", action="store_true", help="Display debug logs.")

# CREATE-PROJECT
create_project_parser = subparsers.add_parser(
"create-project",
parents=[verbose_parent, json_parent],
help="Create a new Agent Studio project under an account.",
description=(
"Create a new Agent Studio project under an interactively selected account.\n\n"
"Examples:\n"
" poly create-project\n"
" poly create-project --region us-1 --account_id my-account --name my-project\n"
),
formatter_class=RawTextHelpFormatter,
)
create_project_parser.add_argument(
"--base-path",
type=str,
default=os.getcwd(),
help="Base path to initialize the project. Defaults to current working directory.",
)
create_project_parser.add_argument(
"--region",
type=str,
choices=REGIONS,
help="Region for the Agent Studio project.",
)
create_project_parser.add_argument(
"--account_id",
type=str,
help="Account ID for the Agent Studio project.",
)
create_project_parser.add_argument(
"--name",
type=str,
dest="project_name",
help="Name for the new project.",
)

# PULL
pull_parser = subparsers.add_parser(
"pull",
Expand Down Expand Up @@ -673,6 +710,15 @@ def _run_command(cls, args):
output_json_projection=args.output_json_projection,
)

elif args.command == "create-project":
cls.create_project(
args.base_path,
region=args.region,
account_id=args.account_id,
project_name=args.project_name,
output_json=args.json,
)

elif args.command == "pull":
cls.pull(
args.path,
Expand Down Expand Up @@ -899,6 +945,90 @@ def read_project_config(cls, base_path: str) -> AgentStudioProject:
# Recurse into parent directory
return cls.read_project_config(parent_path)

@classmethod
def create_project(
cls,
base_path: str,
region: str = None,
account_id: str = None,
project_name: str = None,
output_json: bool = False,
) -> None:
"""Create a new Agent Studio project under an interactively selected account."""
if output_json and not (region and account_id and project_name):
json_print(
{
"success": False,
"error": (
"create-project with --json requires --region, --account_id, and --name."
),
}
)
sys.exit(1)

if not region:
region = questionary.select("Select Region", choices=REGIONS).ask()
if not region:
warning("No region selected. Exiting.")
return

api_handler = AgentStudioInterface()

if not account_id:
accounts = api_handler.get_accounts(region)
if not accounts:
error("No accounts found for this region.")
return
account_menu = questionary.select(
"Select Account",
choices=list(accounts.keys()),
use_search_filter=True,
use_jk_keys=False,
).ask()
if not account_menu:
warning("No account selected. Exiting.")
return
account_id = accounts[account_menu]

if not project_name:
project_name = questionary.text("Enter project name:").ask()
if not project_name or not project_name.strip():
warning("No project name provided. Exiting.")
return
project_name = project_name.strip()

if not output_json:
info(f"Creating project [bold]{project_name}[/bold] under account {account_id}...")

try:
result = api_handler.create_project(region, account_id, project_name)
except Exception as e:
if output_json:
json_print({"success": False, "error": str(e)})
else:
error(f"Failed to create project: {e}")
return

project_id = result.get("id")
if not project_id:
if output_json:
json_print({"success": False, "error": "No project ID returned by API."})
else:
error("No project ID returned by API.")
return

if not output_json:
success(f"Created project [bold]{project_name}[/bold] ({project_id})")
info("Initializing project locally...")

cls.init_project(
base_path,
region=region,
account_id=account_id,
project_id=project_id,
output_json=output_json,
)

@classmethod
def init_project(
cls,
Expand Down
20 changes: 20 additions & 0 deletions src/poly/handlers/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ def get_projects(region: str, account_id: str) -> dict[str, str]:
"""
return PlatformAPIHandler.get_projects(region, account_id)

@staticmethod
def create_project(
region: str,
account_id: str,
project_name: str,
project_id: str = None,
) -> dict[str, str]:
"""Create a new project in an account.

Args:
region (str): The region name
account_id (str): The account ID
project_name (str): The display name for the new project
project_id (str | None): Optional slug/ID for the project

Returns:
dict[str, str]: A dictionary with the created project's 'id' and 'name'
"""
return PlatformAPIHandler.create_project(region, account_id, project_name, project_id)

@staticmethod
def get_deployments(region: str, account_id: str, project_id: str) -> dict[str, str]:
"""Get the deployments for a given project.
Expand Down
89 changes: 89 additions & 0 deletions src/poly/handlers/platform_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ class PlatformAPIHandler:
"us-1": "https://api.us.poly.ai/adk/v1",
}

region_to_sourcerer_url = {
"dev": "https://sourcerer.dev.platform.polyai.app/api/v1",
"staging": "https://sourcerer.staging.platform.polyai.app/api/v1",
"euw-1": "https://sourcerer.euw-1.platform.polyai.app/api/v1",
"uk-1": "https://sourcerer.uk-1.platform.polyai.app/api/v1",
"us-1": "https://sourcerer.us-1.platform.polyai.app/api/v1",
}

@staticmethod
def get_base_url(region: str) -> str:
"""Get the base URL for the Platform API based on the region.
Expand All @@ -50,6 +58,19 @@ def get_base_url(region: str) -> str:
return base_url
raise ValueError(f"Unknown region: {region}")

@staticmethod
def get_sourcerer_url(region: str) -> str:
"""Get the Sourcerer API base URL for the given region.

Args:
region (str): The region name
Returns:
str: The Sourcerer API base URL
"""
if base_url := PlatformAPIHandler.region_to_sourcerer_url.get(region):
return base_url
raise ValueError(f"Unknown region: {region}")

@staticmethod
def _retrieve_api_key() -> str:
"""Get API key from environment"""
Expand Down Expand Up @@ -168,6 +189,74 @@ def get_projects(region: str, account_id: str) -> dict[str, str]:

return projects

@staticmethod
def create_project(
region: str,
account_id: str,
project_name: str,
project_id: str = None,
) -> dict[str, str]:
"""Create a new project in an account via the Sourcerer API.

Args:
region (str): The region name
account_id (str): The account ID
project_name (str): The display name for the new project
project_id (str | None): Optional slug/ID for the project.
Defaults to a slugified version of the project name.

Returns:
dict[str, str]: A dictionary with the created project's 'id' and 'name'
"""
if not project_id:
project_id = project_name.lower().replace(" ", "-")

endpoint = PROJECTS_URL.format(account_id=account_id)
url = PlatformAPIHandler.get_sourcerer_url(region) + endpoint
data = {
"name": project_name,
"project_id": project_id,
"config": {
"voice_id": "VOICE-afe2b8e8",
"model_id": "MODEL-27a9c7af",
"config": {"language_code": "en-US"},
},
"topic_names": [],
"knowledge_base": {
"welcome_message": "Hello, how can I help you?",
"additional_context": {},
"knowledge_base": {"rules": {"behaviour": ""}},
},
}

correlation_id = f"adk-{uuid.uuid4()}"
headers = {
"X-API-KEY": PlatformAPIHandler._retrieve_api_key(),
"X-PolyAI-Correlation-Id": correlation_id,
"Content-Type": "application/json",
}

logger.info(f"Creating project at {url}")
response = requests.request(
method="POST",
url=url,
headers=headers,
allow_redirects=False,
data=json.dumps(data),
)

try:
response.raise_for_status()
except requests.HTTPError:
logger.debug(
f"Error creating project. url={url!r} status_code={response.status_code!r}"
f" response={response.text!r}"
)
raise

result = response.json()
return {"id": result.get("id"), "name": result.get("name")}

@staticmethod
def get_deployments(region: str, account_id: str, project_id: str) -> dict[str, str]:
"""Get the deployments for a given project.
Expand Down
Loading
Loading