├── src └── nova_act │ ├── py.typed │ ├── cli │ ├── __init__.py │ ├── workflow │ │ ├── __init__.py │ │ ├── commands │ │ │ ├── __init__.py │ │ │ ├── list.py │ │ │ ├── delete.py │ │ │ ├── show.py │ │ │ └── update.py │ │ ├── services │ │ │ ├── agentcore │ │ │ │ ├── templates │ │ │ │ │ ├── requirements.txt │ │ │ │ │ ├── Dockerfile │ │ │ │ │ └── agentcore_handler.py │ │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ └── constants.py │ │ └── utils │ │ │ ├── __init__.py │ │ │ ├── tags.py │ │ │ ├── console.py │ │ │ ├── bucket_manager.py │ │ │ └── arn.py │ ├── __version__.py │ ├── core │ │ ├── __init__.py │ │ ├── clients │ │ │ ├── ecr │ │ │ │ ├── __init__.py │ │ │ │ └── constants.py │ │ │ ├── s3 │ │ │ │ └── __init__.py │ │ │ ├── nova_act │ │ │ │ ├── __init__.py │ │ │ │ ├── constants.py │ │ │ │ ├── types.py │ │ │ │ └── client.py │ │ │ ├── __init__.py │ │ │ ├── agentcore │ │ │ │ ├── __init__.py │ │ │ │ ├── constants.py │ │ │ │ ├── types.py │ │ │ │ └── response_parser.py │ │ │ └── iam │ │ │ │ ├── __init__.py │ │ │ │ ├── types.py │ │ │ │ └── client.py │ │ ├── region.py │ │ ├── constants.py │ │ ├── logging.py │ │ ├── exceptions.py │ │ ├── config.py │ │ ├── identity.py │ │ ├── types.py │ │ ├── user_config_manager.py │ │ └── styling.py │ └── cli.py │ ├── impl │ ├── __init__.py │ ├── program │ │ ├── __init__.py │ │ └── base.py │ ├── backends │ │ ├── starburst │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── factory.py │ │ └── common.py │ ├── extension.py │ ├── controller.py │ ├── keyboard_event_watcher.py │ └── telemetry.py │ ├── samples │ ├── __init__.py │ ├── setup_chrome_user_data_dir.py │ ├── booking_with_data_from_tool.py │ ├── s3_writer_example.py │ └── print_number_of_emails.py │ ├── tools │ ├── human │ │ ├── __init__.py │ │ ├── default │ │ │ └── __init__.py │ │ └── interface │ │ │ └── __init__.py │ ├── browser │ │ ├── interface │ │ │ ├── __init__.py │ │ │ ├── types │ │ │ │ ├── dimensions_dict.py │ │ │ │ ├── __init__.py │ │ │ │ ├── click_types.py │ │ │ │ ├── scroll_types.py │ │ │ │ ├── element_dict.py │ │ │ │ ├── click_options.py │ │ │ │ └── agent_redirect_error.py │ │ │ └── playwright_pages.py │ │ └── default │ │ │ ├── dom_actuation │ │ │ ├── dispatch_events_dict.py │ │ │ ├── type_events.py │ │ │ ├── scroll_events.py │ │ │ ├── click_events.py │ │ │ └── create_dom_events.py │ │ │ ├── util │ │ │ ├── go_to_url.py │ │ │ ├── agent_hover.py │ │ │ ├── dispatch_dom_events.py │ │ │ ├── bbox_parser.py │ │ │ ├── file_upload_helpers.py │ │ │ ├── get_bbox_values.py │ │ │ └── take_observation.py │ │ │ └── playwright_instance_options.py │ └── actuator │ │ └── interface │ │ └── actuator.py │ ├── types │ ├── __init__.py │ ├── state │ │ ├── __init__.py │ │ ├── page.py │ │ └── step.py │ ├── json_type.py │ ├── api │ │ ├── status.py │ │ └── trace.py │ ├── workflow_run.py │ ├── guardrail.py │ ├── hooks.py │ ├── events.py │ ├── act_result.py │ ├── features.py │ ├── errors.py │ └── act_metadata.py │ ├── __version__.py │ ├── util │ ├── constants.py │ ├── common_js_expressions.py │ ├── human_wait_time_tracker.py │ ├── jsonschema.py │ ├── event_handler.py │ ├── step_server_time_tracker.py │ ├── s3_writer_errors.py │ ├── decode_string.py │ ├── terminal_manager.py │ └── error_messages.py │ └── __init__.py ├── requirements-cli.txt ├── requirements-dev.txt ├── NOTICE ├── CODE_OF_CONDUCT.md ├── requirements.txt ├── pyproject.toml └── CONTRIBUTING.md /src/nova_act/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/cli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/impl/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/samples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/impl/program/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/tools/human/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-cli.txt: -------------------------------------------------------------------------------- 1 | click 2 | pyyaml 3 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/impl/backends/starburst/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/tools/human/default/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/nova_act/tools/human/interface/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | build 2 | setuptools 3 | twine 4 | wheel 5 | mypy 6 | boto3-stubs 7 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Amazon Nova Act 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/services/agentcore/templates/requirements.txt: -------------------------------------------------------------------------------- 1 | bedrock-agentcore 2 | nova-act 3 | pyyaml 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography 2 | fire 3 | install_playwright 4 | jsonschema 5 | numpy<=2.2.6 6 | pandas 7 | pillow 8 | playwright>=1.54.0,<=1.56.0 9 | pydantic>=2.10.6 10 | requests 11 | retry 12 | tenacity 13 | pytz 14 | strands-agents>=1.2.0,<=1.14.0 15 | strands-agents-tools<=0.2.13 16 | Deprecated 17 | boto3>=1.42.1 18 | boto3-stubs 19 | mypy-boto3-s3 20 | -------------------------------------------------------------------------------- /src/nova_act/cli/__version__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | VERSION = "1.0.0" 15 | -------------------------------------------------------------------------------- /src/nova_act/types/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Types module for nova_act.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/__version__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | VERSION = "3.0.67.0" # pragma: no cover 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Workflow utilities.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Core components for Nova Act CLI.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/ecr/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ECR client implementation.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/s3/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """S3 client implementation.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/services/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Workflow services package.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/types/state/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """State types module for nova_act.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/impl/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # Backend implementations for Nova Act SDK 15 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Browser interface module.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/nova_act/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """NovaAct client implementation.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Core client implementations for AWS services.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/agentcore/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """AgentCore client implementation.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/services/agentcore/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """AgentCore workflow services.""" 15 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/nova_act/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Constants for NovaAct client operations.""" 15 | 16 | DEFAULT_SERVICE_NAME = "nova-act" 17 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/ecr/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Constants for ECR client operations.""" 15 | 16 | ECR_SERVICE = "ecr" 17 | DEFAULT_ECR_REPO_NAME = "nova-act-cli-default" 18 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/types/dimensions_dict.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing_extensions import TypedDict 15 | 16 | 17 | class DimensionsDict(TypedDict): 18 | width: int 19 | height: int 20 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/iam/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """IAM client for role and policy operations.""" 15 | 16 | from nova_act.cli.core.clients.iam.client import IAMClient 17 | 18 | __all__ = ["IAMClient"] 19 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/types/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Types for browser interface.""" 15 | 16 | from nova_act.tools.browser.interface.types.agent_redirect_error import AgentRedirectError 17 | 18 | __all__ = ["AgentRedirectError"] 19 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/services/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Constants for workflow services.""" 15 | 16 | from pathlib import Path 17 | 18 | # AgentCore service template directory 19 | AGENTCORE_TEMPLATE_DIR = Path(__file__).parent / "agentcore" / "templates" 20 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/types/click_types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing_extensions import Literal, TypedDict 15 | 16 | 17 | class ClickOptions(TypedDict): 18 | blurField: bool | None 19 | 20 | 21 | ClickType = Literal["left", "left-double", "right"] 22 | -------------------------------------------------------------------------------- /src/nova_act/util/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Constants for NovaAct SDK. 15 | 16 | This module contains reusable constants used throughout the SDK. 17 | """ 18 | 19 | NOVA_ACT_FREE_VERSION = "Nova Act Free Version" 20 | NOVA_ACT_AWS_SERVICE = "Nova Act AWS Service" 21 | -------------------------------------------------------------------------------- /src/nova_act/types/json_type.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Union # noqa: F401 15 | 16 | from typing_extensions import TypeAliasType 17 | 18 | JSONType = TypeAliasType( 19 | "JSONType", 20 | "Union[dict[str, JSONType], list[JSONType], str, int, float, bool, None]", 21 | ) 22 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/types/scroll_types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from dataclasses import dataclass 15 | from typing import Literal 16 | 17 | 18 | @dataclass 19 | class ScrollPosition: 20 | x: float 21 | y: float 22 | 23 | 24 | ScrollDirection = Literal["up", "down", "left", "right"] 25 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/types/element_dict.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing_extensions import Dict, Optional, TypedDict 15 | 16 | 17 | class ElementDict(TypedDict): 18 | id: Optional[str] 19 | tagName: str 20 | className: Optional[str] 21 | textContent: Optional[str] 22 | attributes: Dict[str, str] 23 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/dom_actuation/dispatch_events_dict.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing_extensions import TypedDict 15 | 16 | from nova_act.tools.browser.default.dom_actuation.create_dom_events import DomEvents 17 | 18 | 19 | class DispatchEvents(TypedDict): 20 | type: str 21 | init: DomEvents | dict[str, bool] 22 | -------------------------------------------------------------------------------- /src/nova_act/types/state/page.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from attrs import define, field 15 | from attrs.setters import frozen 16 | 17 | 18 | @define 19 | class PageState: 20 | # Required constructor params (immutable) 21 | session_id: str = field(on_setattr=frozen) 22 | is_settled: bool = field(factory=lambda: False, init=False) 23 | -------------------------------------------------------------------------------- /src/nova_act/types/api/status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Literal, TypeAlias 15 | 16 | ActStatus: TypeAlias = Literal[ 17 | "RUNNING", "PENDING_CLIENT_ACTION", "PENDING_HUMAN_ACTION", "SUCCEEDED", "FAILED", "TIMED_OUT" 18 | ] 19 | 20 | WorkflowRunStatus: TypeAlias = Literal["RUNNING", "SUCCEEDED", "FAILED", "TIMED_OUT", "DELETING"] 21 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/types/click_options.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import warnings 15 | 16 | from nova_act.tools.browser.interface.types import click_types 17 | 18 | ClickOptions = click_types.ClickOptions 19 | 20 | 21 | __all__ = [ 22 | "ClickOptions", 23 | ] 24 | 25 | warnings.warn(f"{__name__} is deprecated; use {click_types.__name__}") 26 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/region.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Region utility functions for Nova Act CLI.""" 15 | 16 | import boto3 17 | 18 | DEFAULT_REGION = "us-east-1" 19 | 20 | 21 | def get_default_region() -> str: 22 | """Get the default AWS region from boto3 session or fallback to DEFAULT_REGION.""" 23 | session = boto3.Session() 24 | return session.region_name or DEFAULT_REGION 25 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/types/agent_redirect_error.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | class AgentRedirectError(Exception): 15 | """Error raised when an agent needs to be redirected.""" 16 | 17 | def __init__(self, error_and_correction: str): 18 | super().__init__(error_and_correction) 19 | self.error_and_correction = error_and_correction 20 | self.name = "AgentRedirectError" 21 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/services/agentcore/templates/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | # Set working directory 4 | WORKDIR /app 5 | 6 | # Install system dependencies 7 | RUN apt-get update && apt-get install -y \ 8 | curl \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | # Copy and install custom wheel files first 12 | # COPY wheels/ ./wheels/ 13 | # RUN pip install --no-cache-dir wheels/*.whl 14 | 15 | # Copy requirements and install remaining Python dependencies 16 | COPY requirements.txt . 17 | RUN pip install --no-cache-dir -r requirements.txt 18 | 19 | # Copy application files 20 | COPY . . 21 | 22 | # Create non-root user for security 23 | RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app 24 | USER appuser 25 | 26 | # Expose AgentCore standard port 27 | EXPOSE 8080 28 | 29 | # Set environment variables 30 | ENV PYTHONPATH=/app 31 | ENV AWS_DEFAULT_REGION=us-east-1 32 | ENV ENTRY_POINT={{entry_point}} 33 | 34 | # Start the application 35 | CMD ["python", "-m", "agentcore_handler"] 36 | -------------------------------------------------------------------------------- /src/nova_act/impl/extension.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from deprecated import deprecated 15 | 16 | from nova_act.tools.browser.interface.browser import BrowserActuatorBase 17 | from nova_act.tools.browser.interface.playwright_pages import PlaywrightPageManagerBase 18 | 19 | 20 | @deprecated(version="2.1", reason="NovaAct no longer supports extension-based actuation.") 21 | class ExtensionActuator(BrowserActuatorBase, PlaywrightPageManagerBase): 22 | """Dummy class kept for better error messaging. 23 | 24 | TODO: Remove, eventually. 25 | 26 | """ 27 | -------------------------------------------------------------------------------- /src/nova_act/types/workflow_run.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """WorkflowRun DTO for passing workflow context data safely.""" 15 | 16 | from __future__ import annotations 17 | 18 | from dataclasses import dataclass 19 | 20 | 21 | @dataclass(frozen=True) 22 | class WorkflowRun: 23 | """Immutable data transfer object for workflow context. 24 | 25 | This DTO safely passes workflow context data between components without 26 | creating tight coupling or state conflicts. 27 | """ 28 | 29 | workflow_definition_name: str 30 | workflow_run_id: str 31 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/interface/playwright_pages.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from abc import ABC, abstractmethod 15 | 16 | from playwright.sync_api import Page 17 | 18 | 19 | class PlaywrightPageManagerBase(ABC): 20 | """An object which maintains one or more Playwright Pages.""" 21 | 22 | @abstractmethod 23 | def get_page(self, index: int = -1) -> Page: 24 | """Get a page by index, or the main page if unspecfied.""" 25 | 26 | @property 27 | @abstractmethod 28 | def pages(self) -> list[Page]: 29 | """All of the pages managed by this instance.""" 30 | -------------------------------------------------------------------------------- /src/nova_act/types/guardrail.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Guardrail types for NovaAct.""" 15 | 16 | from enum import Enum, auto 17 | from typing import Callable, NamedTuple 18 | 19 | 20 | class GuardrailDecision(Enum): 21 | """Decision returned by state guardrail callback.""" 22 | 23 | PASS = auto() 24 | BLOCK = auto() 25 | 26 | 27 | class GuardrailInputState(NamedTuple): 28 | """Input representing state of the agent on which to apply a guardrail policy.""" 29 | 30 | browser_url: str 31 | 32 | 33 | GuardrailCallable = Callable[[GuardrailInputState], GuardrailDecision] 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "build", 4 | "setuptools", 5 | "wheel", 6 | ] 7 | build-backend = "setuptools.build_meta" 8 | 9 | [project] 10 | name = "nova-act" 11 | description = "A Python SDK for Amazon Nova Act." 12 | dynamic = ["dependencies", "version", "optional-dependencies"] 13 | requires-python = ">=3.10" 14 | authors = [ 15 | {name = "Amazon Nova Act", email="nova-act@amazon.com"}, 16 | ] 17 | license = "Apache-2.0" 18 | readme = "README.md" 19 | 20 | [project.scripts] 21 | act = "nova_act.cli.cli:main" 22 | 23 | [tool.setuptools.dynamic] 24 | version = {attr = "nova_act.__version__.VERSION"} 25 | dependencies = {file = "requirements.txt"} 26 | optional-dependencies.dev = {file = "requirements-dev.txt"} 27 | optional-dependencies.cli = {file = "requirements-cli.txt"} 28 | 29 | [tool.setuptools.package-data] 30 | nova_act = ["cli/workflow/services/*/templates/*"] 31 | 32 | [tool.mypy] 33 | disallow_untyped_defs = true 34 | disallow_any_explicit = true 35 | exclude = [ 36 | "src/nova_act/cli/workflow/services/agentcore/templates/*", 37 | ] 38 | plugins = [ 39 | "pydantic.mypy" 40 | ] 41 | strict = true 42 | 43 | [tool.pydantic-mypy] 44 | init_forbid_extra = true 45 | init_typed = true -------------------------------------------------------------------------------- /src/nova_act/util/common_js_expressions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from enum import Enum 15 | 16 | 17 | class Expressions(Enum): 18 | """JavaScript functions for Playwright page evaluation""" 19 | 20 | GET_VIEWPORT_SIZE = """() => { 21 | return { 22 | scrollHeight: document.body.scrollHeight, 23 | scrollLeft: window.scrollX, 24 | scrollTop: window.scrollY, 25 | scrollWidth: document.body.scrollWidth, 26 | width: window.innerWidth, 27 | height: window.innerHeight, 28 | } 29 | }""" 30 | 31 | GET_USER_AGENT = "() => navigator.userAgent" 32 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Configuration constants for Nova Act CLI.""" 15 | 16 | from nova_act.cli.core.theme import ThemeName 17 | 18 | # Default configuration values 19 | DEFAULT_REGION = "us-east-1" 20 | 21 | # Configuration directory and file names 22 | CONFIG_DIR_NAME = ".act_cli" 23 | USER_CONFIG_FILE_NAME = "config.yml" 24 | STATE_DIR_NAME = "state" 25 | BUILDS_DIR_NAME = "builds" 26 | 27 | # Build configuration 28 | BUILD_TEMP_DIR = "/tmp/nova-act-workflow-build/" 29 | BUILD_DIR_PREFIX = "nova-act-build-" 30 | DEFAULT_ENTRY_POINT = "main.py" 31 | 32 | # Theme configuration 33 | DEFAULT_THEME = ThemeName.DEFAULT 34 | THEME_ENV_VAR = "ACT_CLI_THEME" 35 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/util/go_to_url.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from playwright.sync_api import Page 15 | 16 | from nova_act.types.guardrail import GuardrailCallable 17 | from nova_act.util.url import validate_url 18 | 19 | 20 | def go_to_url( 21 | url: str, page: Page, allowed_file_open_paths: list[str] = [], state_guardrail: GuardrailCallable | None = None 22 | ) -> None: 23 | 24 | # Navigate to the URL, after validating 25 | page.goto( 26 | validate_url( 27 | url=url, 28 | default_to_https=True, 29 | allowed_file_open_paths=allowed_file_open_paths, 30 | state_guardrail=state_guardrail, 31 | ) 32 | ) 33 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/utils/tags.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Tag generation utilities for workflow deployments.""" 15 | 16 | from typing import Dict 17 | 18 | # WORKFLOW_TAG_KEY helps associate AWS resources with WorkflowDefinition 19 | WORKFLOW_TAG_KEY = "nova-act-workflow-definition-v1" 20 | 21 | 22 | def generate_workflow_tags(workflow_definition_name: str) -> Dict[str, str]: 23 | """Generate standardized tags for AgentCore runtime deployment. 24 | 25 | Args: 26 | workflow_definition_name: Name of the workflow definition 27 | 28 | Returns: 29 | Dictionary of tags to apply to the AgentCore runtime 30 | """ 31 | return {WORKFLOW_TAG_KEY: workflow_definition_name} 32 | -------------------------------------------------------------------------------- /src/nova_act/types/hooks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import TYPE_CHECKING, Protocol 15 | 16 | if TYPE_CHECKING: 17 | from nova_act.nova_act import NovaAct 18 | 19 | 20 | class StopHook(Protocol): 21 | """Interface for exit hooks that can be registered with NovaAct. 22 | 23 | Exit hooks are called during the stop() method, allowing for custom cleanup 24 | or finalization logic to be executed when the NovaAct client is stopped. 25 | """ 26 | 27 | def on_stop(self, nova_act: "NovaAct") -> None: 28 | """Called when NovaAct is stopping. 29 | 30 | Parameters 31 | ---------- 32 | nova_act : NovaAct 33 | The NovaAct instance that is being stopped 34 | """ 35 | ... 36 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/util/agent_hover.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from playwright.sync_api import Page 15 | 16 | from nova_act.tools.browser.default.util.bbox_parser import bounding_box_to_point 17 | from nova_act.tools.browser.default.util.element_helpers import viewport_dimensions 18 | from nova_act.types.api.step import BboxTLBR 19 | 20 | 21 | def agent_hover(bbox: BboxTLBR, page: Page) -> None: 22 | """ 23 | Hover on a point within a bounding box. 24 | 25 | Args: 26 | bounding_box: A dict representation of a bounding box 27 | page: Playwright Page object 28 | """ 29 | bbox.validate_in_viewport(**viewport_dimensions(page)) 30 | point = bounding_box_to_point(bbox) 31 | page.mouse.move(point["x"], point["y"]) 32 | -------------------------------------------------------------------------------- /src/nova_act/samples/setup_chrome_user_data_dir.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Set up a user_data_dir for logged in websites. 15 | 16 | See README for more details. 17 | 18 | Usage: 19 | python -m nova_act.samples.setup_chrome_user_data_dir --user_data_dir 20 | """ 21 | 22 | import os 23 | 24 | import fire # type: ignore 25 | 26 | from nova_act import NovaAct 27 | 28 | 29 | def main(user_data_dir: str) -> None: 30 | os.makedirs(user_data_dir, exist_ok=True) 31 | 32 | with NovaAct(starting_page="https://nova.amazon.com/act", user_data_dir=user_data_dir, clone_user_data_dir=False): 33 | input("Log into your websites, then press enter...") 34 | 35 | print(f"User data dir saved to {user_data_dir=}") 36 | 37 | 38 | if __name__ == "__main__": 39 | fire.Fire(main) 40 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/util/dispatch_dom_events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from playwright.sync_api import Locator 15 | 16 | from nova_act.tools.browser.default.dom_actuation.dispatch_events_dict import DispatchEvents 17 | 18 | 19 | def dispatch_event_sequence(element: Locator, events_config: list[DispatchEvents]) -> None: 20 | """ 21 | Dispatch a sequence of events to an element. 22 | 23 | Args: 24 | element: Playwright ElementHandle 25 | events_config: List of event configurations, each containing: 26 | - type: Event type (e.g., "pointermove", "click") 27 | - init: Dictionary of event initialization parameters 28 | """ 29 | 30 | for event in events_config: 31 | element.dispatch_event(event["type"], dict(event["init"])) 32 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/agentcore/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Constants for AgentCore client operations.""" 15 | 16 | # Service names 17 | BEDROCK_AGENT_CONTROL_SERVICE = "bedrock-agentcore-control" 18 | BEDROCK_AGENT_DATA_SERVICE = "bedrock-agentcore" 19 | STS_SERVICE = "sts" 20 | 21 | # Timeout configuration 22 | DEFAULT_READ_TIMEOUT = 7200 # 2 hours in seconds 23 | 24 | # Error codes 25 | ALREADY_EXISTS_ERROR = "AlreadyExists" 26 | CONFLICT_ERROR = "Conflict" 27 | RUNTIME_NOT_FOUND_ERROR = "Runtime not found" 28 | 29 | # Log group configuration 30 | LOG_GROUP_PREFIX = "/aws/bedrock-agentcore/runtimes/" 31 | OTEL_LOG_SUFFIX = "/runtime-logs" 32 | 33 | # Network configuration 34 | PUBLIC_NETWORK_MODE = "PUBLIC" 35 | DEFAULT_ENDPOINT_NAME = "DEFAULT" 36 | 37 | # Agent naming 38 | AGENT_NAME_PREFIX = "agent_" 39 | MAX_AGENT_NAME_LENGTH = 48 40 | -------------------------------------------------------------------------------- /src/nova_act/types/events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import annotations 15 | 16 | from dataclasses import dataclass 17 | from enum import Enum 18 | from typing import Optional, Union 19 | 20 | 21 | @dataclass 22 | class Event: 23 | type: EventType 24 | data: Union[LogData, ActionData] 25 | context: EventContext 26 | 27 | 28 | @dataclass 29 | class ActionData: 30 | action: str 31 | data: object 32 | 33 | 34 | @dataclass 35 | class LogData: 36 | log_level: LogType 37 | data: str 38 | 39 | 40 | class LogType(str, Enum): 41 | INFO = "info" 42 | ERROR = "error" 43 | 44 | 45 | class EventType(str, Enum): 46 | LOG = "log" 47 | ACTION = "action" 48 | 49 | 50 | @dataclass 51 | class EventContext: 52 | session_id: str 53 | act_id: Optional[str] = None 54 | num_steps_executed: Optional[int] = None 55 | payload_type: Optional[str] = None 56 | is_complete: bool = False 57 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/dom_actuation/type_events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from nova_act.tools.browser.default.dom_actuation.create_dom_events import ( 15 | create_mouse_event_init, 16 | create_pointer_event_init, 17 | ) 18 | from nova_act.tools.browser.default.dom_actuation.dispatch_events_dict import DispatchEvents 19 | 20 | 21 | def get_after_type_events(point: dict[str, float]) -> list[DispatchEvents]: 22 | """Get events for after typing.""" 23 | return [ 24 | {"type": "pointerout", "init": create_pointer_event_init(point, -1, 0)}, 25 | { 26 | "type": "pointerleave", 27 | "init": create_pointer_event_init(point, -1, 0, False, False, False), 28 | }, 29 | {"type": "mouseout", "init": create_mouse_event_init(point)}, 30 | { 31 | "type": "mouseleave", 32 | "init": create_mouse_event_init(point, -1, 0, False, False, False), 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/dom_actuation/scroll_events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from nova_act.tools.browser.default.dom_actuation.create_dom_events import ( 15 | create_mouse_event_init, 16 | create_pointer_event_init, 17 | ) 18 | from nova_act.tools.browser.default.dom_actuation.dispatch_events_dict import DispatchEvents 19 | 20 | 21 | def get_after_scroll_events(point: dict[str, float]) -> list[DispatchEvents]: 22 | """Get events for after scrolling.""" 23 | return [ 24 | {"type": "pointerout", "init": create_pointer_event_init(point, -1, 0)}, 25 | { 26 | "type": "pointerleave", 27 | "init": create_pointer_event_init(point, -1, 0, False, False, False), 28 | }, 29 | {"type": "mouseout", "init": create_mouse_event_init(point)}, 30 | { 31 | "type": "mouseleave", 32 | "init": create_mouse_event_init(point, -1, 0, False, False, False), 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/utils/console.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """AWS Console URL utilities for Nova Act CLI.""" 15 | 16 | 17 | def build_bedrock_agentcore_console_url(region: str, agent_id: str) -> str: 18 | """Build console URL for Bedrock AgentCore agent. 19 | 20 | Args: 21 | region: AWS region (e.g., 'us-east-1') 22 | agent_id: Agent ID extracted from ARN 23 | 24 | Returns: 25 | Console URL for the agent 26 | """ 27 | return f"https://{region}.console.aws.amazon.com/bedrock-agentcore/agents/{agent_id}" 28 | 29 | 30 | def build_nova_act_workflow_console_url(region: str, workflow_definition_name: str) -> str: 31 | """Build console URL for Nova Act workflow definition. 32 | 33 | Args: 34 | region: AWS region (e.g., 'us-east-1') 35 | workflow_definition_name: Workflow definition name 36 | 37 | Returns: 38 | Console URL for the workflow definition 39 | """ 40 | return f"https://{region}.console.aws.amazon.com/nova-act/home#/workflow-definitions/{workflow_definition_name}" 41 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/logging.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Centralized logging utilities for Nova Act CLI.""" 15 | 16 | import logging 17 | import os 18 | 19 | 20 | def log_api_key_status(logger: logging.Logger) -> str | None: 21 | """Log Nova Act API key status with appropriate level.""" 22 | api_key = os.getenv("NOVA_ACT_API_KEY") 23 | if api_key: 24 | logger.info("NOVA_ACT_API_KEY found in environment") 25 | else: 26 | logger.info("NOVA_ACT_API_KEY not found in environment") 27 | return api_key 28 | 29 | 30 | def get_live_tail_command(log_group: str) -> str: 31 | """Get AWS CLI start-live-tail command for a log group.""" 32 | return f"aws logs start-live-tail --log-group-identifiers {log_group}" 33 | 34 | 35 | def get_follow_command(log_group: str) -> str: 36 | """Get AWS CLI tail --follow command for a log group.""" 37 | return f"aws logs tail {log_group} --follow" 38 | 39 | 40 | def get_since_command(log_group: str) -> str: 41 | """Get AWS CLI tail --since command for a log group.""" 42 | return f"aws logs tail {log_group} --since 1h" 43 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/nova_act/types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Type definitions for workflow definition operations. 16 | """ 17 | 18 | from datetime import datetime 19 | 20 | from pydantic import BaseModel 21 | 22 | 23 | class ExportConfig(BaseModel): 24 | s3BucketName: str 25 | s3KeyPrefix: str | None = None 26 | 27 | 28 | class CreateWorkflowDefinitionRequest(BaseModel): 29 | name: str 30 | description: str | None = None 31 | exportConfig: ExportConfig | None = None 32 | clientToken: str | None = None 33 | 34 | 35 | class CreateWorkflowDefinitionResponse(BaseModel): 36 | pass 37 | 38 | 39 | class GetWorkflowDefinitionRequest(BaseModel): 40 | workflowDefinitionName: str 41 | 42 | 43 | class GetWorkflowDefinitionResponse(BaseModel): 44 | name: str 45 | arn: str 46 | createdAt: datetime 47 | description: str | None = None 48 | exportConfig: ExportConfig | None = None 49 | status: str 50 | 51 | 52 | class DeleteWorkflowDefinitionRequest(BaseModel): 53 | workflowDefinitionName: str 54 | 55 | 56 | class DeleteWorkflowDefinitionResponse(BaseModel): 57 | pass 58 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/iam/types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """IAM client types for basic boto3 operations.""" 15 | 16 | from typing import Dict 17 | 18 | from pydantic import BaseModel 19 | 20 | 21 | class CreateRoleRequest(BaseModel): 22 | """Request for creating IAM role.""" 23 | 24 | model_config = {"extra": "allow"} 25 | 26 | RoleName: str 27 | AssumeRolePolicyDocument: str 28 | Description: str | None = None 29 | 30 | 31 | class CreateRoleResponse(BaseModel): 32 | """Response from creating IAM role.""" 33 | 34 | model_config = {"extra": "allow"} 35 | 36 | Role: Dict[str, object] 37 | 38 | 39 | class GetRoleResponse(BaseModel): 40 | """Response from getting IAM role.""" 41 | 42 | model_config = {"extra": "allow"} 43 | 44 | Role: Dict[str, object] 45 | 46 | 47 | class AttachRolePolicyRequest(BaseModel): 48 | """Request for attaching managed policy to role.""" 49 | 50 | model_config = {"extra": "allow"} 51 | 52 | RoleName: str 53 | PolicyArn: str 54 | 55 | 56 | class PutRolePolicyRequest(BaseModel): 57 | """Request for putting inline policy on role.""" 58 | 59 | model_config = {"extra": "allow"} 60 | 61 | RoleName: str 62 | PolicyName: str 63 | PolicyDocument: str 64 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Custom exception classes for Nova Act CLI. 16 | """ 17 | 18 | 19 | class NovaActCLIError(Exception): 20 | """Base exception for all Nova Act CLI errors.""" 21 | 22 | pass 23 | 24 | 25 | class ValidationError(NovaActCLIError): 26 | """Raised when input validation fails.""" 27 | 28 | def __init__(self, message: str): 29 | self.message = message 30 | super().__init__(message) 31 | 32 | 33 | class DeploymentError(NovaActCLIError): 34 | """Raised when deployment operations fail.""" 35 | 36 | pass 37 | 38 | 39 | class ConfigurationError(NovaActCLIError): 40 | """Raised when configuration is invalid or missing.""" 41 | 42 | pass 43 | 44 | 45 | class WorkflowError(NovaActCLIError): 46 | """Raised when workflow operations fail.""" 47 | 48 | pass 49 | 50 | 51 | class WorkflowNameArnMismatchError(WorkflowError): 52 | """Raised when workflow name doesn't match the resource name in the ARN.""" 53 | 54 | pass 55 | 56 | 57 | class RuntimeError(NovaActCLIError): 58 | """Raised when runtime operations fail.""" 59 | 60 | pass 61 | 62 | 63 | class ImageBuildError(NovaActCLIError): 64 | """Raised when ECR image build operations fail.""" 65 | 66 | pass 67 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/dom_actuation/click_events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from nova_act.tools.browser.default.dom_actuation.create_dom_events import ( 15 | create_focus_event_init, 16 | create_mouse_event_init, 17 | create_pointer_event_init, 18 | ) 19 | from nova_act.tools.browser.default.dom_actuation.dispatch_events_dict import DispatchEvents 20 | 21 | 22 | def get_after_click_events(point: dict[str, float]) -> list[DispatchEvents]: 23 | """Get events for after a click.""" 24 | return [ 25 | {"type": "pointermove", "init": create_pointer_event_init(point, -1, 0)}, 26 | {"type": "mousemove", "init": create_mouse_event_init(point, 0, 0)}, 27 | {"type": "pointerout", "init": create_pointer_event_init(point, -1, 0)}, 28 | { 29 | "type": "pointerleave", 30 | "init": create_pointer_event_init(point, -1, 0, False, False, False), 31 | }, 32 | {"type": "mouseout", "init": create_mouse_event_init(point, 0, 0)}, 33 | { 34 | "type": "mouseleave", 35 | "init": create_mouse_event_init(point, 0, 0, False, False, False), 36 | }, 37 | {"type": "blur", "init": create_focus_event_init(False, False, True)}, 38 | {"type": "focusout", "init": create_focus_event_init(True, False, True)}, 39 | ] 40 | -------------------------------------------------------------------------------- /src/nova_act/samples/booking_with_data_from_tool.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Book a trip. 15 | 16 | Shows how to use Nova Act to fill out a multi-step form to book a trip. 17 | 18 | Usage: 19 | python -m nova_act.samples.booking_with_data_from_tool 20 | """ 21 | 22 | import fire # type: ignore 23 | 24 | from nova_act import NovaAct, tool 25 | 26 | 27 | @tool 28 | def get_traveller_info() -> dict[str, str]: 29 | """ 30 | Provides the necessary traveller info to book a flight. 31 | """ 32 | return { 33 | "name": "John Doe", 34 | "date_of_birth": "1/8/2025", 35 | "emergency_contact_name": "Jane Smith", 36 | "emergency_contact_relationship": "Spouse", 37 | "emergency_contact_phone": "555-555-5555", 38 | "medical_has_traveled_interstellar": "yes", 39 | "medical_implants": "no", 40 | "cabin_selection": "premium", 41 | "additional_cargo": "no", 42 | "payment_prepaid_code": "NOVAACT2025", 43 | } 44 | 45 | 46 | def main() -> None: 47 | with NovaAct( 48 | starting_page="https://nova.amazon.com/act/gym/next-dot/booking/step/1", tools=[get_traveller_info] 49 | ) as nova: 50 | result = nova.act_get("Book a flight and return the booking number.") 51 | print(f"✓ Booking number: {result.parsed_response}") 52 | 53 | 54 | if __name__ == "__main__": 55 | fire.Fire(main) 56 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/playwright_instance_options.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from dataclasses import dataclass, field 15 | 16 | from playwright.sync_api import Playwright 17 | 18 | from nova_act.types.features import SecurityOptions 19 | 20 | _DEFAULT_GO_TO_URL_TIMEOUT = 60 21 | 22 | 23 | @dataclass 24 | class PlaywrightInstanceOptions: 25 | maybe_playwright: Playwright | None 26 | starting_page: str | None 27 | chrome_channel: str 28 | headless: bool 29 | user_data_dir: str 30 | profile_directory: str | None 31 | cdp_endpoint_url: str | None 32 | screen_width: int 33 | screen_height: int 34 | user_agent: str | None 35 | record_video: bool 36 | ignore_https_errors: bool 37 | use_default_chrome_browser: bool = False 38 | go_to_url_timeout: int | None = None 39 | cdp_headers: dict[str, str] | None = None 40 | proxy: dict[str, str] | None = None 41 | cdp_use_existing_page: bool = False 42 | user_browser_args: list[str] | None = None 43 | security_options: SecurityOptions = field(default_factory=SecurityOptions) 44 | 45 | def __post_init__(self) -> None: 46 | self.owns_playwright = self.maybe_playwright is None 47 | self.owns_context = self.cdp_endpoint_url is None and not self.use_default_chrome_browser 48 | self.go_to_url_timeout = 1000 * (self.go_to_url_timeout or _DEFAULT_GO_TO_URL_TIMEOUT) 49 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Configuration management for regional workflow support.""" 15 | 16 | from pathlib import Path 17 | 18 | from nova_act.cli.core.constants import BUILDS_DIR_NAME, CONFIG_DIR_NAME 19 | 20 | 21 | def get_cli_config_dir() -> Path: 22 | """Get CLI configuration directory path.""" 23 | return Path.home() / CONFIG_DIR_NAME 24 | 25 | 26 | def get_state_dir() -> Path: 27 | """Get state directory path.""" 28 | return get_cli_config_dir() / "state" 29 | 30 | 31 | def get_account_dir(account_id: str) -> Path: 32 | """Get account directory path.""" 33 | return get_state_dir() / account_id 34 | 35 | 36 | def get_region_dir(account_id: str, region: str) -> Path: 37 | """Get region directory path.""" 38 | return get_account_dir(account_id) / region 39 | 40 | 41 | def get_state_file_path(account_id: str, region: str) -> Path: 42 | """Get state file path for specific account and region.""" 43 | return get_region_dir(account_id=account_id, region=region) / "workflows.json" 44 | 45 | 46 | def get_cli_config_file_path() -> Path: 47 | """Get CLI configuration file path for display purposes.""" 48 | return get_cli_config_dir() / "act_cli_config.json" 49 | 50 | 51 | def get_builds_dir() -> Path: 52 | """Get builds directory path.""" 53 | return get_cli_config_dir() / BUILDS_DIR_NAME 54 | 55 | 56 | def get_workflow_build_dir(workflow_name: str) -> Path: 57 | """Get build directory path for specific workflow.""" 58 | return get_builds_dir() / workflow_name 59 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/utils/bucket_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """High-level S3 bucket management for Nova Act workflows.""" 15 | 16 | import logging 17 | 18 | from boto3 import Session 19 | 20 | from nova_act.cli.core.clients.s3.client import S3Client 21 | 22 | logger = logging.getLogger(__name__) 23 | 24 | 25 | class BucketManager: 26 | """Manages S3 bucket operations for Nova Act workflows.""" 27 | 28 | def __init__(self, session: Session | None, region: str, account_id: str): 29 | self.region = region 30 | self.account_id = account_id 31 | self.s3_client = S3Client(session=session, region=region) 32 | 33 | def _default_bucket_exists(self) -> bool: 34 | """Check if default bucket exists directly.""" 35 | bucket_name = self.generate_default_nova_act_bucket_name() 36 | return self.s3_client.bucket_exists(bucket_name) 37 | 38 | def ensure_default_bucket(self) -> str: 39 | """Ensure default S3 bucket exists, create if needed.""" 40 | if self._default_bucket_exists(): 41 | return self.generate_default_nova_act_bucket_name() 42 | return self._create_default_bucket() 43 | 44 | def _create_default_bucket(self) -> str: 45 | """Create default bucket with standard naming.""" 46 | bucket_name = self.generate_default_nova_act_bucket_name() 47 | self.s3_client.create_bucket(bucket_name) 48 | return bucket_name 49 | 50 | def generate_default_nova_act_bucket_name(self) -> str: 51 | """Generate standard bucket name.""" 52 | return f"nova-act-{self.account_id}-{self.region}" 53 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/dom_actuation/create_dom_events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing_extensions import TypedDict 15 | 16 | 17 | class DomEvents(TypedDict): 18 | bubbles: bool 19 | button: int 20 | clientX: float 21 | clientY: float 22 | cancelable: bool 23 | composed: bool 24 | detail: int 25 | 26 | 27 | def create_pointer_event_init( 28 | point: dict[str, float], 29 | button: int = -1, 30 | detail: int = 0, 31 | bubbles: bool = True, 32 | cancelable: bool = True, 33 | composed: bool = True, 34 | ) -> DomEvents: 35 | """Create initialization parameters for pointer events.""" 36 | return { 37 | "bubbles": bubbles, 38 | "button": button, 39 | "cancelable": cancelable, 40 | "clientX": point["x"], 41 | "clientY": point["y"], 42 | "composed": composed, 43 | "detail": detail, 44 | } 45 | 46 | 47 | def create_mouse_event_init( 48 | point: dict[str, float], 49 | button: int = 0, 50 | detail: int = 0, 51 | bubbles: bool = True, 52 | cancelable: bool = True, 53 | composed: bool = True, 54 | ) -> DomEvents: 55 | """Create initialization parameters for mouse events.""" 56 | return { 57 | "bubbles": bubbles, 58 | "button": button, 59 | "cancelable": cancelable, 60 | "clientX": point["x"], 61 | "clientY": point["y"], 62 | "composed": composed, 63 | "detail": detail, 64 | } 65 | 66 | 67 | def create_focus_event_init(bubbles: bool = False, cancelable: bool = False, composed: bool = True) -> dict[str, bool]: 68 | """Create initialization parameters for focus events.""" 69 | return {"bubbles": bubbles, "cancelable": cancelable, "composed": composed} 70 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/services/agentcore/templates/agentcore_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | AgentCore handler for Nova Act workflows. 16 | 17 | This handler expects the following to be present in the deployment: 18 | - A main workflow file specified by the entry_point parameter 19 | - All required dependencies installed via requirements.txt 20 | - Proper AWS credentials and permissions for the agent runtime 21 | - The workflow function should accept a payload parameter and return results 22 | """ 23 | 24 | import importlib.util 25 | import os 26 | from typing import Any, Dict 27 | 28 | from bedrock_agentcore import BedrockAgentCoreApp 29 | 30 | app = BedrockAgentCoreApp() 31 | 32 | 33 | @app.route("/ping") 34 | def ping() -> Dict[str, str]: 35 | return {"status": "healthy"} 36 | 37 | 38 | @app.entrypoint 39 | def handler(payload: Dict[str, Any]) -> None: # type: ignore 40 | """Main entrypoint for AgentCore Runtime.""" 41 | # Extract and set environment variables from payload 42 | if "AC_HANDLER_ENV" in payload: 43 | env_vars = payload.pop("AC_HANDLER_ENV") 44 | if isinstance(env_vars, dict): 45 | os.environ.update(env_vars) 46 | 47 | entry_point = os.environ.get("ENTRY_POINT", "main.py") 48 | module_name = entry_point.replace(".py", "") 49 | 50 | spec = importlib.util.spec_from_file_location(module_name, entry_point) 51 | if spec is None: 52 | raise ImportError(f"Could not load spec for {entry_point}") 53 | 54 | module = importlib.util.module_from_spec(spec) 55 | if spec.loader is None: 56 | raise ImportError(f"No loader available for {entry_point}") 57 | 58 | spec.loader.exec_module(module) 59 | 60 | module.main(payload) 61 | 62 | 63 | if __name__ == "__main__": 64 | app.run() 65 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/identity.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Core identity utilities for Nova Act CLI.""" 15 | 16 | from boto3 import Session 17 | from botocore.exceptions import ClientError, NoCredentialsError 18 | 19 | from nova_act.cli.core.error_detection import get_credential_error_message, is_credential_error 20 | from nova_act.cli.core.exceptions import ConfigurationError 21 | 22 | 23 | def validate_iam_role_arn(role_arn: str) -> bool: 24 | """Validate IAM role ARN format. 25 | 26 | Args: 27 | role_arn: The IAM role ARN to validate 28 | 29 | Returns: 30 | True if valid format, False otherwise 31 | """ 32 | return role_arn.startswith("arn:aws:iam::") and ":role/" in role_arn 33 | 34 | 35 | def extract_role_name_from_arn(role_arn: str) -> str: 36 | """Extract role name from IAM role ARN. 37 | 38 | Args: 39 | role_arn: The IAM role ARN (e.g., arn:aws:iam::123456789012:role/MyRole) 40 | 41 | Returns: 42 | The role name (e.g., MyRole) 43 | 44 | Raises: 45 | ValueError: If ARN format is invalid 46 | """ 47 | if not validate_iam_role_arn(role_arn): 48 | raise ValueError(f"Invalid IAM role ARN format: {role_arn}") 49 | 50 | return role_arn.split(":role/")[1] 51 | 52 | 53 | def auto_detect_account_id(session: Session | None, region: str) -> str: 54 | """Auto-detect AWS account ID using STS.""" 55 | try: 56 | effective_session = session or Session() 57 | sts_client = effective_session.client("sts", region_name=region) 58 | response = sts_client.get_caller_identity() 59 | return str(response["Account"]) 60 | except (NoCredentialsError, ClientError) as e: 61 | if is_credential_error(e): 62 | raise ConfigurationError(get_credential_error_message()) 63 | raise 64 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/util/bbox_parser.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Dict 15 | 16 | from nova_act.types.api.step import BboxTLBR 17 | 18 | 19 | def parse_bbox_string(bbox_string: str) -> BboxTLBR: 20 | """Convert a bounding box string to a dictionary representation. 21 | 22 | Args: 23 | bbox_string: A string in the format "top,left,bottom,right" 24 | 25 | Returns: 26 | A dictionary with keys 'top', 'bottom', 'left', 'right' representing the bounding rectangle 27 | """ 28 | bbox_string = bbox_string.strip() 29 | if not bbox_string.startswith("") or not bbox_string.endswith(""): 30 | raise ValueError( 31 | f"Invalid bounding box format. Expected 'top,left,bottom,right', got: {bbox_string}" 32 | ) 33 | 34 | # Extract the coordinates 35 | coords_str = bbox_string.replace("", "").replace("", "").strip() 36 | if not coords_str: 37 | raise ValueError(f"Empty coordinates in bounding box: {bbox_string}") 38 | 39 | # Parse coordinates 40 | try: 41 | coord_parts = coords_str.split(",") 42 | if len(coord_parts) != 4: 43 | raise ValueError(f"Expected 4 coordinates, got {len(coord_parts)}: {bbox_string}") 44 | 45 | coords = [float(coord.strip()) for coord in coord_parts] 46 | except ValueError as e: 47 | raise ValueError(f"Invalid coordinate values in bounding box: {bbox_string}. Error: {e}") from e 48 | 49 | return BboxTLBR(*coords) 50 | 51 | 52 | def bounding_box_to_point(bbox: BboxTLBR) -> Dict[str, float]: 53 | # Calculate the center point of the bounding box 54 | center_x = (bbox.left + bbox.right) / 2 55 | center_y = (bbox.top + bbox.bottom) / 2 56 | 57 | return {"x": center_x, "y": center_y} 58 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/utils/arn.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ARN utilities for workflow definitions.""" 15 | 16 | import re 17 | 18 | 19 | def construct_workflow_definition_arn(name: str, region: str, account_id: str) -> str: 20 | """Construct a workflow definition ARN.""" 21 | return f"arn:aws:nova-act:{region}:{account_id}:workflow-definition/{name}" 22 | 23 | 24 | def extract_agent_id_from_arn(agent_arn: str) -> str: 25 | """Extract agent ID from agent ARN. 26 | 27 | Args: 28 | agent_arn: Full agent ARN (e.g., 'arn:aws:bedrock:us-east-1:123456789012:agent/agent-id') 29 | 30 | Returns: 31 | Agent ID extracted from ARN 32 | 33 | Example: 34 | >>> extract_agent_id_from_arn('arn:aws:bedrock:us-east-1:123456789012:agent/ABCD1234') 35 | 'ABCD1234' 36 | """ 37 | return agent_arn.split("/")[-1] 38 | 39 | 40 | def extract_workflow_definition_name_from_arn(workflow_definition_arn: str) -> str: 41 | """Extract workflow definition name from ARN. 42 | 43 | Args: 44 | workflow_definition_arn: Full workflow definition ARN 45 | 46 | Returns: 47 | Workflow definition name extracted from ARN 48 | """ 49 | return workflow_definition_arn.split("/")[-1] 50 | 51 | 52 | def validate_workflow_definition_arn(arn: str) -> None: 53 | """Validate nova-act workflow definition ARN format.""" 54 | # Handle None input 55 | if arn is None: 56 | raise ValueError("ARN cannot be None") 57 | 58 | # Handle empty string 59 | if not arn or not arn.strip(): 60 | raise ValueError("ARN cannot be empty") 61 | 62 | # Regex validation 63 | pattern = r"^arn:aws:nova-act:[^:]+:\d{12}:workflow-definition/.+$" 64 | if not re.match(pattern=pattern, string=arn): 65 | raise ValueError(f"Invalid nova-act workflow definition ARN format: {arn}") 66 | -------------------------------------------------------------------------------- /src/nova_act/cli/cli.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Main entry point for Nova Act CLI.""" 15 | 16 | try: 17 | import click # noqa: F401 18 | except ImportError: 19 | raise ImportError("To use the Nova Act CLI, install with the [cli] extra: " "pip install --upgrade nova-act[cli]") 20 | 21 | try: 22 | import yaml # noqa: F401 23 | except ImportError: 24 | raise ImportError("To use the Nova Act CLI, install with the [cli] extra: " "pip install --upgrade nova-act[cli]") 25 | 26 | import os 27 | 28 | from nova_act.cli.__version__ import VERSION 29 | from nova_act.cli.core.theme import ThemeName, set_active_theme 30 | from nova_act.cli.group import StyledGroup 31 | from nova_act.cli.workflow.commands import create, delete, deploy, run, show, update 32 | from nova_act.cli.workflow.commands.list import list 33 | 34 | 35 | @click.group(cls=StyledGroup) 36 | @click.option("--profile", help="AWS profile to use (from ~/.aws/credentials)") 37 | @click.pass_context 38 | def workflow(ctx: click.Context, profile: str | None) -> None: 39 | """Workflow management commands.""" 40 | if profile: 41 | os.environ["AWS_PROFILE"] = profile 42 | 43 | 44 | # Add all commands to workflow group in desired order 45 | workflow.add_command(create.create) 46 | workflow.add_command(update.update) 47 | workflow.add_command(delete.delete) 48 | workflow.add_command(show.show) 49 | workflow.add_command(deploy.deploy) 50 | workflow.add_command(run.run) 51 | workflow.add_command(list) 52 | 53 | 54 | @click.group(cls=StyledGroup) 55 | @click.version_option(version=VERSION) 56 | @click.option("--no-color", is_flag=True, help="Disable colored output") 57 | def main(no_color: bool) -> None: 58 | """Nova Act CLI.""" 59 | if no_color: 60 | set_active_theme(ThemeName.NONE) 61 | 62 | 63 | main.add_command(workflow) 64 | 65 | if __name__ == "__main__": 66 | main() 67 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/util/file_upload_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Optional 15 | 16 | from playwright.sync_api import FileChooser, Page 17 | from playwright.sync_api import TimeoutError as PlaywrightTimeoutError 18 | 19 | 20 | def click_and_maybe_return_file_chooser( 21 | page: Page, 22 | x: float, 23 | y: float, 24 | *, 25 | timeout_ms: int = 600, 26 | ) -> Optional[FileChooser]: 27 | """ 28 | Perform a single *left click* at (x, y), wrapped in Playwright's `expect_file_chooser`. 29 | Returns the `FileChooser` if clicking opened a native file chooser dialog; otherwise returns None. 30 | 31 | Notes: 32 | This function does *not* complete/cancel the chooser. The caller decides what to do next. 33 | This is because there is no playwright API to easily cancel the chooser. One could call 34 | file_chooser.set_files([]) to "dismiss" the chooser, but pages often have some 35 | on-upload listener and this would trigger an update to the page's state. 36 | 37 | In order to prevent this side effect, we simply do nothing with the file chooser. This allows 38 | the agent to later upload the file with an agent type. 39 | However, because this file chooser is left open, for some sites, it also prevents any other action 40 | from being taken until a file has been uploaded. 41 | """ 42 | try: 43 | with page.expect_file_chooser(timeout=timeout_ms) as file_chooser_info: 44 | page.mouse.click(x, y) 45 | 46 | # If a file chooser shows up, return it (Playwright provides it at `info.value`) 47 | return file_chooser_info.value 48 | except (PlaywrightTimeoutError, AttributeError, TypeError): 49 | # Timeout => no chooser fired. 50 | # AttributeError/TypeError => likely a half-mocked page; treat as no chooser. 51 | return None 52 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/util/get_bbox_values.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import importlib.resources 15 | from typing import Tuple 16 | 17 | from playwright.sync_api import Page 18 | 19 | from nova_act.types.api.step import BboxTLWH 20 | 21 | 22 | def get_bbox_values(page: Page) -> Tuple[dict[int, BboxTLWH], str]: 23 | """ 24 | Get the bounding box values for all elements on the page and generate a new HTML DOM 25 | with nova-act-id attributes added to elements that pass the filtering conditions. 26 | 27 | Args: 28 | page: The Playwright Page object 29 | 30 | Returns: 31 | A tuple containing: 32 | - A dictionary mapping element identifiers to their bounding boxes 33 | - A string containing the modified HTML DOM with nova-act-id attributes 34 | """ 35 | 36 | # Use importlib.resources to load the JavaScript file 37 | # This works regardless of whether the package is installed from source or as a wheel 38 | js_code = ( 39 | importlib.resources.files("nova_act.tools.browser.default.util").joinpath("get_simplified_dom.js").read_text() 40 | ) 41 | 42 | # Process all elements in a single JavaScript execution 43 | # This avoids multiple Python-to-browser round trips 44 | js_results = page.evaluate(js_code) 45 | 46 | # Extract the bounding box results and modified HTML 47 | bbox_results = js_results.get("bboxes", {}) 48 | modified_html = js_results.get("modifiedHtml", "") 49 | 50 | # Convert JavaScript object to Python dictionary with proper types 51 | bbox_dict: dict[int, BboxTLWH] = {} 52 | for key, value in bbox_results.items(): 53 | bbox_dict[int(key)] = { 54 | "x": float(value["x"]), 55 | "y": float(value["y"]), 56 | "width": float(value["width"]), 57 | "height": float(value["height"]), 58 | } 59 | 60 | return bbox_dict, modified_html 61 | -------------------------------------------------------------------------------- /src/nova_act/types/api/trace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Literal 15 | 16 | from typing_extensions import NotRequired, TypedDict 17 | 18 | """ 19 | Trace type definitions for Nova Act API. 20 | 21 | """ 22 | 23 | 24 | ErrorCode = Literal[ 25 | "INVALID_INPUT", 26 | "MODEL_ERROR", 27 | "INTERNAL_ERROR", 28 | "GUARDRAILS_ERROR", 29 | "UNAUTHORIZED_ERROR", 30 | "TOO_MANY_REQUESTS", 31 | "DAILY_QUOTA_LIMIT_ERROR", 32 | "SESSION_EXPIRED_ERROR", 33 | ] 34 | 35 | 36 | class FailureTrace(TypedDict): 37 | """Failure trace of the service.""" 38 | 39 | type: ErrorCode 40 | message: str 41 | 42 | 43 | class TraceMetadataDict(TypedDict): 44 | """Metadata for trace information.""" 45 | 46 | sessionId: str 47 | actId: str 48 | stepId: str 49 | stepCount: int 50 | startTime: str 51 | 52 | 53 | class ScreenshotDict(TypedDict): 54 | """Screenshot data structure in trace.""" 55 | 56 | source: str 57 | sourceType: str 58 | 59 | 60 | class OrchestrationTraceInputDict(TypedDict): 61 | """Input data for orchestration trace.""" 62 | 63 | screenshot: ScreenshotDict 64 | activeURL: str 65 | prompt: str 66 | 67 | 68 | class OrchestrationTraceOutputDict(TypedDict): 69 | """Output data for orchestration trace.""" 70 | 71 | rawResponse: str 72 | 73 | 74 | class OrchestrationTraceDict(TypedDict): 75 | """Complete orchestration trace structure.""" 76 | 77 | input: OrchestrationTraceInputDict 78 | output: OrchestrationTraceOutputDict 79 | 80 | 81 | class ExternalTraceDict(TypedDict): 82 | """External trace structure.""" 83 | 84 | metadata: TraceMetadataDict 85 | orchestrationTrace: OrchestrationTraceDict 86 | failureTrace: NotRequired[FailureTrace] 87 | 88 | 89 | class TraceDict(TypedDict): 90 | """Complete trace structure with external wrapper.""" 91 | 92 | external: ExternalTraceDict 93 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/iam/client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """IAM client for basic boto3 IAM operations.""" 15 | 16 | from boto3 import Session 17 | from botocore.exceptions import ClientError 18 | 19 | from nova_act.cli.core.clients.iam.types import ( 20 | AttachRolePolicyRequest, 21 | CreateRoleRequest, 22 | CreateRoleResponse, 23 | GetRoleResponse, 24 | PutRolePolicyRequest, 25 | ) 26 | 27 | 28 | class IAMClient: 29 | """Client for basic IAM operations.""" 30 | 31 | def __init__(self, session: Session | None, region: str): 32 | self.region = region 33 | self.session = session or Session() 34 | self.client = self.session.client("iam", region_name=region) 35 | 36 | def create_role(self, request: CreateRoleRequest) -> CreateRoleResponse: 37 | """Create IAM role.""" 38 | response = self.client.create_role(**request.model_dump()) 39 | return CreateRoleResponse(**response) 40 | 41 | def get_role(self, role_name: str) -> GetRoleResponse: 42 | """Get IAM role.""" 43 | response = self.client.get_role(RoleName=role_name) 44 | return GetRoleResponse(**response) 45 | 46 | def role_exists(self, role_name: str) -> bool: 47 | """Check if IAM role exists.""" 48 | try: 49 | self.get_role(role_name) 50 | return True 51 | except ClientError as e: 52 | if e.response["Error"]["Code"] == "NoSuchEntity": 53 | return False 54 | raise RuntimeError(f"Failed to check IAM role {role_name}: {e}") 55 | 56 | def attach_role_policy(self, request: AttachRolePolicyRequest) -> None: 57 | """Attach managed policy to role.""" 58 | self.client.attach_role_policy(**request.model_dump()) 59 | 60 | def put_role_policy(self, request: PutRolePolicyRequest) -> None: 61 | """Put inline policy on role.""" 62 | self.client.put_role_policy(**request.model_dump()) 63 | -------------------------------------------------------------------------------- /src/nova_act/samples/s3_writer_example.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Example script demonstrating how to use the S3Writer with NovaAct. 16 | 17 | This script shows how to: 18 | 1. Create a boto3 session with appropriate credentials 19 | 2. Create an S3Writer with various configuration options 20 | 3. Use the S3Writer with NovaAct to automatically upload session files to S3 21 | 22 | Required AWS permissions: 23 | - s3:ListObjects on the bucket and prefix 24 | - s3:PutObject on the bucket and prefix 25 | """ 26 | 27 | import boto3 28 | import fire # type: ignore 29 | 30 | from nova_act import NovaAct 31 | from nova_act.util.s3_writer import S3Writer 32 | 33 | 34 | def main( 35 | s3_bucket_name: str, 36 | s3_prefix: str = "s3_writer_example/", 37 | record_video: bool = False, 38 | ) -> None: 39 | """ 40 | Run a simple NovaAct session and upload the session files to S3. 41 | 42 | Parameters 43 | ---------- 44 | s3_bucket_name : str 45 | The name of the S3 bucket to upload files to 46 | s3_prefix : str, optional 47 | A prefix to add to the S3 keys (like a folder path in S3) 48 | The prefix is used exactly as provided 49 | record_video : bool, optional 50 | Whether to record video of the browser session 51 | """ 52 | # Create a boto3 session with appropriate credentials 53 | boto_session = boto3.Session() 54 | 55 | # Create an S3Writer 56 | s3_writer = S3Writer( 57 | boto_session=boto_session, 58 | s3_bucket_name=s3_bucket_name, 59 | s3_prefix=s3_prefix, 60 | metadata={"Example": "S3Writer", "Source": "example_script"}, 61 | ) 62 | 63 | # Use the S3Writer with NovaAct 64 | with NovaAct( 65 | starting_page="https://nova.amazon.com/act/gym/next-dot/search", 66 | record_video=record_video, 67 | stop_hooks=[s3_writer], 68 | ) as nova: 69 | nova.act("Find flights from Boston to Wolf on Feb 22nd") 70 | 71 | 72 | if __name__ == "__main__": 73 | fire.Fire(main) 74 | -------------------------------------------------------------------------------- /src/nova_act/util/human_wait_time_tracker.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Utility for tracking human wait time during act execution.""" 15 | 16 | import time 17 | 18 | 19 | class HumanWaitTimeTracker: 20 | """Tracks cumulative human wait time during an act execution. 21 | 22 | This tracker is used to measure the total time spent waiting for human input 23 | (e.g., approve() or ui_takeover() calls) so that it can be excluded from the 24 | agent's "time worked" calculation. 25 | """ 26 | 27 | def __init__(self) -> None: 28 | """Initialize a new HumanWaitTimeTracker.""" 29 | self._total_wait_time_s: float = 0.0 30 | self._current_wait_start: float | None = None 31 | 32 | def start_wait(self) -> None: 33 | """Mark the start of a human wait period. 34 | 35 | Raises: 36 | RuntimeError: If a wait is already in progress (nested wait calls). 37 | """ 38 | if self._current_wait_start is not None: 39 | raise RuntimeError("Wait already in progress") 40 | self._current_wait_start = time.time() 41 | 42 | def end_wait(self) -> None: 43 | """Mark the end of a human wait period and accumulate the duration. 44 | 45 | Raises: 46 | RuntimeError: If no wait is in progress. 47 | """ 48 | if self._current_wait_start is None: 49 | raise RuntimeError("No wait in progress") 50 | duration = time.time() - self._current_wait_start 51 | self._total_wait_time_s += duration 52 | self._current_wait_start = None 53 | 54 | def get_total_wait_time_s(self) -> float: 55 | """Get the total accumulated wait time in seconds. 56 | 57 | Returns: 58 | The total wait time in seconds across all wait periods. 59 | """ 60 | return self._total_wait_time_s 61 | 62 | def reset(self) -> None: 63 | """Reset the tracker for a new act. 64 | 65 | This clears both the total accumulated wait time and any in-progress wait. 66 | """ 67 | self._total_wait_time_s = 0.0 68 | self._current_wait_start = None 69 | -------------------------------------------------------------------------------- /src/nova_act/tools/browser/default/util/take_observation.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import base64 15 | import os 16 | from datetime import datetime 17 | 18 | from playwright.sync_api import Page 19 | 20 | from nova_act.tools.browser.default.util.image_helpers import ( 21 | resize_image, 22 | take_screenshot_as_data_url, 23 | ) 24 | from nova_act.tools.browser.interface.types.dimensions_dict import DimensionsDict 25 | 26 | 27 | def take_observation(page: Page, dimensions: DimensionsDict | None = None, save_screenshot: bool = False) -> str: 28 | """ 29 | Takes an observation of the current page state, including a screenshot. 30 | 31 | Args: 32 | page: The Playwright Page object. 33 | dimensions: Dictionary with width and height keys for resizing the image. 34 | 35 | Returns: 36 | A data URL of the resized screenshot. 37 | """ 38 | # Take a screenshot and get it as a data URL 39 | screenshot_data_url = take_screenshot_as_data_url(page) 40 | if dimensions is not None: 41 | screenshot_data_url = resize_image(screenshot_data_url, dimensions) 42 | 43 | if save_screenshot: 44 | save_data_url_to_file(screenshot_data_url, f"screenshot_{datetime.now().isoformat()}.jpg") 45 | return screenshot_data_url 46 | 47 | 48 | def save_data_url_to_file(data_url: str, file_path: str) -> None: 49 | """ 50 | Saves a data URL to a file. 51 | 52 | Args: 53 | data_url: The data URL to save. 54 | file_path: The path to save the file to. 55 | """ 56 | # Extract the base64 encoded image data from the data URL 57 | if "base64," in data_url: 58 | base64_data = data_url.split("base64,")[1] 59 | image_data = base64.b64decode(base64_data) 60 | 61 | # Create directory if it doesn't exist 62 | os.makedirs(os.path.dirname(os.path.abspath(file_path)), exist_ok=True) 63 | 64 | # Write the image data to a file 65 | with open(file_path, "wb") as f: 66 | f.write(image_data) 67 | print(f"Image saved to {file_path}") 68 | else: 69 | raise RuntimeError("Invalid data URL format") 70 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/commands/list.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """List command for listing local workflows.""" 15 | 16 | from typing import Dict 17 | 18 | import click 19 | from boto3 import Session 20 | 21 | from nova_act.cli.core.identity import auto_detect_account_id 22 | from nova_act.cli.core.region import get_default_region 23 | from nova_act.cli.core.styling import command, header, secondary, value 24 | from nova_act.cli.core.types import WorkflowInfo 25 | from nova_act.cli.workflow.workflow_manager import WorkflowManager 26 | 27 | 28 | def _display_region_header(region: str) -> None: 29 | """Display current region header.""" 30 | click.echo(header(f"Workflows in {region}")) 31 | click.echo() 32 | 33 | 34 | def _display_workflow_table(workflows: Dict[str, WorkflowInfo]) -> None: 35 | """Display workflows in simple list format.""" 36 | for name, workflow_info in workflows.items(): 37 | created_date = workflow_info.created_at.strftime("%Y-%m-%d") 38 | click.echo(f"{value(name)} {secondary(f'({created_date})')}") 39 | 40 | 41 | def _display_footer() -> None: 42 | """Display footer with show command reference.""" 43 | click.echo() 44 | click.echo(f"Use {command('act workflow show -n ')} for detailed information.") 45 | click.echo() 46 | 47 | 48 | @click.command() 49 | @click.option("--region", help="AWS region to query") 50 | def list(region: str | None = None) -> None: 51 | """List all configured workflows.""" 52 | # Create session at command boundary 53 | session = Session() 54 | 55 | effective_region = region or get_default_region() 56 | account_id = auto_detect_account_id(session=session, region=effective_region) 57 | workflow_manager = WorkflowManager(session=session, region=effective_region, account_id=account_id) 58 | workflows = workflow_manager.list_workflows() 59 | 60 | if not workflows: 61 | click.echo( 62 | f"{secondary('No workflows found.')} Use {command('act workflow create')} to create your first workflow." 63 | ) 64 | click.echo() 65 | return 66 | 67 | _display_region_header(effective_region) 68 | _display_workflow_table(workflows) 69 | _display_footer() 70 | -------------------------------------------------------------------------------- /src/nova_act/types/act_result.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import annotations 15 | 16 | import dataclasses 17 | 18 | from nova_act.types.act_metadata import ActMetadata 19 | from nova_act.types.json_type import JSONType 20 | 21 | """ 22 | Successful outcome of act() 23 | """ 24 | 25 | 26 | @dataclasses.dataclass(frozen=True) 27 | class ActResult: 28 | """A result from act().""" 29 | 30 | metadata: ActMetadata 31 | 32 | def __repr__(self) -> str: 33 | # Get all instance attributes except 'metadata' and 'steps_taken' 34 | field_names = [ 35 | field.name for field in dataclasses.fields(self) if field.name not in ("metadata", "steps_taken") 36 | ] 37 | 38 | # Get the values of those instance attributes 39 | field_values = [getattr(self, field) for field in field_names] 40 | 41 | # Strip starting _ from any field names 42 | field_names = [field[1:] if field.startswith("_") else field for field in field_names] 43 | 44 | # Build the custom fields string 45 | custom_fields = "\n ".join( 46 | f"{field_name} = {field_value}" for field_name, field_value in zip(field_names, field_values) 47 | ) 48 | 49 | # Indent metadata for visual distinction 50 | metadata_str = str(self.metadata).replace("\n", "\n ") 51 | 52 | # If there are custom fields, add them before the metadata 53 | if custom_fields: 54 | return f"{self.__class__.__name__}(\n" f" {custom_fields}\n" f" metadata = {metadata_str}\n" f")" 55 | 56 | # If no custom fields, just show the metadata 57 | return f"{self.__class__.__name__}(\n" f" metadata = {metadata_str}\n" f")" 58 | 59 | 60 | 61 | @dataclasses.dataclass(frozen=True, repr=False) 62 | class ActGetResult(ActResult): 63 | """A result from act_get().""" 64 | 65 | response: str | None = None 66 | parsed_response: JSONType | None = None 67 | valid_json: bool | None = None 68 | matches_schema: bool | None = None 69 | 70 | def without_response(self) -> ActResult: 71 | """Convert to an ActResultWithoutResponse.""" 72 | return ActResult( 73 | metadata=self.metadata, 74 | ) 75 | -------------------------------------------------------------------------------- /src/nova_act/impl/program/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from dataclasses import dataclass, field 15 | 16 | from pydantic import BaseModel, ConfigDict 17 | 18 | from nova_act.tools.actuator.interface.actuator import ActionType 19 | from nova_act.tools.compatibility import callable_tool 20 | from nova_act.types.act_errors import ActToolError 21 | from nova_act.types.json_type import JSONType 22 | 23 | 24 | class FrozenBaseModel(BaseModel): 25 | model_config = ConfigDict(frozen=True) 26 | 27 | 28 | class Call(FrozenBaseModel): 29 | name: str 30 | kwargs: dict[str, JSONType] 31 | id: str 32 | is_tool: bool = False 33 | 34 | 35 | @dataclass(frozen=True) 36 | class CompiledCall: 37 | source: Call 38 | target: ActionType 39 | 40 | 41 | @dataclass(frozen=True) 42 | class CallResult: 43 | call: Call 44 | return_value: JSONType 45 | error: Exception | None 46 | 47 | 48 | @dataclass(frozen=True) 49 | class ProgramResult: 50 | call_results: list[CallResult] = field(default_factory=list) 51 | 52 | def has_return(self) -> CallResult | None: 53 | return next((r for r in self.call_results if r.call.name == "return"), None) 54 | 55 | def has_throw(self) -> CallResult | None: 56 | return next((r for r in self.call_results if r.call.name == "throw"), None) 57 | 58 | def has_exception(self) -> CallResult | None: 59 | return next((r for r in self.call_results if r.error is not None), None) 60 | 61 | def has_observation(self) -> CallResult | None: 62 | return next((r for r in self.call_results if r.call.name == "takeObservation"), None) 63 | 64 | 65 | @dataclass(frozen=True) 66 | class CompiledProgram: 67 | calls: list[CompiledCall] 68 | 69 | 70 | class Program(FrozenBaseModel): 71 | calls: list[Call] 72 | 73 | def compile(self, tool_map: dict[str, ActionType]) -> CompiledProgram: 74 | compiled_calls = [] 75 | for call in self.calls: 76 | target = tool_map.get(call.name) 77 | if target is None: 78 | raise ActToolError(message=f"Tool '{call.name}' was not found.") 79 | compiled_calls.append(CompiledCall(source=call, target=callable_tool(target))) 80 | return CompiledProgram(calls=compiled_calls) 81 | -------------------------------------------------------------------------------- /src/nova_act/util/jsonschema.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | from typing import Mapping 17 | 18 | import jsonschema 19 | 20 | from nova_act.types.act_result import ActGetResult, ActResult 21 | from nova_act.types.json_type import JSONType 22 | 23 | BOOL_SCHEMA = {"type": "boolean"} 24 | STRING_SCHEMA = {"type": "string"} 25 | 26 | 27 | def validate_jsonschema_schema(schema: Mapping[str, JSONType]) -> None: 28 | try: 29 | jsonschema.Draft7Validator.check_schema(schema) 30 | except jsonschema.SchemaError as e: 31 | raise jsonschema.SchemaError("Schema provided isn't a valid jsonschema") from e 32 | 33 | 34 | def add_schema_to_prompt(prompt: str, schema: Mapping[str, JSONType]) -> str: 35 | schema_str: str = json.dumps(schema) 36 | return f"{prompt}, format output with jsonschema: {schema_str}" 37 | 38 | 39 | def populate_json_schema_response(result: ActResult, schema: Mapping[str, JSONType]) -> ActGetResult: 40 | response = result.response if isinstance(result, ActGetResult) else None 41 | if not response: 42 | return ActGetResult( 43 | response=response, 44 | parsed_response=None, 45 | valid_json=False, 46 | matches_schema=False, 47 | metadata=result.metadata, 48 | ) 49 | try: 50 | if schema != STRING_SCHEMA: 51 | parsed_response = json.loads(response) 52 | else: 53 | parsed_response = response 54 | jsonschema.validate(instance=parsed_response, schema=schema) 55 | except json.JSONDecodeError: 56 | return ActGetResult( 57 | response=response, 58 | parsed_response=None, 59 | valid_json=False, 60 | matches_schema=False, 61 | metadata=result.metadata, 62 | ) 63 | except jsonschema.ValidationError: 64 | return ActGetResult( 65 | response=response, 66 | parsed_response=parsed_response, 67 | valid_json=True, 68 | matches_schema=False, 69 | metadata=result.metadata, 70 | ) 71 | return ActGetResult( 72 | response=response, 73 | parsed_response=parsed_response, 74 | valid_json=True, 75 | matches_schema=True, 76 | metadata=result.metadata, 77 | ) 78 | -------------------------------------------------------------------------------- /src/nova_act/util/event_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from typing import Callable 15 | 16 | from nova_act.types.events import ( 17 | ActionData, 18 | Event, 19 | EventContext, 20 | EventType, 21 | LogData, 22 | LogType, 23 | ) 24 | from nova_act.types.state.act import Act 25 | from nova_act.util.logging import get_session_id 26 | 27 | NovaEventCallback = Callable[[Event], None] 28 | 29 | 30 | class EventHandler: 31 | def __init__(self, callback: NovaEventCallback | None): 32 | self._callback = callback 33 | self._act: Act | None = None 34 | 35 | def set_act(self, act: Act) -> None: 36 | self._act = act 37 | 38 | def build_context(self, **kwargs: object) -> EventContext: 39 | data = kwargs.get("data") 40 | return EventContext( 41 | session_id=get_session_id(), 42 | act_id=self._act.id if self._act else None, 43 | num_steps_executed=len(self._act._steps) if self._act else None, 44 | payload_type=type(data).__name__ if data is not None else None, 45 | is_complete=self._act.is_complete if self._act else False, 46 | ) 47 | 48 | def build_data(self, *, event_type: EventType, **kwargs: object) -> ActionData | LogData: 49 | match event_type: 50 | case EventType.ACTION: 51 | return ActionData( 52 | action=str(kwargs.get("action", "unknown")), 53 | data=kwargs.get("data", ""), 54 | ) 55 | case EventType.LOG: 56 | log_level = kwargs.get("log_level", LogType.INFO) 57 | if not isinstance(log_level, LogType): 58 | raise TypeError(f"log_level must be a LogType, got {type(log_level).__name__}") 59 | return LogData(log_level=log_level, data=str(kwargs.get("data", ""))) 60 | case _: 61 | raise ValueError(f"Unsupported EventType: {type}") 62 | 63 | def send_event(self, *, type: EventType, **kwargs: object) -> None: 64 | if self._callback is None: 65 | return 66 | event_data = self.build_data(event_type=type, **kwargs) 67 | event_context = self.build_context(**kwargs) 68 | event = Event(type=type, data=event_data, context=event_context) 69 | self._callback(event) 70 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/nova_act/client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Minimal client for WorkflowDefinition operations. 16 | """ 17 | 18 | import logging 19 | from importlib.resources import files 20 | 21 | from boto3 import Session 22 | 23 | from nova_act.cli.core.clients.nova_act.constants import DEFAULT_SERVICE_NAME 24 | from nova_act.cli.core.clients.nova_act.types import ( 25 | CreateWorkflowDefinitionRequest, 26 | CreateWorkflowDefinitionResponse, 27 | DeleteWorkflowDefinitionRequest, 28 | DeleteWorkflowDefinitionResponse, 29 | GetWorkflowDefinitionRequest, 30 | GetWorkflowDefinitionResponse, 31 | ) 32 | from nova_act.cli.core.constants import DEFAULT_REGION 33 | 34 | logger = logging.getLogger(__name__) 35 | 36 | 37 | class NovaActClient: 38 | """Minimal client for WorkflowDefinition operations.""" 39 | 40 | def __init__( 41 | self, 42 | boto_session: Session, 43 | region_name: str = DEFAULT_REGION, 44 | ): 45 | client_kwargs = {"service_name": DEFAULT_SERVICE_NAME, "region_name": region_name} 46 | self._client = boto_session.client(**client_kwargs) # type: ignore[call-overload] 47 | 48 | def create_workflow_definition(self, request: CreateWorkflowDefinitionRequest) -> CreateWorkflowDefinitionResponse: 49 | """Create a workflow definition.""" 50 | params = request.model_dump(exclude_none=True) 51 | response = self._client.create_workflow_definition(**params) 52 | result = CreateWorkflowDefinitionResponse.model_validate(response) 53 | logger.info(f"Successfully created workflow definition: {request.name}") 54 | return result 55 | 56 | def get_workflow_definition(self, request: GetWorkflowDefinitionRequest) -> GetWorkflowDefinitionResponse: 57 | """Get a workflow definition.""" 58 | params = request.model_dump(exclude_none=True) 59 | response = self._client.get_workflow_definition(**params) 60 | return GetWorkflowDefinitionResponse.model_validate(response) 61 | 62 | def delete_workflow_definition(self, request: DeleteWorkflowDefinitionRequest) -> DeleteWorkflowDefinitionResponse: 63 | """Delete a workflow definition.""" 64 | params = request.model_dump(exclude_none=True) 65 | response = self._client.delete_workflow_definition(**params) 66 | return DeleteWorkflowDefinitionResponse.model_validate(response) 67 | -------------------------------------------------------------------------------- /src/nova_act/util/step_server_time_tracker.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import annotations 15 | 16 | import json 17 | 18 | from playwright.sync_api import BrowserContext 19 | 20 | from nova_act.util.logging import setup_logging 21 | 22 | _LOGGER = setup_logging(__name__) 23 | 24 | 25 | class StepServerTimeTracker: 26 | _instance = None 27 | _initialized: bool 28 | 29 | def __new__( 30 | cls, browser_context: BrowserContext | None = None, endpoint_pattern: str = "/step" 31 | ) -> "StepServerTimeTracker": 32 | if cls._instance is None: 33 | cls._instance = super().__new__(cls) 34 | cls._instance._initialized = False 35 | return cls._instance 36 | 37 | def __init__(self, browser_context: BrowserContext | None = None, endpoint_pattern: str = "/step"): 38 | if self._initialized: 39 | return 40 | 41 | if browser_context is None: 42 | raise ValueError("First initialization requires browser_context") 43 | 44 | self.context = browser_context 45 | self.endpoint_pattern = endpoint_pattern 46 | self._step_durations: dict[str, float] = {} 47 | self._setup_tracking() 48 | self._initialized = True 49 | 50 | def _setup_tracking(self) -> None: 51 | def handle_response(response) -> None: # type: ignore[no-untyped-def] 52 | if self.endpoint_pattern in response.url and response.request.post_data: 53 | try: 54 | body = json.loads(response.request.post_data) 55 | act_id = body.get("actId") 56 | timing = response.request.timing 57 | duration = (timing["responseStart"] - timing["requestStart"]) / 1000 58 | if act_id: 59 | key = f"{act_id}" 60 | self._step_durations[key] = duration 61 | except Exception as e: 62 | _LOGGER.debug("Missed step server timing", exc_info=e) 63 | 64 | self.context.on("response", handle_response) 65 | 66 | def get_step_duration_s(self, *, act_id: str) -> float | None: 67 | key = f"{act_id}" 68 | return self._step_durations.get(key) 69 | 70 | @classmethod 71 | def get_instance(cls) -> StepServerTimeTracker | None: 72 | if cls._instance is not None: 73 | return cls._instance 74 | return None 75 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Core type definitions for Nova Act CLI.""" 15 | 16 | from datetime import datetime 17 | from typing import Dict 18 | 19 | from pydantic import BaseModel, Field 20 | 21 | from nova_act.cli.core.constants import BUILD_TEMP_DIR, DEFAULT_ENTRY_POINT 22 | 23 | 24 | class ServiceDeployment(BaseModel): 25 | """Represents a service deployment with ARN.""" 26 | 27 | deployment_arn: str 28 | image_uri: str 29 | 30 | 31 | class AgentCoreDeployment(BaseModel): 32 | """Represents an AgentCore deployment result.""" 33 | 34 | deployment_arn: str 35 | image_uri: str 36 | image_tag: str 37 | 38 | 39 | class WorkflowDeployments(BaseModel): 40 | """Service-based deployment structure.""" 41 | 42 | agentcore: AgentCoreDeployment | None = None 43 | 44 | 45 | class WorkflowInfo(BaseModel): 46 | """Workflow information with per-region WorkflowDefinition ARN.""" 47 | 48 | name: str 49 | directory_path: str 50 | created_at: datetime 51 | workflow_definition_arn: str | None = None 52 | deployments: WorkflowDeployments = Field(default_factory=WorkflowDeployments) 53 | metadata: Dict[str, str] | None = None 54 | last_image_tag: str | None = None 55 | 56 | 57 | class BuildConfig(BaseModel): 58 | """Build configuration settings.""" 59 | 60 | default_entry_point: str = DEFAULT_ENTRY_POINT 61 | temp_dir: str = BUILD_TEMP_DIR 62 | 63 | 64 | class ThemeConfig(BaseModel): 65 | """Theme configuration settings.""" 66 | 67 | name: str = "default" 68 | enabled: bool = True 69 | 70 | 71 | class UserConfig(BaseModel): 72 | """User configuration preferences (YAML-based).""" 73 | 74 | build: BuildConfig = Field(default_factory=BuildConfig) 75 | theme: ThemeConfig = Field(default_factory=ThemeConfig) 76 | 77 | 78 | class RegionState(BaseModel): 79 | """Per-region workflow state.""" 80 | 81 | workflows: Dict[str, WorkflowInfo] = Field(default_factory=dict) 82 | last_updated: datetime = Field(default_factory=datetime.now) 83 | version: str = "1.0" 84 | 85 | 86 | class StateLockInfo(BaseModel): 87 | """State file locking information.""" 88 | 89 | lock_file: str # Using str instead of Path for JSON serialization 90 | timeout: int = 30 91 | 92 | 93 | class RegionContext(BaseModel): 94 | """Region and account context for deployment operations.""" 95 | 96 | region: str 97 | account_id: str 98 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/user_config_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """User configuration manager for YAML-based config.""" 15 | 16 | from typing import Any, Dict 17 | 18 | import yaml 19 | 20 | from nova_act.cli.core.config import get_cli_config_dir 21 | from nova_act.cli.core.constants import USER_CONFIG_FILE_NAME 22 | from nova_act.cli.core.exceptions import ConfigurationError 23 | from nova_act.cli.core.types import UserConfig 24 | 25 | 26 | class UserConfigManager: 27 | """Manages user configuration in YAML format.""" 28 | 29 | @staticmethod 30 | def get_config() -> UserConfig: 31 | """Load user configuration from YAML file.""" 32 | config_file = get_cli_config_dir() / USER_CONFIG_FILE_NAME 33 | if not config_file.exists(): 34 | return UserConfigManager._create_default_config() 35 | 36 | try: 37 | with open(config_file) as f: 38 | data = yaml.safe_load(f) or {} 39 | return UserConfigManager._load_user_config(data) 40 | except Exception as e: 41 | raise ConfigurationError(f"Failed to load user config: {e}") 42 | 43 | @staticmethod 44 | def save_config(config: UserConfig) -> None: 45 | """Save user configuration to YAML file.""" 46 | UserConfigManager._ensure_config_dir() 47 | 48 | try: 49 | data = UserConfigManager._convert_config_to_dict(config) 50 | config_file = get_cli_config_dir() / USER_CONFIG_FILE_NAME 51 | with open(file=config_file, mode="w") as f: 52 | yaml.dump(data=data, stream=f, default_flow_style=False, sort_keys=False) 53 | except Exception as e: 54 | raise ConfigurationError(f"Failed to save user config: {e}") 55 | 56 | @staticmethod 57 | def _create_default_config() -> UserConfig: 58 | """Create default user configuration.""" 59 | config = UserConfig() 60 | UserConfigManager.save_config(config) 61 | return config 62 | 63 | @staticmethod 64 | def _ensure_config_dir() -> None: 65 | """Ensure ~/.act_cli directory exists.""" 66 | get_cli_config_dir().mkdir(exist_ok=True) 67 | 68 | @staticmethod 69 | def _load_user_config(data: Dict[str, Any]) -> UserConfig: # type: ignore[explicit-any] 70 | """Load UserConfig from dictionary.""" 71 | return UserConfig.model_validate(data) 72 | 73 | @staticmethod 74 | def _convert_config_to_dict(config: UserConfig) -> Dict[str, Any]: # type: ignore[explicit-any] 75 | """Convert UserConfig to dictionary.""" 76 | return config.model_dump() 77 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/commands/delete.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Delete command for Nova Act CLI workflows.""" 15 | 16 | import click 17 | from boto3 import Session 18 | 19 | from nova_act.cli.core.exceptions import ConfigurationError, WorkflowError 20 | from nova_act.cli.core.identity import auto_detect_account_id 21 | from nova_act.cli.core.region import get_default_region 22 | from nova_act.cli.core.styling import secondary, styled_error_exception, success, value 23 | from nova_act.cli.workflow.workflow_manager import WorkflowManager 24 | 25 | 26 | def _display_deletion_summary(name: str, region: str) -> None: 27 | """Display summary of what will be deleted.""" 28 | click.echo(f"{secondary('Workflow to delete:')} {value(name)}") 29 | click.echo(f"{secondary('Target region:')} {value(region)}") 30 | click.echo(f"{secondary('Action:')} {secondary('Remove from configuration')}") 31 | 32 | 33 | def _confirm_deletion(force: bool) -> bool: 34 | """Handle deletion confirmation prompt.""" 35 | if not force: 36 | if not click.confirm("Are you sure you want to proceed?"): 37 | click.echo(secondary("Operation cancelled")) 38 | return False 39 | return True 40 | 41 | 42 | @click.command() 43 | @click.option("--name", "-n", required=True, help="Name of the workflow to delete") 44 | @click.option("--region", help="AWS region to delete WorkflowDefinition from (defaults to configured region)") 45 | @click.option("--force", is_flag=True, help="Skip confirmation prompt") 46 | def delete(name: str, region: str | None = None, force: bool = False) -> None: 47 | """Delete a workflow from configuration.""" 48 | try: 49 | # Create session at command boundary 50 | session = Session() 51 | 52 | target_region = region or get_default_region() 53 | account_id = auto_detect_account_id(session=session, region=target_region) 54 | workflow_manager = WorkflowManager(session=session, region=target_region, account_id=account_id) 55 | 56 | _display_deletion_summary(name=name, region=target_region) 57 | 58 | if not _confirm_deletion(force=force): 59 | return 60 | 61 | # Remove from local config only to avoid unintentional AWS resource deletion 62 | workflow_manager.delete_workflow(name=name) 63 | success(f"✅ Removed '{name}' from configuration") 64 | click.echo() 65 | 66 | except (WorkflowError, ConfigurationError) as e: 67 | raise styled_error_exception(str(e)) 68 | except Exception as e: 69 | raise styled_error_exception(f"Unexpected error: {str(e)}") from e 70 | -------------------------------------------------------------------------------- /src/nova_act/types/state/step.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import annotations 15 | 16 | import json 17 | from dataclasses import dataclass, fields 18 | from datetime import datetime 19 | 20 | from nova_act.impl.program.base import Program 21 | from nova_act.types.api.step import Statement 22 | from nova_act.types.api.trace import TraceDict 23 | 24 | 25 | @dataclass(frozen=True) 26 | class ModelInput: 27 | image: str 28 | prompt: str 29 | active_url: str 30 | simplified_dom: str 31 | 32 | 33 | @dataclass(frozen=True) 34 | class ModelOutput: 35 | awl_raw_program: str 36 | request_id: str 37 | program_ast: list[Statement] 38 | 39 | @classmethod 40 | def from_plan_response(cls, plan_response: str, request_id: str = "") -> ModelOutput: 41 | """Instantiate from a plan response.""" 42 | 43 | try: 44 | plan_response_json = json.loads(plan_response) 45 | except json.JSONDecodeError: 46 | raise ValueError("actuationPlanResponse is not JSON-Type.") 47 | 48 | if "rawProgramBody" not in plan_response_json: 49 | raise ValueError("actuationPlanResponse is missing rawProgramBody.") 50 | 51 | awl_raw_program = plan_response_json["rawProgramBody"] 52 | 53 | try: 54 | program_ast: list[Statement] = plan_response_json["program"]["body"][0]["body"]["body"] 55 | except (IndexError, KeyError, TypeError): 56 | raise LookupError("actuationPlanResponse is missing program body.") 57 | 58 | return cls(awl_raw_program=awl_raw_program, request_id=request_id, program_ast=program_ast) 59 | 60 | 61 | @dataclass(frozen=True) 62 | class Step: 63 | model_input: ModelInput 64 | model_output: ModelOutput 65 | observed_time: datetime 66 | server_time_s: float | None 67 | step_id: str | None = None 68 | trace: TraceDict | None = None 69 | 70 | # Input validation 71 | def __post_init__(self) -> None: 72 | """Validate instance after creation.""" 73 | if not self.model_input.image: 74 | raise ValueError("Screenshot is required") 75 | if not self.model_output.awl_raw_program: 76 | raise ValueError("Program body is required") 77 | 78 | def with_program(self, program: Program) -> StepWithProgram: 79 | """Create a new instance with an interpreted Program.""" 80 | return StepWithProgram( 81 | program=program, 82 | **{field.name: getattr(self, field.name) for field in fields(self)}, 83 | ) 84 | 85 | 86 | @dataclass(frozen=True, kw_only=True) 87 | class StepWithProgram(Step): 88 | program: Program 89 | -------------------------------------------------------------------------------- /src/nova_act/impl/controller.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from __future__ import annotations 15 | 16 | import threading 17 | from contextlib import nullcontext 18 | from enum import Enum, auto 19 | from types import TracebackType 20 | from typing import Optional, Type 21 | 22 | from nova_act.impl.keyboard_event_watcher import KeyboardEventWatcher 23 | 24 | 25 | class ControlState(Enum): 26 | ACTIVE = auto() 27 | PAUSED = auto() 28 | CANCELLED = auto() 29 | 30 | 31 | class NovaStateController: 32 | def __init__(self, tty: bool) -> None: 33 | self._keyboard_manager = ( 34 | KeyboardEventWatcher(chr(24), "ctrl+x", "stop agent act() call without quitting the browser") 35 | if tty 36 | else nullcontext() 37 | ) 38 | self._state = ControlState.ACTIVE 39 | self._lock = threading.Lock() 40 | self._tty = tty 41 | 42 | @property 43 | def state(self) -> ControlState: 44 | with self._lock: 45 | if isinstance(self._keyboard_manager, KeyboardEventWatcher) and self._keyboard_manager.is_triggered(): 46 | if self._state != ControlState.CANCELLED: 47 | self._state = ControlState.CANCELLED 48 | return self._state 49 | 50 | def pause(self) -> None: 51 | with self._lock: 52 | if self._state != ControlState.ACTIVE: 53 | raise RuntimeError(f"Cannot pause when state is {self._state}") 54 | self._state = ControlState.PAUSED 55 | 56 | def resume(self) -> None: 57 | with self._lock: 58 | if self._state != ControlState.PAUSED: 59 | raise RuntimeError(f"Cannot resume when state is {self._state}") 60 | self._state = ControlState.ACTIVE 61 | 62 | def cancel(self) -> None: 63 | with self._lock: 64 | self._state = ControlState.CANCELLED 65 | 66 | def reset(self) -> None: 67 | with self._lock: 68 | if isinstance(self._keyboard_manager, KeyboardEventWatcher): 69 | self._keyboard_manager.reset() 70 | self._state = ControlState.ACTIVE 71 | 72 | def __enter__(self) -> NovaStateController: 73 | try: 74 | self._keyboard_manager.__enter__() 75 | finally: 76 | self.reset() 77 | 78 | return self 79 | 80 | def __exit__( 81 | self, 82 | exc_type: Optional[Type[BaseException]], 83 | exc_val: Optional[BaseException], 84 | exc_tb: Optional[TracebackType], 85 | ) -> None: 86 | try: 87 | self._keyboard_manager.__exit__(exc_type, exc_val, exc_tb) 88 | finally: 89 | self.reset() 90 | -------------------------------------------------------------------------------- /src/nova_act/samples/print_number_of_emails.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Log into email app, and print the title of first email if approved. 15 | 16 | Shows how to use Nova Act to provide HITL (Human in the loop) callbacks 17 | to let human takeover the UI, and approve a workflow step. 18 | 19 | Usage: 20 | python -m nova_act.samples.print_number_of_emails --email_app_url 21 | """ 22 | 23 | import fire # type: ignore 24 | 25 | from nova_act import NovaAct 26 | from nova_act.tools.human.interface.human_input_callback import ( 27 | ApprovalResponse, 28 | HumanInputCallbacksBase, 29 | UiTakeoverResponse, 30 | ) 31 | 32 | 33 | class ConsoleBasedHumanInputCallbacks(HumanInputCallbacksBase): 34 | def approve(self, message: str) -> ApprovalResponse: 35 | print(f"\n🤖 Approval required for act_id: {self.current_act_id} inside act_session_id: {self.act_session_id}:") 36 | print(f" {message}") 37 | 38 | while True: 39 | answer = input(" Please enter '(y)es' or '(n)o' to approve the request: ") 40 | if answer in ["n", "y"]: 41 | return ApprovalResponse.YES if answer == "y" else ApprovalResponse.CANCEL 42 | 43 | def ui_takeover(self, message: str) -> UiTakeoverResponse: 44 | print( 45 | f"\n🤖 UI Takeover required for act_id: {self.current_act_id} inside act_session_id: {self.act_session_id}:" 46 | ) 47 | print(f" {message}") 48 | print(" Please complete the action in the browser...") 49 | while True: 50 | answer = input( 51 | " Please enter '(d)one' or '(c)ancel' to indicate completion or cancellation of takeover: " 52 | ) 53 | if answer in ["d", "c"]: 54 | return UiTakeoverResponse.COMPLETE if answer == "d" else UiTakeoverResponse.CANCEL 55 | 56 | 57 | def print_email_title(email_app_url: str) -> None: 58 | """Print the title of the first email in the inbox. 59 | 60 | Args: 61 | email_app_url: full URL of the email application to log into, and read email from 62 | 63 | """ 64 | task_prompt = ( 65 | "Log into the email web appliation. " 66 | "Ask for approval to return the number of emails in the inbox. " 67 | "If approved, return the number of emails in the inbox." 68 | ) 69 | with NovaAct( 70 | starting_page=email_app_url, 71 | tty=False, 72 | human_input_callbacks=ConsoleBasedHumanInputCallbacks(), 73 | ) as nova: 74 | result = nova.act_get(task_prompt) 75 | print(f"Task completed: {result.response}") 76 | 77 | 78 | if __name__ == "__main__": 79 | fire.Fire(print_email_title) 80 | -------------------------------------------------------------------------------- /src/nova_act/util/s3_writer_errors.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Custom exception classes for S3Writer component. 16 | 17 | This module defines specialized exception classes for the S3Writer component 18 | to provide more specific error handling for different S3-related issues. 19 | """ 20 | 21 | from typing import Optional 22 | 23 | 24 | class S3WriterError(Exception): 25 | """Base exception class for all S3Writer-related errors.""" 26 | 27 | def __init__(self, message: str, original_error: Optional[Exception] = None): 28 | """ 29 | Initialize S3WriterError. 30 | 31 | Args: 32 | message: Descriptive error message 33 | original_error: Original exception that caused this error, if any 34 | """ 35 | self.original_error = original_error 36 | super().__init__(message) 37 | 38 | 39 | class S3WriterBucketNotFoundError(S3WriterError): 40 | """Exception raised when the specified S3 bucket does not exist.""" 41 | 42 | def __init__(self, bucket_name: str, original_error: Optional[Exception] = None): 43 | """ 44 | Initialize S3WriterBucketNotFoundError. 45 | 46 | Args: 47 | bucket_name: Name of the bucket that does not exist 48 | original_error: Original exception that caused this error, if any 49 | """ 50 | message = ( 51 | f"S3 bucket '{bucket_name}' does not exist. " 52 | f"Please verify the bucket name and ensure it exists in your AWS account." 53 | ) 54 | super().__init__(message, original_error) 55 | self.bucket_name = bucket_name 56 | 57 | 58 | class S3WriterPermissionError(S3WriterError): 59 | """Exception raised when there are permission issues with S3 operations.""" 60 | 61 | def __init__( 62 | self, 63 | operation: str, 64 | resource: str, 65 | original_error: Optional[Exception] = None, 66 | additional_info: str = "", 67 | ): 68 | """ 69 | Initialize S3WriterPermissionError. 70 | 71 | Args: 72 | operation: The S3 operation that failed (e.g., "ListObjects", "PutObject") 73 | resource: The S3 resource being accessed (e.g., "bucket/prefix") 74 | original_error: Original exception that caused this error, if any 75 | additional_info: Additional information about the error or how to resolve it 76 | """ 77 | message = f"Insufficient permissions to perform '{operation}' on S3 resource '{resource}'. " 78 | 79 | if additional_info: 80 | message += f" {additional_info}" 81 | 82 | super().__init__(message, original_error) 83 | self.operation = operation 84 | self.resource = resource 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /src/nova_act/tools/actuator/interface/actuator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from abc import ABC, abstractmethod 15 | from typing import Any, Callable, Sequence, TypeAlias 16 | 17 | from deprecated import deprecated 18 | from strands import tool 19 | from strands.tools.decorator import DecoratedFunctionTool 20 | from strands.types.tools import ToolSpec 21 | 22 | from nova_act.tools.compatibility import safe_tool_spec 23 | 24 | 25 | @deprecated(version="2.0.200", reason="the `@action` decorator is no longer required.") 26 | def action(method: Callable[..., Any]) -> Callable[..., Any]: # type: ignore[explicit-any] 27 | """Annotate a method as an Action.""" 28 | return tool(method) 29 | 30 | 31 | ActionType: TypeAlias = DecoratedFunctionTool[..., Any] # type: ignore[explicit-any] 32 | """An Action the NovaAct client can carry out upon model request.""" 33 | 34 | 35 | class ActuatorBase(ABC): 36 | """Base class for defining an Actuator. 37 | 38 | Users provide Actions to their Actuator by implementing the `list_actions` 39 | method, which must return a `Sequence` of strands `DecoratedFunctionTools`. 40 | A `DecoratedFunctionTool` can be created by using strands's `@tool` decorator 41 | on a function/method. Once the user has provided this list of Actions, NovaAct 42 | parses the Action name, description, and signature from these and provides the 43 | information to the planning model. 44 | 45 | Actuators may also define the `domain` attribute. This is optional; 46 | when provided, it is used to ground the planning model to the specifics 47 | of the actuation environment. 48 | 49 | Actuators may also define custom `start` and `stop` methods, to be called 50 | when NovaAct enters and exits. Applications might include starting and 51 | stopping a required server or client; for example, an MCP ClientSession. 52 | 53 | """ 54 | 55 | domain: str | None = None 56 | """An optional description of the actuation domain.""" 57 | 58 | def start(self, **kwargs: Any) -> None: # type: ignore[explicit-any] 59 | """Prepare for actuation.""" 60 | 61 | def stop(self, **kwargs: Any) -> None: # type: ignore[explicit-any] 62 | """Clean up when done.""" 63 | 64 | @property 65 | @abstractmethod 66 | def started(self, **kwargs: Any) -> bool: # type: ignore[explicit-any] 67 | """ 68 | Tells whether the actuator instance was started or not. 69 | """ 70 | 71 | @abstractmethod 72 | def list_actions(self) -> Sequence[ActionType]: 73 | """List the valid Actions this Actuator can take.""" 74 | 75 | def asdict(self) -> dict[str, str | list[ToolSpec]]: 76 | """Return a dictionary representation of this class.""" 77 | return { 78 | "domain": self.domain or "", 79 | "actions": [safe_tool_spec(action.tool_spec) for action in self.list_actions()], 80 | } 81 | -------------------------------------------------------------------------------- /src/nova_act/types/features.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator 15 | from typing_extensions import TypedDict 16 | 17 | from nova_act.util.logging import setup_logging 18 | from nova_act.util.path_validator import validate_allowed_paths 19 | 20 | _LOGGER = setup_logging(__name__) 21 | 22 | 23 | class SecurityOptions(BaseModel): 24 | # Disallow extra parameters 25 | model_config = ConfigDict(extra="forbid") 26 | 27 | allow_file_urls: bool = Field( 28 | default=False, 29 | deprecated=True, 30 | description="allow_file_urls is deprecated; use allowed_file_open_paths instead", 31 | ) 32 | """Note that use of a boolean to enable all file:// urls is deprecated as it was too coarse-grained 33 | """ 34 | 35 | allowed_file_open_paths: list[str] = Field(default_factory=list, validate_default=True) 36 | """List of local file:// paths which the agent is allowed to navigate to 37 | 38 | Examples: 39 | - ["/home/nova-act/shared/*"] - Allow access to files in a specific directory 40 | - ["/home/nova-act/shared/file.txt"] - Allow access to a specific filepath 41 | - ["*"] - Enable file:// access to app paths 42 | - [] - Disable file access (Default) 43 | """ 44 | 45 | allowed_file_upload_paths: list[str] = [] 46 | """List of local filepaths from which file uploads are permitted. 47 | 48 | Examples: 49 | - ["/home/nova-act/shared/*"] - Allow uploads from specific directory 50 | - ["/home/nova-act/shared/file.txt"] - Allow uploads with specific filepath 51 | - ["*"] - Enable file uploads from all paths 52 | - [] - Disable file uploads (Default) 53 | """ 54 | 55 | @field_validator("allowed_file_open_paths", mode="before") 56 | def set_allowed_file_open_paths_from_deprecated( # type: ignore[explicit-any] 57 | cls, allowed_file_open_paths: list[str], info: ValidationInfo 58 | ) -> list[str]: 59 | # If allow_file_urls=True and allowed_file_open_paths is not set, then 60 | # set allowed_file_open_paths=['*'] to maintain backwards compatibility 61 | 62 | if info.data.get("allow_file_urls", False): 63 | _LOGGER.warning("The 'allow_file_urls' argument has been deprecated. Use 'allowed_file_open_paths' instead") 64 | if len(allowed_file_open_paths) == 0: 65 | return ["*"] 66 | return allowed_file_open_paths 67 | 68 | @field_validator("allowed_file_open_paths", "allowed_file_upload_paths", mode="after") 69 | @classmethod 70 | def validate_file_open_paths(cls, paths: list[str]) -> list[str]: 71 | """Validate that all paths in allowed_file_upload_paths are valid.""" 72 | 73 | # Throws if a path is not valid 74 | validate_allowed_paths(paths) 75 | return paths 76 | 77 | 78 | class PreviewFeatures(TypedDict, total=False): 79 | """Experimental features for opt-in.""" 80 | -------------------------------------------------------------------------------- /src/nova_act/types/errors.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from abc import ABC 15 | 16 | 17 | class NovaActError(Exception, ABC): 18 | """Superclass for all NovaAct client exceptions.""" 19 | 20 | 21 | """ 22 | Wrapper classes for unhandled exceptions 23 | """ 24 | 25 | 26 | class StartFailed(NovaActError): 27 | """Exception raised when the client fails during start() for an otherwise unhandled reason.""" 28 | 29 | 30 | class StopFailed(NovaActError): 31 | """Exception raised when the client fails during stop() for an otherwise unhandled reason.""" 32 | 33 | 34 | class PauseFailed(NovaActError): 35 | """Exception raised when the client fails during stop() for an otherwise unhandled reason.""" 36 | 37 | 38 | class ResumeFailed(NovaActError): 39 | """Exception raised when the client fails during stop() for an otherwise unhandled reason.""" 40 | 41 | 42 | class CancelFailed(NovaActError): 43 | """Exception raised when the client fails during stop() for an otherwise unhandled reason.""" 44 | 45 | 46 | """ 47 | Known Usage Errors 48 | """ 49 | 50 | 51 | class AuthError(NovaActError): 52 | """Indicates there's error with user auth""" 53 | 54 | def __init__(self, message: str): 55 | super().__init__(message) 56 | 57 | 58 | class IAMAuthError(NovaActError): 59 | """Indicates there's an error with IAM credentials""" 60 | 61 | def __init__(self, message: str | None = "IAM authentication failed."): 62 | super().__init__(message) 63 | 64 | 65 | class ValidationFailed(NovaActError, ABC): 66 | """Indicates assumptions violated about how the SDK can be used""" 67 | 68 | 69 | class ClientNotStarted(ValidationFailed): 70 | pass 71 | 72 | 73 | class InvalidPlaywrightState(NovaActError): 74 | pass 75 | 76 | 77 | class InvalidPageState(NovaActError): 78 | pass 79 | 80 | 81 | class UnsupportedOperatingSystem(ValidationFailed): 82 | pass 83 | 84 | 85 | class InvalidInputLength(ValidationFailed): 86 | pass 87 | 88 | 89 | class InvalidScreenResolution(ValidationFailed): 90 | pass 91 | 92 | 93 | class InvalidPath(ValidationFailed): 94 | pass 95 | 96 | 97 | class InvalidURL(ValidationFailed): 98 | pass 99 | 100 | 101 | class InvalidCertificate(ValidationFailed): 102 | pass 103 | 104 | 105 | class InvalidTimeout(ValidationFailed): 106 | pass 107 | 108 | 109 | class InvalidMaxSteps(ValidationFailed): 110 | def __init__(self, num_steps_allowed: int): 111 | super().__init__(f"Please choose a number less than {num_steps_allowed}") 112 | 113 | 114 | class InvalidChromeChannel(ValidationFailed): 115 | pass 116 | 117 | 118 | class PageNotFoundError(ValidationFailed): 119 | pass 120 | 121 | 122 | class InterpreterError(NovaActError): 123 | """Indicates an error encountered while interpreting model output.""" 124 | 125 | 126 | class InvalidTrajectoryReplay(NovaActError): 127 | """Indicates a Trajectory is being replayed on an invalid environment.""" 128 | -------------------------------------------------------------------------------- /src/nova_act/impl/keyboard_event_watcher.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import threading 15 | import time 16 | from types import TracebackType 17 | from typing import Literal, Type 18 | 19 | from nova_act.util.logging import setup_logging 20 | from nova_act.util.terminal_manager import TerminalInputManager 21 | 22 | # Shared event indicating a debugger is attached. 23 | DEBUGGER_ATTACHED_EVENT = threading.Event() 24 | 25 | _LOGGER = setup_logging(__name__) 26 | 27 | 28 | class KeyboardEventWatcher: 29 | """ 30 | Helper class for allowing user keystrokes to be monitored on a non-blocking thread. 31 | 32 | Use as follows: 33 | 34 | with KeyboardEventWatcher("", "") as watcher: 35 | if watcher.is_triggered(): 36 | do_something() 37 | watcher.reset() # if you need to reuse the watcher 38 | """ 39 | 40 | key: str 41 | trigger: threading.Event 42 | watcher_thread: threading.Thread | None 43 | final_stop: bool 44 | terminal_manager: TerminalInputManager 45 | 46 | def __init__(self, key: str, human_readable_key: str, message: str): 47 | self.key = key 48 | self.trigger = threading.Event() 49 | self.final_stop = False 50 | self.watcher_thread = None 51 | 52 | def _watch_for_trigger(self) -> None: 53 | while not self.final_stop: 54 | if DEBUGGER_ATTACHED_EVENT.is_set(): 55 | _LOGGER.warning(f"Detected attached debugger; disabling {type(self).__name__}") 56 | self.final_stop = True 57 | break 58 | 59 | key = self.terminal_manager.get_char(block=False) 60 | if self.key == key: 61 | if self.trigger.is_set(): 62 | continue 63 | self.trigger.set() 64 | 65 | # Short sleep to avoid a tight loop and reduce CPU usage 66 | time.sleep(0.1) 67 | 68 | def __enter__(self) -> "KeyboardEventWatcher": 69 | """Override terminal and start new thread when watcher is entered.""" 70 | self.terminal_manager = TerminalInputManager().__enter__() 71 | 72 | # Start the watcher thread 73 | self.watcher_thread = threading.Thread(target=self._watch_for_trigger, daemon=True) 74 | self.watcher_thread.start() 75 | 76 | return self 77 | 78 | def __exit__( 79 | self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None 80 | ) -> Literal[False]: 81 | """Clean up the watcher thread and reset terminal when exiting the context.""" 82 | if self.terminal_manager: 83 | self.terminal_manager.__exit__(exc_type, exc_val, exc_tb) 84 | 85 | self.final_stop = True 86 | if self.watcher_thread and self.watcher_thread.is_alive(): 87 | self.watcher_thread.join() 88 | return False # Don't suppress any exceptions 89 | 90 | def is_triggered(self) -> bool: 91 | return self.trigger.is_set() 92 | 93 | def reset(self) -> None: 94 | self.trigger.clear() 95 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/styling.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Styling utilities for Nova Act CLI using Click's built-in styling.""" 15 | 16 | import os 17 | from logging import getLogger 18 | 19 | import click 20 | 21 | from nova_act.cli.core.constants import DEFAULT_THEME, THEME_ENV_VAR 22 | from nova_act.cli.core.theme import ThemeName, get_active_theme, set_active_theme 23 | from nova_act.cli.core.user_config_manager import UserConfigManager 24 | 25 | logger = getLogger(__name__) 26 | 27 | 28 | def _initialize_theme() -> None: 29 | """Initialize theme from config or environment.""" 30 | env_theme = os.environ.get(THEME_ENV_VAR) 31 | if env_theme: 32 | try: 33 | theme_name = ThemeName(env_theme) 34 | except ValueError: 35 | logger.warning(f"Invalid theme in environment variable '{env_theme}', falling back to default") 36 | theme_name = ThemeName.DEFAULT 37 | set_active_theme(theme_name) 38 | return 39 | 40 | try: 41 | config = UserConfigManager.get_config() 42 | if not config.theme.enabled: 43 | set_active_theme(ThemeName.NONE) 44 | else: 45 | try: 46 | theme_name = ThemeName(config.theme.name) 47 | except ValueError: 48 | logger.warning(f"Invalid theme in config '{config.theme.name}', falling back to default") 49 | theme_name = ThemeName.DEFAULT 50 | set_active_theme(theme_name) 51 | except Exception: 52 | set_active_theme(DEFAULT_THEME) 53 | 54 | 55 | _initialize_theme() 56 | 57 | 58 | def info(message: str) -> None: 59 | """Print info message.""" 60 | click.echo(get_active_theme().apply_info(message)) 61 | 62 | 63 | def success(message: str) -> None: 64 | """Print success message.""" 65 | click.echo(get_active_theme().apply_success(message)) 66 | 67 | 68 | def warning(message: str) -> None: 69 | """Print warning message.""" 70 | click.echo(get_active_theme().apply_warning(message)) 71 | 72 | 73 | def error(message: str) -> None: 74 | """Print error message.""" 75 | click.echo(get_active_theme().apply_error(message)) 76 | 77 | 78 | def header(text: str) -> str: 79 | """Style text as section header.""" 80 | return get_active_theme().apply_header(text) 81 | 82 | 83 | def value(text: str) -> str: 84 | """Style important values (names, ARNs, etc).""" 85 | return get_active_theme().apply_value(text) 86 | 87 | 88 | def secondary(text: str) -> str: 89 | """Style secondary information (timestamps, paths, etc).""" 90 | return get_active_theme().apply_secondary(text) 91 | 92 | 93 | def command(text: str) -> str: 94 | """Style command examples.""" 95 | return get_active_theme().apply_command(text) 96 | 97 | 98 | def styled_error_exception(message: str) -> click.ClickException: 99 | """Create a ClickException with styled error message.""" 100 | return click.ClickException(get_active_theme().apply_error(message)) 101 | -------------------------------------------------------------------------------- /src/nova_act/impl/backends/factory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from enum import Enum 15 | from typing import Optional, TypeVar, Union 16 | 17 | from nova_act.impl.backends.base import Endpoints 18 | from nova_act.impl.backends.starburst.connector import StarburstBackend 19 | from nova_act.impl.backends.sunburst import SunburstBackend 20 | from nova_act.impl.backends.sunshine import SunshineBackend 21 | from nova_act.types.errors import ( 22 | AuthError, 23 | ) 24 | from nova_act.types.workflow import Workflow 25 | from nova_act.util.logging import create_warning_box, make_trace_logger 26 | 27 | _TRACE_LOGGER = make_trace_logger() 28 | 29 | # TypeVar for Backend that can work with any Endpoints subtype 30 | T = TypeVar("T", bound=Endpoints) 31 | 32 | # Type alias for any concrete backend type that can be returned by the factory 33 | NovaActBackend = Union[ 34 | StarburstBackend, 35 | SunburstBackend, 36 | SunshineBackend, 37 | ] 38 | 39 | 40 | class AuthStrategy(Enum): 41 | """Enumeration of supported authentication strategies.""" 42 | 43 | API_KEY = "api_key" 44 | 45 | 46 | class BackendFactory: 47 | """Factory for creating Backend instances based on parameters.""" 48 | 49 | @staticmethod 50 | def create_backend( 51 | # auth strategies 52 | api_key: str | None = None, 53 | workflow: Workflow | None = None, 54 | ) -> NovaActBackend: 55 | """Create appropriate Backend instance with endpoints selection.""" 56 | 57 | if workflow is not None: 58 | return workflow.backend 59 | 60 | auth_strategy = BackendFactory._determine_auth_strategy( 61 | api_key, 62 | ) 63 | 64 | match auth_strategy: 65 | 66 | case AuthStrategy.API_KEY: 67 | assert api_key is not None # Type narrowing 68 | 69 | return SunburstBackend( 70 | api_key=api_key, 71 | ) 72 | 73 | @staticmethod 74 | def _determine_auth_strategy( 75 | api_key: Optional[str], 76 | ) -> AuthStrategy: 77 | """Validate auth parameters and determine strategy.""" 78 | provided_auths = [ 79 | (api_key is not None, AuthStrategy.API_KEY), 80 | ] 81 | 82 | active_auths = [strategy for is_provided, strategy in provided_auths if is_provided] 83 | 84 | if len(active_auths) == 0: 85 | # We show the default message asking to get API key if no auth strategy provided 86 | _message = create_warning_box( 87 | [ 88 | "Authentication failed.", 89 | "", 90 | "Please ensure you are using a key from: https://nova.amazon.com/dev-apis", 91 | ] 92 | ) 93 | raise AuthError(_message) 94 | elif len(active_auths) > 1: 95 | strategies = [strategy.value for strategy in active_auths] 96 | raise AuthError(f"Only one auth strategy allowed, got: {strategies}") 97 | 98 | strategy = active_auths[0] 99 | 100 | 101 | return strategy 102 | -------------------------------------------------------------------------------- /src/nova_act/util/decode_string.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import re 15 | 16 | from nova_act.util.logging import setup_logging 17 | 18 | _LOGGER = setup_logging(__name__) 19 | 20 | 21 | def safe_string(s: str) -> str: 22 | r""" 23 | Decode a string containing unicode escape sequences while blocking dangerous ANSI sequences. 24 | 25 | This function: 26 | 1. Decodes unicode escape sequences (like \\u00fc for ü and \\ud83d\\ude05 for 😅) 27 | 2. Removes dangerous ANSI escape sequences (like \\x1b]52) 28 | 3. Handles typescript-style path strings with escaped backslashes (\\\\) 29 | """ 30 | # First, handle double-escaped unicode sequences (\\uXXXX -> \uXXXX) 31 | s = s.replace("\\\\u", "\\u") 32 | 33 | # Decode unicode escape sequences using a manual approach 34 | # This handles both regular unicode and surrogate pairs 35 | def decode_unicode_escapes(text: str) -> str: 36 | # Pattern to match \uXXXX sequences 37 | pattern = r"\\u([0-9a-fA-F]{4})" 38 | 39 | def replace_match(match: re.Match[str]) -> str: 40 | code_point = int(match.group(1), 16) 41 | # Check if this is a high surrogate (0xD800-0xDBFF) 42 | if 0xD800 <= code_point <= 0xDBFF: 43 | # This is a high surrogate, we need to find the low surrogate 44 | return chr(code_point) 45 | # Check if this is a low surrogate (0xDC00-0xDFFF) 46 | elif 0xDC00 <= code_point <= 0xDFFF: 47 | return chr(code_point) 48 | else: 49 | # Regular unicode character 50 | return chr(code_point) 51 | 52 | result = re.sub(pattern, replace_match, text) 53 | 54 | # Now handle surrogate pairs by encoding and decoding properly 55 | try: 56 | result = result.encode("utf-16", "surrogatepass").decode("utf-16") 57 | except Exception as e: 58 | _LOGGER.debug(f"Error decoding surrogate pairs in {result}. {type(e).__name__}: {e}") 59 | 60 | return result 61 | 62 | decoded = decode_unicode_escapes(s) 63 | 64 | # Now remove dangerous ANSI escape sequences 65 | # Remove ESC sequences (starting with \x1b) 66 | decoded = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", decoded) # CSI sequences 67 | decoded = re.sub(r"\x1b\][^\x07\x1b]*\x07", "", decoded) # OSC sequences ending with BEL 68 | decoded = re.sub(r"\x1b\][^\x1b]*\x1b\\", "", decoded) # OSC sequences ending with ST 69 | decoded = re.sub(r"\x1b.", "", decoded) # Other ESC sequences (two chars) 70 | 71 | # Remove BEL character 72 | decoded = decoded.replace("\x07", "") 73 | 74 | # Handle escaped backslashes (\\) -> (\) 75 | # This needs to be done carefully to not interfere with other escape sequences 76 | decoded = decoded.replace("\\\\", "\\") 77 | 78 | return decoded 79 | 80 | 81 | def decode_awl_raw_program(awl_raw_program: str) -> str: 82 | """Helper to decode a multi-line AWL program.""" 83 | lines = awl_raw_program.split("\\n") 84 | decoded_lines = [] 85 | for line in lines: 86 | decoded_lines.append(safe_string(line)) 87 | awl_program = "\n".join(decoded_lines) 88 | return awl_program 89 | -------------------------------------------------------------------------------- /src/nova_act/util/terminal_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import os 15 | import sys 16 | from types import TracebackType 17 | from typing import Type 18 | 19 | DEFAULT_TERMINAL_COLS = 80 20 | 21 | if sys.platform == "win32": 22 | import msvcrt 23 | else: 24 | import select 25 | import termios 26 | 27 | 28 | class TerminalInputManager: 29 | """ 30 | Terminal Manager inspired by: https://simondlevy.academic.wlu.edu/files/software/kbhit.py 31 | """ 32 | 33 | fd: int 34 | new_term: list # type: ignore[type-arg] 35 | old_term: list # type: ignore[type-arg] 36 | is_interactive: bool = False 37 | 38 | def __init__(self) -> None: 39 | # Check if running in an interactive terminal 40 | self.is_interactive = sys.stdin.isatty() and os.isatty(sys.stdin.fileno()) 41 | 42 | def __enter__(self) -> "TerminalInputManager": 43 | if not self.is_interactive: 44 | return self 45 | 46 | if sys.platform == "win32": 47 | # No equivalent setup required for Windows. 48 | pass 49 | else: 50 | try: 51 | # Save the terminal settings 52 | self.fd = sys.stdin.fileno() 53 | self.new_term = termios.tcgetattr(self.fd) 54 | self.old_term = termios.tcgetattr(self.fd) 55 | 56 | # New terminal setting unbuffered 57 | self.new_term[3] = self.new_term[3] & ~termios.ICANON & ~termios.ECHO 58 | termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term) 59 | except termios.error: 60 | # Handle case where terminal manipulation fails 61 | self.is_interactive = False 62 | 63 | return self 64 | 65 | def __exit__( 66 | self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None 67 | ) -> None: 68 | if not self.is_interactive: 69 | return 70 | 71 | if sys.platform != "win32": 72 | try: 73 | termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term) 74 | except termios.error: 75 | pass # Ignore errors when restoring terminal settings 76 | 77 | def get_char(self, block: bool) -> str | None: 78 | """Get a single character 79 | 80 | Parameters 81 | ---------- 82 | block: bool 83 | Whether the function should block until a character is received. 84 | 85 | Returns 86 | ------- 87 | str | None 88 | The character received, or None if no character was received and block is False. 89 | """ 90 | if sys.platform == "win32": 91 | if block: 92 | return msvcrt.getch().decode("utf-8") 93 | elif msvcrt.kbhit(): 94 | return msvcrt.getch().decode("utf-8") 95 | else: 96 | if block: 97 | return sys.stdin.read(1) 98 | else: 99 | i, _, _ = select.select([sys.stdin], [], [], 0) 100 | if i != []: 101 | return sys.stdin.read(1) 102 | 103 | return None 104 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/commands/show.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Show command for Nova Act CLI workflows.""" 15 | 16 | import sys 17 | 18 | import click 19 | from boto3 import Session 20 | 21 | from nova_act.cli.core.exceptions import WorkflowError 22 | from nova_act.cli.core.identity import auto_detect_account_id 23 | from nova_act.cli.core.region import get_default_region 24 | from nova_act.cli.core.styling import command, header, secondary, styled_error_exception, value 25 | from nova_act.cli.core.types import WorkflowInfo 26 | from nova_act.cli.workflow.utils.arn import extract_workflow_definition_name_from_arn 27 | from nova_act.cli.workflow.utils.console import build_nova_act_workflow_console_url 28 | from nova_act.cli.workflow.workflow_manager import WorkflowManager 29 | 30 | 31 | def _display_workflow_info(workflow_info: WorkflowInfo, region: str) -> None: 32 | """Display all WorkflowInfo fields in key-value format.""" 33 | click.echo(header("Workflow Details")) 34 | click.echo(f"{secondary('Name:')} {value(workflow_info.name)}") 35 | 36 | # Show directory path directly without existence check 37 | if workflow_info.directory_path and workflow_info.directory_path.strip(): 38 | click.echo(f"{secondary('Directory:')} {value(workflow_info.directory_path)}") 39 | 40 | if workflow_info.created_at: 41 | click.echo(f"{secondary('Created:')} {value(workflow_info.created_at.strftime('%Y-%m-%d %H:%M:%S'))}") 42 | 43 | if workflow_info.workflow_definition_arn: 44 | click.echo(f"{secondary('Workflow Definition ARN:')} {value(workflow_info.workflow_definition_arn)}") 45 | workflow_name = extract_workflow_definition_name_from_arn(workflow_info.workflow_definition_arn) 46 | console_url = build_nova_act_workflow_console_url(region, workflow_name) 47 | click.echo(f"{secondary('Console URL:')} {value(console_url)}") 48 | 49 | if workflow_info.deployments.agentcore: 50 | deployment = workflow_info.deployments.agentcore 51 | click.echo(f"{secondary('AgentCore Deployment:')} {value(deployment.deployment_arn)}") 52 | click.echo(f"{secondary('Container Image:')} {value(deployment.image_uri)}") 53 | 54 | 55 | @click.command() 56 | @click.option("--name", "-n", required=True, help="Name of the workflow to show") 57 | @click.option("--region", help="AWS region to query") 58 | def show(name: str, region: str | None = None) -> None: 59 | """Show detailed information about a workflow.""" 60 | try: 61 | # Create session at command boundary 62 | session = Session() 63 | 64 | effective_region = region or get_default_region() 65 | account_id = auto_detect_account_id(session=session, region=effective_region) 66 | workflow_manager = WorkflowManager(session=session, region=effective_region, account_id=account_id) 67 | workflow_info = workflow_manager.get_workflow(name) 68 | _display_workflow_info(workflow_info, effective_region) 69 | click.echo() 70 | 71 | except WorkflowError: 72 | click.echo( 73 | f"Workflow '{value(name)}' not found. Use {command('act workflow list')} to see available workflows." 74 | ) 75 | sys.exit(1) 76 | except Exception as e: 77 | raise styled_error_exception(message=f"Unexpected error: {str(e)}") from e 78 | -------------------------------------------------------------------------------- /src/nova_act/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import builtins 15 | import pdb 16 | import sys 17 | 18 | from strands import tool 19 | 20 | from nova_act.impl.common import rsync_from_default_user_data 21 | from nova_act.impl.extension import ExtensionActuator 22 | from nova_act.tools.human.interface.human_input_callback import ( 23 | HumanInputCallbacksProvider, 24 | ) 25 | 26 | 27 | from nova_act.nova_act import NovaAct 28 | from nova_act.tools.browser.default.default_nova_local_browser_actuator import DefaultNovaLocalBrowserActuator 29 | from nova_act.tools.browser.interface.browser import BrowserActuatorBase 30 | from nova_act.tools.browser.interface.playwright_pages import PlaywrightPageManagerBase 31 | from nova_act.types.act_errors import ( 32 | ActActuationError, 33 | ActAgentError, 34 | ActAgentFailed, 35 | ActCanceledError, 36 | ActClientError, 37 | ActDispatchError, 38 | ActError, 39 | ActExceededMaxStepsError, 40 | ActExecutionError, 41 | ActGuardrailsError, 42 | ActInternalServerError, 43 | ActInvalidModelGenerationError, 44 | ActModelError, 45 | ActProtocolError, 46 | ActRateLimitExceededError, 47 | ActServerError, 48 | ActStateGuardrailError, 49 | ActTimeoutError, 50 | ) 51 | from nova_act.types.act_metadata import ActMetadata 52 | from nova_act.types.act_result import ActGetResult, ActResult 53 | from nova_act.types.errors import NovaActError, StartFailed, StopFailed, ValidationFailed 54 | from nova_act.types.features import SecurityOptions 55 | from nova_act.types.guardrail import GuardrailDecision, GuardrailInputState 56 | from nova_act.types.json_type import JSONType 57 | from nova_act.types.workflow import Workflow, get_current_workflow, workflow 58 | from nova_act.util.jsonschema import BOOL_SCHEMA, STRING_SCHEMA 59 | from nova_act.util.logging import setup_logging 60 | 61 | __all__ = [ 62 | "NovaAct", 63 | "ActAgentError", 64 | "ActAgentFailed", 65 | "ActExecutionError", 66 | "ActActuationError", 67 | "ActCanceledError", 68 | "ActClientError", 69 | "ActDispatchError", 70 | "ActError", 71 | "ActExceededMaxStepsError", 72 | "ActGuardrailsError", 73 | "ActInternalServerError", 74 | "ActInvalidModelGenerationError", 75 | "ActModelError", 76 | "ActRateLimitExceededError", 77 | "ActServerError", 78 | "ActTimeoutError", 79 | "ActMetadata", 80 | "ActResult", 81 | "ActGetResult", 82 | "ActStateGuardrailError", 83 | "NovaActError", 84 | "SecurityOptions", 85 | "StartFailed", 86 | "StopFailed", 87 | "ValidationFailed", 88 | "BOOL_SCHEMA", 89 | "STRING_SCHEMA", 90 | "BrowserActuatorBase", 91 | "ExtensionActuator", 92 | "DefaultNovaLocalBrowserActuator", 93 | "JSONType", 94 | "rsync_from_default_user_data", 95 | "GuardrailDecision", 96 | "GuardrailInputState", 97 | "HumanInputCallbacksProvider", 98 | "tool", 99 | "Workflow", 100 | "get_current_workflow", 101 | "workflow", 102 | ] 103 | 104 | 105 | # Intercept `builtins.breakpoint` to disable KeyboardEventWatcher 106 | from nova_act.impl.keyboard_event_watcher import DEBUGGER_ATTACHED_EVENT 107 | 108 | _LOGGER = setup_logging(__name__) 109 | 110 | 111 | def set_trace_and_signal_event(*args, **kwargs): # type: ignore[no-untyped-def] 112 | _LOGGER.info("Intercepted breakpoint call. Signaling threads.") 113 | DEBUGGER_ATTACHED_EVENT.set() 114 | pdb.Pdb().set_trace(sys._getframe(1)) 115 | 116 | 117 | builtins.breakpoint = set_trace_and_signal_event 118 | -------------------------------------------------------------------------------- /src/nova_act/types/act_metadata.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import dataclasses 15 | from datetime import datetime 16 | from typing import Dict 17 | 18 | 19 | @dataclasses.dataclass(frozen=True) 20 | class ActMetadata: 21 | session_id: str 22 | act_id: str 23 | num_steps_executed: int 24 | start_time: float | None 25 | end_time: float | None 26 | prompt: str 27 | step_server_times_s: list[float] = dataclasses.field(default_factory=list) 28 | time_worked_s: float | None = None 29 | human_wait_time_s: float = 0.0 30 | 31 | def __repr__(self) -> str: 32 | local_tz = datetime.now().astimezone().tzinfo 33 | 34 | # Convert Unix timestamps to readable format if they exist 35 | start_time_str = ( 36 | datetime.fromtimestamp(self.start_time, tz=local_tz).strftime("%Y-%m-%d %H:%M:%S.%f %Z") 37 | if self.start_time is not None 38 | else "None" 39 | ) 40 | end_time_str = ( 41 | datetime.fromtimestamp(self.end_time, tz=local_tz).strftime("%Y-%m-%d %H:%M:%S.%f %Z") 42 | if self.end_time is not None 43 | else "None" 44 | ) 45 | 46 | step_times_line = "" 47 | if self.step_server_times_s and any(t != 0 for t in self.step_server_times_s): 48 | formatted_times = [f"{t:.3f}" for t in self.step_server_times_s] 49 | step_times_line = f" step_server_times_s = {formatted_times}\n" 50 | 51 | time_worked_line = "" 52 | if self.time_worked_s is not None: 53 | try: 54 | time_worked_str = _format_duration(self.time_worked_s) 55 | if self.human_wait_time_s > 0: 56 | human_wait_str = _format_duration(self.human_wait_time_s) 57 | time_worked_line = ( 58 | f" time_worked = {time_worked_str} " f"(excluding {human_wait_str} human wait)\n" 59 | ) 60 | else: 61 | time_worked_line = f" time_worked = {time_worked_str}\n" 62 | except (TypeError, AttributeError): 63 | # Handle cases where time values are mocks or invalid types 64 | time_worked_line = f" time_worked = {self.time_worked_s}\n" 65 | 66 | return ( 67 | f"ActMetadata(\n" 68 | f" session_id = {self.session_id}\n" 69 | f" act_id = {self.act_id}\n" 70 | f" num_steps_executed = {self.num_steps_executed}\n" 71 | f" start_time = {start_time_str}\n" 72 | f" end_time = {end_time_str}\n" 73 | f"{step_times_line}" 74 | f"{time_worked_line}" 75 | f" prompt = '{self.prompt}'\n" 76 | f")" 77 | ) 78 | 79 | 80 | def _format_duration(seconds: float) -> str: 81 | """Format duration in seconds to human-readable string. 82 | 83 | Examples: 84 | 45.234 -> "45.2s" 85 | 154.567 -> "2m 34.6s" 86 | 3725.123 -> "1h 2m 5.1s" 87 | 88 | Args: 89 | seconds: Duration in seconds 90 | 91 | Returns: 92 | Human-readable duration string 93 | """ 94 | if seconds < 60: 95 | return f"{seconds:.1f}s" 96 | 97 | # Use divmod for efficient calculation using division and modulo 98 | total_minutes, remaining_seconds = divmod(seconds, 60) 99 | total_minutes = int(total_minutes) 100 | 101 | if total_minutes < 60: 102 | return f"{total_minutes}m {remaining_seconds:.1f}s" 103 | 104 | hours, minutes = divmod(total_minutes, 60) 105 | return f"{hours}h {minutes}m {remaining_seconds:.1f}s" 106 | -------------------------------------------------------------------------------- /src/nova_act/cli/workflow/commands/update.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Update command for Nova Act CLI workflows.""" 15 | 16 | import click 17 | from boto3 import Session 18 | 19 | from nova_act.cli.core.identity import auto_detect_account_id 20 | from nova_act.cli.core.region import get_default_region 21 | from nova_act.cli.core.styling import command, secondary, styled_error_exception, success, value 22 | from nova_act.cli.workflow.utils.arn import ( 23 | extract_workflow_definition_name_from_arn, 24 | validate_workflow_definition_arn, 25 | ) 26 | from nova_act.cli.workflow.utils.console import build_nova_act_workflow_console_url 27 | from nova_act.cli.workflow.workflow_manager import WorkflowManager 28 | 29 | 30 | def _validate_workflow_exists(name: str, region: str, workflow_manager: WorkflowManager) -> str: 31 | """Validate workflow exists and return current ARN for comparison.""" 32 | try: 33 | workflow = workflow_manager.get_workflow(name) 34 | return workflow.workflow_definition_arn or "Not set" 35 | except Exception as e: 36 | raise styled_error_exception( 37 | f"Workflow '{name}' not found in configuration.\n" f"Use 'act workflow list' to see available workflows." 38 | ) from e 39 | 40 | 41 | def _validate_arn_format(arn: str) -> None: 42 | """Validate ARN format and structure.""" 43 | try: 44 | validate_workflow_definition_arn(arn) 45 | except ValueError as e: 46 | raise styled_error_exception(f"Invalid WorkflowDefinition ARN: {str(e)}\n") 47 | 48 | 49 | def _display_update_success(name: str, region: str, old_arn: str, new_arn: str) -> None: 50 | """Display workflow update success with before/after comparison.""" 51 | click.echo(f"Updated workflow '{value(name)}' WorkflowDefinition ARN in region '{value(region)}':") 52 | click.echo(f" {secondary(text='Old:')} {secondary(text=old_arn)}") 53 | click.echo(f" {secondary(text='New:')} {value(text=new_arn)}") 54 | workflow_name = extract_workflow_definition_name_from_arn(new_arn) 55 | console_url = build_nova_act_workflow_console_url(region, workflow_name) 56 | click.echo(f" {secondary(text='Console URL:')} {value(text=console_url)}") 57 | click.echo() 58 | success(message=f"✅ Workflow '{name}' updated successfully!") 59 | click.echo(f"Use {command(text=f'act workflow show --name {name}')} for full details.") 60 | click.echo() 61 | 62 | 63 | @click.command() 64 | @click.option("--name", "-n", required=True, help="Name of the workflow to update") 65 | @click.option( 66 | "--workflow-definition-arn", required=True, help="New WorkflowDefinition ARN to associate with the workflow" 67 | ) 68 | @click.option("--region", help="Region for WorkflowDefinition (defaults to config default_region)") 69 | def update(name: str, workflow_definition_arn: str, region: str | None = None) -> None: 70 | """Update an existing workflow's WorkflowDefinition ARN for a specific region.""" 71 | # Create session at command boundary 72 | session = Session() 73 | 74 | effective_region = region or get_default_region() 75 | account_id = auto_detect_account_id(session=session, region=effective_region) 76 | workflow_manager = WorkflowManager(session=session, region=effective_region, account_id=account_id) 77 | 78 | # Validate inputs 79 | old_arn = _validate_workflow_exists(name=name, region=effective_region, workflow_manager=workflow_manager) 80 | _validate_arn_format(arn=workflow_definition_arn) 81 | 82 | # Perform update 83 | workflow_manager.update_workflow_definition_arn(name=name, workflow_definition_arn=workflow_definition_arn) 84 | _display_update_success(name=name, region=effective_region, old_arn=old_arn, new_arn=workflow_definition_arn) 85 | -------------------------------------------------------------------------------- /src/nova_act/impl/telemetry.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import platform 15 | import sys 16 | from typing import Literal 17 | 18 | import requests 19 | 20 | from nova_act.__version__ import VERSION 21 | from nova_act.types.act_result import ActGetResult 22 | from nova_act.types.errors import NovaActError 23 | from nova_act.types.state.act import Act 24 | from nova_act.util.logging import setup_logging 25 | 26 | _LOGGER = setup_logging(__name__) 27 | 28 | 29 | def send_act_telemetry( 30 | endpoint: str, 31 | nova_act_api_key: str | None, 32 | act: Act, 33 | success: ActGetResult | None, 34 | error: NovaActError | None, 35 | ) -> None: 36 | """Send telemetry for the given act.""" 37 | 38 | if not nova_act_api_key: 39 | return 40 | 41 | headers = { 42 | "Authorization": f"ApiKey {nova_act_api_key}", 43 | "Content-Type": "application/json", 44 | "X-Api-Key": f"{nova_act_api_key}", 45 | } 46 | 47 | latency = -1.0 48 | if act.end_time is not None: 49 | latency = act.end_time - act.start_time 50 | 51 | if error: 52 | result = { 53 | "result_type": "ERROR", 54 | "result_error": { 55 | "type": error.__class__.__name__, 56 | "message": error.message if hasattr(error, "message") and error.message else "", 57 | }, 58 | } 59 | elif success: 60 | result = { 61 | "result_type": "SUCCESS", 62 | "result_success": {"response": success.response if success.response else ""}, 63 | } 64 | else: 65 | return 66 | 67 | payload = { 68 | "act": { 69 | "actId": act.id, 70 | "latency": latency, 71 | "sessionId": act.session_id, 72 | **result, 73 | }, 74 | "type": "ACT", 75 | } 76 | 77 | try: 78 | response = requests.post(endpoint + "/telemetry", json=payload, headers=headers) 79 | if response.status_code != 200: 80 | _LOGGER.debug("Failed to send act telemetry: %s", response.text) 81 | except Exception as e: 82 | # Swallow any exceptions 83 | _LOGGER.debug("Error sending act telemetry: %s", e) 84 | 85 | 86 | def send_environment_telemetry( 87 | endpoint: str, 88 | nova_act_api_key: str | None, 89 | session_id: str, 90 | actuator_type: Literal["custom", "playwright"], 91 | ) -> None: 92 | """Send environment telemetry""" 93 | 94 | if not nova_act_api_key: 95 | return 96 | 97 | headers = { 98 | "Authorization": f"ApiKey {nova_act_api_key}", 99 | "Content-Type": "application/json", 100 | "X-Api-Key": f"{nova_act_api_key}", 101 | } 102 | 103 | python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" 104 | 105 | system_name = platform.system().lower() or "unknown" 106 | system_release = platform.release().lower() or "unknown" 107 | system = f"{system_name}/{system_release}" 108 | 109 | payload = { 110 | "environment": { 111 | "actuatorType": actuator_type, 112 | "pythonVersion": python_version, 113 | "sessionId": session_id, 114 | "sdkVersion": VERSION, 115 | "system": system, 116 | }, 117 | "type": "ENVIRONMENT", 118 | } 119 | 120 | try: 121 | response = requests.post(endpoint + "/telemetry", json=payload, headers=headers) 122 | if response.status_code != 200: 123 | _LOGGER.debug("Failed to send environment telemetry: %s", response.text) 124 | except Exception as e: 125 | # Swallow any exceptions 126 | _LOGGER.debug("Error sending environment telemetry: %s", e) 127 | -------------------------------------------------------------------------------- /src/nova_act/util/error_messages.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Error message constants for NovaAct SDK. 15 | 16 | This module contains reusable error messages displayed to users when specific 17 | conditions are encountered. Messages are formatted as boxed warnings for better visibility. 18 | 19 | """ 20 | 21 | from nova_act.util.constants import NOVA_ACT_AWS_SERVICE, NOVA_ACT_FREE_VERSION 22 | from nova_act.util.logging import create_warning_box 23 | 24 | 25 | def get_api_key_error_message_for_workflow() -> str: 26 | """Get the error message for ambiguous authentication when API key is found in environment. 27 | 28 | Returns: 29 | A formatted error message string. 30 | """ 31 | return create_warning_box( 32 | [ 33 | "Ambiguous Authentication Failure", 34 | "Found API Key environment variable (NOVA_ACT_API_KEY) while using Workflow context.", 35 | "", 36 | f"To use {NOVA_ACT_FREE_VERSION}: set the API key explicitly using", 37 | ' Workflow(api_key="", ...) or @workflow(api_key="", ...)', 38 | "", 39 | f"To use {NOVA_ACT_AWS_SERVICE}: unset the API key environment variable:", 40 | " os.environ.pop('NOVA_ACT_API_KEY', None)", 41 | ] 42 | ) 43 | 44 | 45 | def get_no_authentication_error() -> str: 46 | """Get the error message when no authentication credentials are found. 47 | 48 | Returns: 49 | A formatted error message string. 50 | """ 51 | return create_warning_box( 52 | [ 53 | "Authentication Failed", 54 | "", 55 | "The NovaAct SDK supports two forms of authentication:", 56 | f"1. API Key for {NOVA_ACT_FREE_VERSION}, which can be obtained at", 57 | " https://nova.amazon.com/act", 58 | f"2. AWS Authentication for {NOVA_ACT_AWS_SERVICE}, which uses standard", 59 | " boto Session credentials", 60 | "", 61 | "Please configure one or the other in order to run your workflow.", 62 | ] 63 | ) 64 | 65 | 66 | def get_missing_workflow_definition_error() -> str: 67 | """Get the error message when AWS credentials are set but NOVA_ACT_API_KEY is not and 68 | user is trying to run a non-workflow example. 69 | 70 | Returns: 71 | A formatted error message string. 72 | """ 73 | return create_warning_box( 74 | [ 75 | "Authentication Failed With Invalid Credentials Configuration", 76 | "", 77 | "There are two options for authenticating with Nova Act:", 78 | f"(1) {NOVA_ACT_FREE_VERSION} with API keys or (2) {NOVA_ACT_AWS_SERVICE} with AWS credentials.", 79 | "", 80 | f"To use (1) {NOVA_ACT_FREE_VERSION}, set the NOVA_ACT_API_KEY environment variable", 81 | 'or pass in explicitly using NovaAct(nova_act_api_key="", ...)', 82 | "To generate an API Key go to https://nova.amazon.com/act?tab=dev_tools", 83 | "", 84 | f"To use (2) {NOVA_ACT_AWS_SERVICE}, you must use a Workflow construct. For example:", 85 | "", 86 | '@workflow(workflow_definition_name="", model_id="nova-act-latest")', 87 | "def explore_destinations():", 88 | ' with NovaAct(starting_page="https://nova.amazon.com/act/gym/next-dot/search") as nova:', 89 | ' nova.act("Find flights from Boston to Wolf on Feb 22nd")', 90 | "", 91 | "To create a workflow definition name, use the Nova Act CLI or go to", 92 | "https://docs.aws.amazon.com/nova-act/latest/userguide/step-2-develop-locally.html#develop-with-aws-iam", 93 | "", 94 | "Please configure one or the other in order to run your workflow.", 95 | ] 96 | ) 97 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/agentcore/types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Pydantic types for AgentCore client operations.""" 15 | 16 | from typing import Dict, List 17 | 18 | from pydantic import BaseModel 19 | 20 | 21 | class ContainerConfiguration(BaseModel): 22 | """Container configuration for agent runtime.""" 23 | 24 | model_config = {"extra": "allow"} 25 | 26 | containerUri: str 27 | 28 | 29 | class AgentRuntimeArtifact(BaseModel): 30 | """Agent runtime artifact configuration.""" 31 | 32 | model_config = {"extra": "allow"} 33 | 34 | containerConfiguration: ContainerConfiguration 35 | 36 | 37 | class AgentRuntimeConfig(BaseModel): 38 | """Configuration for AgentCore Runtime.""" 39 | 40 | model_config = {"extra": "allow"} 41 | 42 | container_uri: str 43 | role_arn: str 44 | environment_variables: Dict[str, str] | None = None 45 | tags: Dict[str, str] | None = None 46 | 47 | 48 | class AgentRuntimeResponse(BaseModel): 49 | """Response from AgentCore Runtime operations.""" 50 | 51 | model_config = {"extra": "allow"} 52 | 53 | agentRuntimeArn: str 54 | 55 | 56 | # Control Client Request Types 57 | class CreateAgentRuntimeRequest(BaseModel): 58 | """Request for creating agent runtime.""" 59 | 60 | model_config = {"extra": "allow"} 61 | 62 | agentRuntimeName: str 63 | agentRuntimeArtifact: AgentRuntimeArtifact 64 | roleArn: str 65 | networkConfiguration: Dict[str, str] 66 | environmentVariables: Dict[str, str] | None = None 67 | tags: Dict[str, str] | None = None 68 | 69 | 70 | class UpdateAgentRuntimeRequest(BaseModel): 71 | """Request for updating agent runtime.""" 72 | 73 | model_config = {"extra": "allow"} 74 | 75 | agentRuntimeId: str 76 | agentRuntimeArtifact: AgentRuntimeArtifact 77 | roleArn: str 78 | networkConfiguration: Dict[str, str] 79 | environmentVariables: Dict[str, str] | None = None 80 | 81 | 82 | class ListAgentRuntimesRequest(BaseModel): 83 | """Request for listing agent runtimes.""" 84 | 85 | model_config = {"extra": "allow"} 86 | 87 | nextToken: str | None = None 88 | 89 | 90 | # Control Client Response Types 91 | class CreateAgentRuntimeResponse(BaseModel): 92 | """Response from creating agent runtime.""" 93 | 94 | model_config = {"extra": "allow"} 95 | 96 | agentRuntimeArn: str 97 | 98 | 99 | class UpdateAgentRuntimeResponse(BaseModel): 100 | """Response from updating agent runtime.""" 101 | 102 | model_config = {"extra": "allow"} 103 | 104 | agentRuntimeArn: str 105 | 106 | 107 | class AgentRuntimeSummary(BaseModel): 108 | """Summary of agent runtime in list response.""" 109 | 110 | model_config = {"extra": "allow"} 111 | 112 | agentRuntimeId: str 113 | agentRuntimeName: str 114 | agentRuntimeArn: str 115 | 116 | 117 | class ListAgentRuntimesResponse(BaseModel): 118 | """Response from listing agent runtimes.""" 119 | 120 | model_config = {"extra": "allow"} 121 | 122 | agentRuntimes: List[AgentRuntimeSummary] 123 | nextToken: str | None = None 124 | 125 | 126 | # Data Client Request Types 127 | class InvokeAgentRuntimeRequest(BaseModel): 128 | """Request for invoking agent runtime.""" 129 | 130 | model_config = {"extra": "allow"} 131 | 132 | agentRuntimeArn: str 133 | runtimeSessionId: str 134 | payload: str 135 | 136 | 137 | # Data Client Response Types 138 | class InvokeAgentRuntimeResponse(BaseModel): 139 | """Response from invoking agent runtime. 140 | 141 | Matches the structure expected by response_parser.parse_invoke_response(). 142 | """ 143 | 144 | model_config = {"extra": "allow"} 145 | 146 | response: Dict[str, object] 147 | contentType: str | None = None # MIME type for response parsing 148 | -------------------------------------------------------------------------------- /src/nova_act/impl/backends/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Common utilities shared across backend implementations.""" 15 | 16 | import json 17 | 18 | from requests import Response 19 | from typing_extensions import Any 20 | 21 | from nova_act.tools.browser.interface.browser import BrowserObservation 22 | from nova_act.types.act_errors import ActBadRequestError, ActBadResponseError, ActInternalServerError 23 | from nova_act.types.api.step import AgentRunCreate, StepPlanRequest 24 | from nova_act.types.state.act import Act 25 | 26 | # Request timeout constants 27 | DEFAULT_REQUEST_CONNECT_TIMEOUT = 30 # 30s 28 | DEFAULT_REQUEST_READ_TIMEOUT = 5 * 60 # 5min 29 | 30 | 31 | def assert_json_response( # type: ignore[explicit-any] 32 | response: Response, request_id: str | None = None 33 | ) -> dict[str, Any]: 34 | """Assert that a response contains valid JSON.""" 35 | status_code = response.status_code 36 | message = f"Received Invalid JSON response from {response.url}" 37 | raw_response = response.text 38 | 39 | try: 40 | response = response.json() 41 | except json.JSONDecodeError: 42 | # If we receive invalid JSON, we should still indicate if it's a 43 | # BadResponse, BadRequest, or InternalServerError. 44 | 45 | if status_code < 400: 46 | raise ActBadResponseError( 47 | request_id=request_id, 48 | status_code=status_code, 49 | message=message, 50 | raw_response=raw_response, 51 | ) 52 | elif 400 <= status_code < 500: 53 | raise ActBadRequestError( 54 | request_id=request_id, 55 | status_code=status_code, 56 | message=message, 57 | raw_response=raw_response, 58 | ) 59 | else: 60 | raise ActInternalServerError( 61 | request_id=request_id, 62 | status_code=status_code, 63 | message=message, 64 | raw_response=raw_response, 65 | ) 66 | 67 | if not isinstance(response, dict): 68 | raise ActBadResponseError( 69 | request_id=request_id, 70 | status_code=status_code, 71 | message=message, 72 | raw_response=raw_response, 73 | ) 74 | 75 | return response 76 | 77 | 78 | def construct_step_plan_request( 79 | act: Act, observation: BrowserObservation, error_executing_previous_step: Exception | None 80 | ) -> StepPlanRequest: 81 | """Construct a StepPlanRequest from an Act, Observation, and optional Exception.""" 82 | 83 | plan_request: StepPlanRequest = { 84 | "agentRunId": act.id, 85 | "idToBboxMap": observation.get("idToBboxMap", {}), 86 | "observation": { 87 | "activeURL": observation["activeURL"], 88 | "browserDimensions": observation["browserDimensions"], 89 | "idToBboxMap": observation["idToBboxMap"], 90 | "simplifiedDOM": observation["simplifiedDOM"], 91 | "timestamp_ms": observation["timestamp_ms"], 92 | "userAgent": observation["userAgent"], 93 | }, 94 | "screenshotBase64": observation["screenshotBase64"], 95 | "tempReturnPlanResponse": True, 96 | } 97 | 98 | if error_executing_previous_step is not None: 99 | plan_request["errorExecutingPreviousStep"] = ( 100 | f"{type(error_executing_previous_step).__name__}: {str(error_executing_previous_step)}" 101 | ) 102 | 103 | # If this is the first step, create an agent run 104 | if not act.steps: 105 | agent_run_create: AgentRunCreate = { 106 | "agentConfigName": "plan-v2", 107 | "id": act.id, 108 | "plannerFunctionArgs": {"task": act.prompt}, 109 | "plannerFunctionName": "act", 110 | "task": act.prompt, 111 | } 112 | 113 | plan_request["agentRunCreate"] = agent_run_create 114 | 115 | return plan_request 116 | -------------------------------------------------------------------------------- /src/nova_act/cli/core/clients/agentcore/response_parser.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Amazon Inc 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Response parser utility for handling AWS Bedrock Agent Runtime responses. 15 | 16 | This module provides utilities to properly parse StreamingBody responses from 17 | invoke_agent_runtime calls, handling different content types according to AWS specification. 18 | """ 19 | 20 | import json 21 | from typing import Dict, Union 22 | 23 | from botocore.response import StreamingBody 24 | 25 | 26 | def parse_invoke_response(response: Dict[str, Union[str, StreamingBody]]) -> Dict[str, str]: 27 | """Parse invoke_agent_runtime response handling StreamingBody properly. 28 | 29 | Args: 30 | response: Raw response from boto3 invoke_agent_runtime call 31 | 32 | Returns: 33 | Dict with parsed response content maintaining backward compatibility 34 | """ 35 | if not _is_valid_response(response): 36 | return {"response": str(response)} 37 | 38 | streaming_body = response.get("response") 39 | if not isinstance(streaming_body, StreamingBody): 40 | response_value = response.get("response", "") 41 | return {"response": "" if response_value is None else str(response_value)} 42 | 43 | content_type_raw = response.get("contentType", "") 44 | content_type = str(content_type_raw) if content_type_raw else "" 45 | parsed_content = _handle_streaming_body(streaming_body=streaming_body, content_type=content_type) 46 | 47 | return {"response": parsed_content} 48 | 49 | 50 | def _is_valid_response(response: Union[Dict[str, Union[str, StreamingBody]], str, None]) -> bool: 51 | """Validate response is a dictionary.""" 52 | return isinstance(response, dict) 53 | 54 | 55 | def _handle_streaming_body(streaming_body: StreamingBody, content_type: str) -> str: 56 | """Read content from StreamingBody based on content type. 57 | 58 | Args: 59 | streaming_body: The StreamingBody object from AWS response 60 | content_type: MIME type from response headers 61 | 62 | Returns: 63 | Parsed content as string 64 | """ 65 | try: 66 | if "text/event-stream" in content_type: 67 | return _parse_event_stream(streaming_body) 68 | elif "application/json" in content_type: 69 | return _parse_json_response(streaming_body) 70 | else: 71 | # Fallback: read as raw text 72 | return streaming_body.read().decode("utf-8") 73 | except Exception as e: 74 | return f"Error parsing response: {str(e)}" 75 | 76 | 77 | def _parse_event_stream(streaming_body: StreamingBody) -> str: 78 | """Handle text/event-stream responses. 79 | 80 | Args: 81 | streaming_body: StreamingBody containing event stream data 82 | 83 | Returns: 84 | Concatenated event stream content 85 | """ 86 | content_lines = [] 87 | 88 | for line in streaming_body.iter_lines(chunk_size=10): 89 | if line: 90 | processed_line = _process_event_line(line) 91 | if processed_line: 92 | content_lines.append(processed_line) 93 | 94 | return "\n".join(content_lines) 95 | 96 | 97 | def _process_event_line(line: bytes) -> str: 98 | """Process a single event stream line.""" 99 | line_str = line.decode("utf-8") 100 | if line_str.startswith("data: "): 101 | data_content = line_str[6:] # Remove "data: " prefix 102 | return data_content if data_content.strip() else "" 103 | return "" 104 | 105 | 106 | def _parse_json_response(streaming_body: StreamingBody) -> str: 107 | """Handle application/json responses. 108 | 109 | Args: 110 | streaming_body: StreamingBody containing JSON data 111 | 112 | Returns: 113 | JSON content as formatted string 114 | """ 115 | content_bytes = streaming_body.read() 116 | content_str = content_bytes.decode("utf-8") 117 | 118 | # Parse and re-format JSON for consistent output 119 | parsed_json = json.loads(content_str) 120 | return json.dumps(parsed_json, indent=2) 121 | --------------------------------------------------------------------------------