agent-starter-pack 0.3.3__py3-none-any.whl → 0.21.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_starter_pack/agents/README.md +7 -0
- agents/langgraph_base_react/template/.templateconfig.yaml → agent_starter_pack/agents/adk_a2a_base/.template/templateconfig.yaml +5 -10
- agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
- src/deployment_targets/cloud_run/Dockerfile → agent_starter_pack/agents/adk_a2a_base/app/__init__.py +2 -14
- agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
- agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
- agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → agent_starter_pack/agents/adk_a2a_base/notebooks/evaluating_adk_agent.ipynb +163 -199
- {agents/adk_base → agent_starter_pack/agents/adk_a2a_base}/tests/integration/test_agent.py +2 -2
- agents/adk_base/template/.templateconfig.yaml → agent_starter_pack/agents/adk_base/.template/templateconfig.yaml +4 -1
- {agents → agent_starter_pack/agents}/adk_base/README.md +1 -1
- agent_starter_pack/agents/adk_base/app/__init__.py +17 -0
- {agents → agent_starter_pack/agents}/adk_base/app/agent.py +5 -2
- {agents → agent_starter_pack/agents}/adk_base/notebooks/adk_app_testing.ipynb +128 -82
- agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb → agent_starter_pack/agents/adk_base/notebooks/evaluating_adk_agent.ipynb +181 -207
- agent_starter_pack/agents/adk_base/tests/integration/test_agent.py +58 -0
- agents/crewai_coding_crew/template/.templateconfig.yaml → agent_starter_pack/agents/adk_live/.template/templateconfig.yaml +5 -9
- agent_starter_pack/agents/adk_live/README.md +32 -0
- agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
- agent_starter_pack/agents/adk_live/app/agent.py +51 -0
- agent_starter_pack/agents/adk_live/tests/unit/test_dummy.py +38 -0
- agents/agentic_rag/template/.templateconfig.yaml → agent_starter_pack/agents/agentic_rag/.template/templateconfig.yaml +7 -3
- {agents → agent_starter_pack/agents}/agentic_rag/README.md +1 -1
- agent_starter_pack/agents/agentic_rag/app/__init__.py +17 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/agent.py +8 -4
- {agents → agent_starter_pack/agents}/agentic_rag/notebooks/adk_app_testing.ipynb +128 -82
- agent_starter_pack/agents/agentic_rag/notebooks/evaluating_adk_agent.ipynb +1535 -0
- {agents → agent_starter_pack/agents}/agentic_rag/tests/integration/test_agent.py +3 -3
- agent_starter_pack/agents/langgraph_base/.template/templateconfig.yaml +31 -0
- agent_starter_pack/agents/langgraph_base/README.md +30 -0
- agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
- agent_starter_pack/agents/langgraph_base/app/agent.py +34 -0
- {agents/crewai_coding_crew → agent_starter_pack/agents/langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +30 -17
- {agents/langgraph_base_react → agent_starter_pack/agents/langgraph_base}/tests/integration/test_agent.py +2 -2
- {src → agent_starter_pack}/base_template/.gitignore +5 -3
- agent_starter_pack/base_template/GEMINI.md +5 -0
- agent_starter_pack/base_template/Makefile +339 -0
- agent_starter_pack/base_template/README.md +267 -0
- agent_starter_pack/base_template/deployment/README.md +11 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/apis.tf +2 -2
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/apis.tf +6 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/iam.tf +12 -11
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/providers.tf +5 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/storage.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/variables.tf +10 -10
- agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
- agent_starter_pack/base_template/deployment/terraform/github.tf +337 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/iam.tf +20 -41
- {src → agent_starter_pack}/base_template/deployment/terraform/locals.tf +10 -3
- {src/resources/setup_cicd → agent_starter_pack/base_template/deployment/terraform}/providers.tf +8 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/service_accounts.tf +7 -8
- agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/storage.tf +7 -16
- {src → agent_starter_pack}/base_template/deployment/terraform/variables.tf +61 -28
- {src → agent_starter_pack}/base_template/deployment/terraform/vars/env.tfvars +6 -1
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +43 -0
- src/base_template/deployment/terraform/build_triggers.tf → agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +55 -38
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
- {src → agent_starter_pack}/base_template/pyproject.toml +24 -37
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +132 -0
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +65 -0
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +259 -0
- src/base_template/deployment/cd/deploy-to-prod.yaml → agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +38 -30
- src/base_template/deployment/ci/pr_checks.yaml → agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +5 -5
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +322 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
- {src/base_template/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/typing.py +7 -9
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/__init__.py +25 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/part_converter.py +138 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/__init__.py +13 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/a2a_agent_executor.py +265 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/task_result_aggregator.py +152 -0
- agent_starter_pack/cli/commands/create.py +1256 -0
- agent_starter_pack/cli/commands/enhance.py +611 -0
- agent_starter_pack/cli/commands/list.py +203 -0
- agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
- agent_starter_pack/cli/commands/setup_cicd.py +862 -0
- {src → agent_starter_pack}/cli/main.py +10 -2
- {src → agent_starter_pack}/cli/utils/cicd.py +139 -48
- agent_starter_pack/cli/utils/gcp.py +263 -0
- agent_starter_pack/cli/utils/logging.py +103 -0
- agent_starter_pack/cli/utils/remote_template.py +677 -0
- agent_starter_pack/cli/utils/template.py +1466 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/process_data.py +1 -1
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +20 -6
- {src → agent_starter_pack}/data_ingestion/pyproject.toml +1 -0
- {src → agent_starter_pack}/data_ingestion/uv.lock +602 -494
- agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +484 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +84 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +424 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +263 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +519 -0
- agent_starter_pack/deployment_targets/cloud_run/Dockerfile +51 -0
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +243 -0
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +417 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +705 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +321 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +165 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +329 -0
- agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/fast_api_app.py +556 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package-lock.json +79 -1044
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package.json +1 -9
- agent_starter_pack/frontends/adk_live_react/frontend/src/App.tsx +65 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/SidePanel.tsx +516 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/side-panel.scss +563 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/multimodal-live-types.ts +40 -2
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/multimodal-live-client.ts +210 -24
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/utils.ts +27 -5
- agent_starter_pack/resources/docs/adk-cheatsheet.md +1628 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
- agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +4946 -0
- agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +4991 -0
- agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +4963 -0
- agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +5006 -0
- agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +5487 -0
- agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +5532 -0
- agent_starter_pack/resources/locks/uv-langgraph_base-agent_engine.lock +5788 -0
- agent_starter_pack/resources/locks/uv-langgraph_base-cloud_run.lock +5811 -0
- {src → agent_starter_pack}/utils/generate_locks.py +15 -12
- {src → agent_starter_pack}/utils/lock_utils.py +4 -7
- {src → agent_starter_pack}/utils/watch_and_rebuild.py +2 -2
- agent_starter_pack-0.21.0.dist-info/METADATA +182 -0
- agent_starter_pack-0.21.0.dist-info/RECORD +171 -0
- agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
- llm.txt +362 -0
- agent_starter_pack-0.3.3.dist-info/METADATA +0 -164
- agent_starter_pack-0.3.3.dist-info/RECORD +0 -176
- agent_starter_pack-0.3.3.dist-info/entry_points.txt +0 -2
- agents/crewai_coding_crew/README.md +0 -34
- agents/crewai_coding_crew/app/agent.py +0 -86
- agents/crewai_coding_crew/app/crew/config/agents.yaml +0 -39
- agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
- agents/crewai_coding_crew/app/crew/crew.py +0 -71
- agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
- agents/langgraph_base_react/README.md +0 -9
- agents/langgraph_base_react/app/agent.py +0 -73
- agents/live_api/README.md +0 -37
- agents/live_api/app/agent.py +0 -78
- agents/live_api/app/server.py +0 -196
- agents/live_api/app/templates.py +0 -51
- agents/live_api/app/vector_store.py +0 -55
- agents/live_api/template/.templateconfig.yaml +0 -29
- agents/live_api/tests/integration/test_server_e2e.py +0 -254
- agents/live_api/tests/load_test/load_test.py +0 -40
- agents/live_api/tests/unit/test_server.py +0 -143
- src/base_template/Makefile +0 -72
- src/base_template/README.md +0 -208
- src/base_template/app/__init__.py +0 -3
- src/base_template/app/utils/tracing.py +0 -155
- src/base_template/deployment/README.md +0 -126
- src/base_template/deployment/cd/staging.yaml +0 -216
- src/base_template/deployment/terraform/dev/log_sinks.tf +0 -63
- src/base_template/deployment/terraform/log_sinks.tf +0 -70
- src/base_template/deployment/terraform/providers.tf +0 -37
- src/cli/commands/create.py +0 -664
- src/cli/commands/setup_cicd.py +0 -829
- src/cli/utils/gcp.py +0 -117
- src/cli/utils/logging.py +0 -51
- src/cli/utils/template.py +0 -737
- src/deployment_targets/agent_engine/app/agent_engine_app.py +0 -336
- src/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +0 -183
- src/deployment_targets/agent_engine/tests/load_test/README.md +0 -42
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +0 -107
- src/deployment_targets/cloud_run/app/server.py +0 -154
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +0 -249
- src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
- src/deployment_targets/cloud_run/tests/load_test/README.md +0 -83
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +0 -118
- src/deployment_targets/cloud_run/uv.lock +0 -6952
- src/frontends/live_api_react/frontend/src/App.tsx +0 -205
- src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +0 -217
- src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +0 -201
- src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +0 -161
- src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +0 -285
- src/frontends/streamlit/frontend/side_bar.py +0 -214
- src/frontends/streamlit/frontend/streamlit_app.py +0 -265
- src/frontends/streamlit/frontend/style/app_markdown.py +0 -37
- src/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
- src/frontends/streamlit/frontend/utils/local_chat_history.py +0 -125
- src/frontends/streamlit/frontend/utils/message_editing.py +0 -59
- src/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
- src/frontends/streamlit/frontend/utils/stream_handler.py +0 -301
- src/frontends/streamlit/frontend/utils/title_summary.py +0 -94
- src/frontends/streamlit_adk/frontend/side_bar.py +0 -214
- src/frontends/streamlit_adk/frontend/streamlit_app.py +0 -314
- src/frontends/streamlit_adk/frontend/style/app_markdown.py +0 -37
- src/frontends/streamlit_adk/frontend/utils/chat_utils.py +0 -84
- src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +0 -110
- src/frontends/streamlit_adk/frontend/utils/message_editing.py +0 -61
- src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +0 -223
- src/frontends/streamlit_adk/frontend/utils/stream_handler.py +0 -311
- src/frontends/streamlit_adk/frontend/utils/title_summary.py +0 -129
- src/resources/containers/data_processing/Dockerfile +0 -27
- src/resources/containers/e2e-tests/Dockerfile +0 -19
- src/resources/idx/.idx/dev.nix +0 -57
- src/resources/idx/idx-template.json +0 -21
- src/resources/idx/idx-template.nix +0 -26
- src/resources/locks/uv-adk_base-agent_engine.lock +0 -5338
- src/resources/locks/uv-adk_base-cloud_run.lock +0 -5930
- src/resources/locks/uv-agentic_rag-agent_engine.lock +0 -5528
- src/resources/locks/uv-agentic_rag-cloud_run.lock +0 -6120
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6231
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -6839
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +0 -5233
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +0 -5862
- src/resources/locks/uv-live_api-cloud_run.lock +0 -5832
- src/resources/setup_cicd/cicd_variables.tf +0 -41
- src/resources/setup_cicd/github.tf +0 -87
- {agents → agent_starter_pack/agents}/agentic_rag/app/retrievers.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/templates.py +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/vars/env.tfvars +0 -0
- {src → agent_starter_pack}/base_template/tests/unit/test_dummy.py +0 -0
- {src/deployment_targets/agent_engine/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/gcs.py +0 -0
- {src → agent_starter_pack}/cli/utils/__init__.py +0 -0
- {src → agent_starter_pack}/cli/utils/datastores.py +0 -0
- {src → agent_starter_pack}/cli/utils/version.py +0 -0
- {src → agent_starter_pack}/data_ingestion/README.md +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/pipeline.py +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/deployment_metadata.json +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/favicon.ico +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/index.html +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/robots.txt +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.scss +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.test.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.css +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/setupTests.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/tsconfig.json +0 -0
- {agent_starter_pack-0.3.3.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.3.3.dist-info → agent_starter_pack-0.21.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
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
|
+
{%- if cookiecutter.agent_name == "adk_live" %}
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from locust import User, between, task
|
|
21
|
+
from websockets.exceptions import WebSocketException
|
|
22
|
+
from websockets.sync.client import connect
|
|
23
|
+
|
|
24
|
+
logging.basicConfig(level=logging.INFO)
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WebSocketUser(User):
|
|
29
|
+
"""Simulates a user making websocket requests to the remote agent engine."""
|
|
30
|
+
|
|
31
|
+
wait_time = between(1, 3) # Wait 1-3 seconds between tasks
|
|
32
|
+
abstract = True
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args: object, **kwargs: object) -> None:
|
|
35
|
+
super().__init__(*args, **kwargs)
|
|
36
|
+
if self.host.startswith("https://"):
|
|
37
|
+
self.ws_url = self.host.replace("https://", "wss://", 1) + "/ws"
|
|
38
|
+
elif self.host.startswith("http://"):
|
|
39
|
+
self.ws_url = self.host.replace("http://", "ws://", 1) + "/ws"
|
|
40
|
+
else:
|
|
41
|
+
self.ws_url = self.host + "/ws"
|
|
42
|
+
|
|
43
|
+
@task
|
|
44
|
+
def websocket_audio_conversation(self) -> None:
|
|
45
|
+
"""Test a full websocket conversation with audio input."""
|
|
46
|
+
start_time = time.time()
|
|
47
|
+
response_count = 0
|
|
48
|
+
exception = None
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
response_count = self._websocket_interaction()
|
|
52
|
+
|
|
53
|
+
# Mark as failure if we got no valid responses
|
|
54
|
+
if response_count == 0:
|
|
55
|
+
exception = Exception("No responses received from agent")
|
|
56
|
+
|
|
57
|
+
except WebSocketException as e:
|
|
58
|
+
exception = e
|
|
59
|
+
logger.error(f"WebSocket error: {e}")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
exception = e
|
|
62
|
+
logger.error(f"Unexpected error: {e}")
|
|
63
|
+
finally:
|
|
64
|
+
total_time = int((time.time() - start_time) * 1000)
|
|
65
|
+
|
|
66
|
+
# Report the request metrics to Locust
|
|
67
|
+
self.environment.events.request.fire(
|
|
68
|
+
request_type="WS",
|
|
69
|
+
name="websocket_conversation",
|
|
70
|
+
response_time=total_time,
|
|
71
|
+
response_length=response_count * 100, # Approximate response size
|
|
72
|
+
response=None,
|
|
73
|
+
context={},
|
|
74
|
+
exception=exception,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _websocket_interaction(self) -> int:
|
|
78
|
+
"""Handle the websocket interaction and return response count."""
|
|
79
|
+
response_count = 0
|
|
80
|
+
|
|
81
|
+
with connect(self.ws_url, open_timeout=10, close_timeout=20) as websocket:
|
|
82
|
+
# Wait for setupComplete
|
|
83
|
+
setup_response = websocket.recv(timeout=10.0)
|
|
84
|
+
setup_data = json.loads(setup_response)
|
|
85
|
+
assert "setupComplete" in setup_data, (
|
|
86
|
+
f"Expected setupComplete, got {setup_data}"
|
|
87
|
+
)
|
|
88
|
+
logger.info("Received setupComplete")
|
|
89
|
+
|
|
90
|
+
# Send dummy audio chunk with user_id
|
|
91
|
+
dummy_audio = bytes([0] * 1024)
|
|
92
|
+
audio_msg = {
|
|
93
|
+
"user_id": "load-test-user",
|
|
94
|
+
"realtimeInput": {
|
|
95
|
+
"mediaChunks": [
|
|
96
|
+
{
|
|
97
|
+
"mimeType": "audio/pcm;rate=16000",
|
|
98
|
+
"data": dummy_audio.hex(),
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
websocket.send(json.dumps(audio_msg))
|
|
104
|
+
logger.info("Sent audio chunk")
|
|
105
|
+
|
|
106
|
+
# Send text message to complete the turn
|
|
107
|
+
text_msg = {
|
|
108
|
+
"content": {
|
|
109
|
+
"role": "user",
|
|
110
|
+
"parts": [{"text": "Hello!"}],
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
websocket.send(json.dumps(text_msg))
|
|
114
|
+
logger.info("Sent text completion")
|
|
115
|
+
|
|
116
|
+
# Collect responses until turn_complete or timeout
|
|
117
|
+
for _ in range(20): # Max 20 responses
|
|
118
|
+
try:
|
|
119
|
+
response = websocket.recv(timeout=10.0)
|
|
120
|
+
response_data = json.loads(response)
|
|
121
|
+
response_count += 1
|
|
122
|
+
logger.debug(f"Received response: {response_data}")
|
|
123
|
+
|
|
124
|
+
if isinstance(response_data, dict) and response_data.get(
|
|
125
|
+
"turn_complete"
|
|
126
|
+
):
|
|
127
|
+
logger.info(f"Turn complete after {response_count} responses")
|
|
128
|
+
break
|
|
129
|
+
except TimeoutError:
|
|
130
|
+
logger.info(f"Timeout after {response_count} responses")
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
return response_count
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RemoteAgentUser(WebSocketUser):
|
|
137
|
+
"""User for testing remote agent engine deployment."""
|
|
138
|
+
|
|
139
|
+
# Set the host via command line: locust -f load_test.py --host=https://your-deployed-service.run.app
|
|
140
|
+
host = "http://localhost:8000" # Default for local testing
|
|
141
|
+
{%- else %}
|
|
142
|
+
{%- if cookiecutter.is_a2a %}
|
|
143
|
+
|
|
144
|
+
import json
|
|
145
|
+
import logging
|
|
146
|
+
import os
|
|
147
|
+
import time
|
|
148
|
+
|
|
149
|
+
from locust import HttpUser, between, task
|
|
150
|
+
|
|
151
|
+
# Configure logging
|
|
152
|
+
logging.basicConfig(
|
|
153
|
+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
154
|
+
)
|
|
155
|
+
logger = logging.getLogger(__name__)
|
|
156
|
+
|
|
157
|
+
# Initialize Vertex AI and load agent config
|
|
158
|
+
with open("deployment_metadata.json") as f:
|
|
159
|
+
remote_agent_engine_id = json.load(f)["remote_agent_engine_id"]
|
|
160
|
+
|
|
161
|
+
parts = remote_agent_engine_id.split("/")
|
|
162
|
+
project_id = parts[1]
|
|
163
|
+
location = parts[3]
|
|
164
|
+
engine_id = parts[5]
|
|
165
|
+
|
|
166
|
+
# Convert remote agent engine ID to URLs
|
|
167
|
+
base_url = f"https://{location}-aiplatform.googleapis.com"
|
|
168
|
+
a2a_base_path = f"/v1beta1/projects/{project_id}/locations/{location}/reasoningEngines/{engine_id}/a2a/v1"
|
|
169
|
+
|
|
170
|
+
logger.info("Using remote agent engine ID: %s", remote_agent_engine_id)
|
|
171
|
+
logger.info("Using base URL: %s", base_url)
|
|
172
|
+
logger.info("Using API base path: %s", a2a_base_path)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class SendMessageUser(HttpUser):
|
|
176
|
+
"""Simulates a user interacting with the send message API."""
|
|
177
|
+
|
|
178
|
+
wait_time = between(1, 3) # Wait 1-3 seconds between tasks
|
|
179
|
+
host = base_url # Set the base host URL for Locust
|
|
180
|
+
|
|
181
|
+
@task
|
|
182
|
+
def send_message_and_poll(self) -> None:
|
|
183
|
+
"""Simulates a chat interaction: sends a message and polls for completion."""
|
|
184
|
+
headers = {"Content-Type": "application/json"}
|
|
185
|
+
headers["Authorization"] = f"Bearer {os.environ['_AUTH_TOKEN']}"
|
|
186
|
+
|
|
187
|
+
data = {
|
|
188
|
+
"message": {
|
|
189
|
+
"messageId": "msg-id",
|
|
190
|
+
"content": [{"text": "Hello! What's the weather in New York?"}],
|
|
191
|
+
"role": "ROLE_USER",
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
e2e_start_time = time.time()
|
|
196
|
+
with self.client.post(
|
|
197
|
+
f"{a2a_base_path}/message:send",
|
|
198
|
+
headers=headers,
|
|
199
|
+
json=data,
|
|
200
|
+
catch_response=True,
|
|
201
|
+
name="/v1/message:send",
|
|
202
|
+
) as response:
|
|
203
|
+
if response.status_code != 200:
|
|
204
|
+
response.failure(
|
|
205
|
+
f"Send failed with status code: {response.status_code}"
|
|
206
|
+
)
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
response.success()
|
|
210
|
+
response_data = response.json()
|
|
211
|
+
|
|
212
|
+
# Extract task ID
|
|
213
|
+
try:
|
|
214
|
+
task_id = response_data["task"]["id"]
|
|
215
|
+
except (KeyError, TypeError) as e:
|
|
216
|
+
logger.error(f"Failed to extract task ID: {e}")
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
# Poll for task completion
|
|
220
|
+
max_polls = 20 # Maximum number of poll attempts
|
|
221
|
+
poll_interval = 0.5 # Seconds between polls
|
|
222
|
+
poll_count = 0
|
|
223
|
+
|
|
224
|
+
while poll_count < max_polls:
|
|
225
|
+
poll_count += 1
|
|
226
|
+
time.sleep(poll_interval)
|
|
227
|
+
|
|
228
|
+
with self.client.get(
|
|
229
|
+
f"{a2a_base_path}/tasks/{task_id}",
|
|
230
|
+
headers=headers,
|
|
231
|
+
catch_response=True,
|
|
232
|
+
name="/v1/tasks/{id}",
|
|
233
|
+
) as poll_response:
|
|
234
|
+
if poll_response.status_code != 200:
|
|
235
|
+
poll_response.failure(
|
|
236
|
+
f"Poll failed with status code: {poll_response.status_code}"
|
|
237
|
+
)
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
poll_data = poll_response.json()
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
task_state = poll_data["status"]["state"]
|
|
244
|
+
except (KeyError, TypeError) as e:
|
|
245
|
+
logger.error(f"Failed to extract task state: {e}")
|
|
246
|
+
poll_response.failure(f"Invalid response format: {e}")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
# Check if task is complete
|
|
250
|
+
if task_state in ["TASK_STATE_COMPLETED"]:
|
|
251
|
+
poll_response.success()
|
|
252
|
+
|
|
253
|
+
# Measure end-to-end time
|
|
254
|
+
e2e_duration = (time.time() - e2e_start_time) * 1000
|
|
255
|
+
|
|
256
|
+
# Fire custom event for end-to-end metrics
|
|
257
|
+
self.environment.events.request.fire(
|
|
258
|
+
request_type="E2E",
|
|
259
|
+
name="message:send_and_complete",
|
|
260
|
+
response_time=e2e_duration,
|
|
261
|
+
response_length=len(json.dumps(poll_data)),
|
|
262
|
+
response=poll_response,
|
|
263
|
+
context={"poll_count": poll_count},
|
|
264
|
+
)
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
elif task_state in ["TASK_STATE_WORKING"]:
|
|
268
|
+
poll_response.success()
|
|
269
|
+
|
|
270
|
+
else:
|
|
271
|
+
poll_response.failure(f"Task failed with state: {task_state}")
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
# Timeout - task didn't complete in time
|
|
275
|
+
self.environment.events.request.fire(
|
|
276
|
+
request_type="TIMEOUT",
|
|
277
|
+
name="message:timeout",
|
|
278
|
+
response_time=(time.time() - e2e_start_time) * 1000,
|
|
279
|
+
response_length=0,
|
|
280
|
+
response=None,
|
|
281
|
+
context={"poll_count": poll_count},
|
|
282
|
+
exception=TimeoutError(f"Task did not complete after {max_polls} polls"),
|
|
283
|
+
)
|
|
284
|
+
{%- else %}
|
|
285
|
+
|
|
286
|
+
import json
|
|
287
|
+
import logging
|
|
288
|
+
import os
|
|
289
|
+
import time
|
|
290
|
+
|
|
291
|
+
from locust import HttpUser, between, task
|
|
292
|
+
|
|
293
|
+
# Configure logging
|
|
294
|
+
logging.basicConfig(
|
|
295
|
+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
296
|
+
)
|
|
297
|
+
logger = logging.getLogger(__name__)
|
|
298
|
+
|
|
299
|
+
# Initialize Vertex AI and load agent config
|
|
300
|
+
with open("deployment_metadata.json") as f:
|
|
301
|
+
remote_agent_engine_id = json.load(f)["remote_agent_engine_id"]
|
|
302
|
+
|
|
303
|
+
parts = remote_agent_engine_id.split("/")
|
|
304
|
+
project_id = parts[1]
|
|
305
|
+
location = parts[3]
|
|
306
|
+
engine_id = parts[5]
|
|
307
|
+
|
|
308
|
+
# Convert remote agent engine ID to streaming URL.
|
|
309
|
+
base_url = f"https://{location}-aiplatform.googleapis.com"
|
|
310
|
+
url_path = f"/v1/projects/{project_id}/locations/{location}/reasoningEngines/{engine_id}:streamQuery"
|
|
311
|
+
|
|
312
|
+
logger.info("Using remote agent engine ID: %s", remote_agent_engine_id)
|
|
313
|
+
logger.info("Using base URL: %s", base_url)
|
|
314
|
+
logger.info("Using URL path: %s", url_path)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class ChatStreamUser(HttpUser):
|
|
318
|
+
"""Simulates a user interacting with the chat stream API."""
|
|
319
|
+
|
|
320
|
+
wait_time = between(1, 3) # Wait 1-3 seconds between tasks
|
|
321
|
+
host = base_url # Set the base host URL for Locust
|
|
322
|
+
|
|
323
|
+
@task
|
|
324
|
+
def chat_stream(self) -> None:
|
|
325
|
+
"""Simulates a chat stream interaction."""
|
|
326
|
+
headers = {"Content-Type": "application/json"}
|
|
327
|
+
headers["Authorization"] = f"Bearer {os.environ['_AUTH_TOKEN']}"
|
|
328
|
+
{% if cookiecutter.is_adk %}
|
|
329
|
+
data = {
|
|
330
|
+
"class_method": "async_stream_query",
|
|
331
|
+
"input": {
|
|
332
|
+
"user_id": "test",
|
|
333
|
+
"message": "What's the weather in San Francisco?",
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
{% else %}
|
|
337
|
+
data = {
|
|
338
|
+
"input": {
|
|
339
|
+
"input": {
|
|
340
|
+
"messages": [
|
|
341
|
+
{"type": "human", "content": "Hello, AI!"},
|
|
342
|
+
{"type": "ai", "content": "Hello!"},
|
|
343
|
+
{"type": "human", "content": "How are you?"},
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
"config": {
|
|
347
|
+
"metadata": {"user_id": "test-user", "session_id": "test-session"}
|
|
348
|
+
},
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
{% endif %}
|
|
352
|
+
start_time = time.time()
|
|
353
|
+
with self.client.post(
|
|
354
|
+
url_path,
|
|
355
|
+
headers=headers,
|
|
356
|
+
json=data,
|
|
357
|
+
catch_response=True,
|
|
358
|
+
{%- if cookiecutter.is_adk %}
|
|
359
|
+
name="/streamQuery async_stream_query",
|
|
360
|
+
{%- else %}
|
|
361
|
+
name="/stream_messages first message",
|
|
362
|
+
{%- endif %}
|
|
363
|
+
stream=True,
|
|
364
|
+
params={"alt": "sse"},
|
|
365
|
+
) as response:
|
|
366
|
+
if response.status_code == 200:
|
|
367
|
+
events = []
|
|
368
|
+
has_error = False
|
|
369
|
+
for line in response.iter_lines():
|
|
370
|
+
if line:
|
|
371
|
+
line_str = line.decode("utf-8")
|
|
372
|
+
events.append(line_str)
|
|
373
|
+
|
|
374
|
+
if "429 Too Many Requests" in line_str:
|
|
375
|
+
self.environment.events.request.fire(
|
|
376
|
+
request_type="POST",
|
|
377
|
+
name=f"{url_path} rate_limited 429s",
|
|
378
|
+
response_time=0,
|
|
379
|
+
response_length=len(line),
|
|
380
|
+
response=response,
|
|
381
|
+
context={},
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Check for error responses in the JSON payload
|
|
385
|
+
try:
|
|
386
|
+
event_data = json.loads(line_str)
|
|
387
|
+
if isinstance(event_data, dict) and "code" in event_data:
|
|
388
|
+
# Flag any non-2xx codes as errors
|
|
389
|
+
if event_data["code"] >= 400:
|
|
390
|
+
has_error = True
|
|
391
|
+
error_msg = event_data.get(
|
|
392
|
+
"message", "Unknown error"
|
|
393
|
+
)
|
|
394
|
+
response.failure(f"Error in response: {error_msg}")
|
|
395
|
+
logger.error(
|
|
396
|
+
"Received error response: code=%s, message=%s",
|
|
397
|
+
event_data["code"],
|
|
398
|
+
error_msg,
|
|
399
|
+
)
|
|
400
|
+
except json.JSONDecodeError:
|
|
401
|
+
# If it's not valid JSON, continue processing
|
|
402
|
+
pass
|
|
403
|
+
|
|
404
|
+
end_time = time.time()
|
|
405
|
+
total_time = end_time - start_time
|
|
406
|
+
|
|
407
|
+
# Only fire success event if no errors were found
|
|
408
|
+
if not has_error:
|
|
409
|
+
self.environment.events.request.fire(
|
|
410
|
+
request_type="POST",
|
|
411
|
+
{%- if cookiecutter.is_adk %}
|
|
412
|
+
name="/streamQuery end",
|
|
413
|
+
{%- else %}
|
|
414
|
+
name="/stream_messages end",
|
|
415
|
+
{%- endif %}
|
|
416
|
+
response_time=total_time * 1000, # Convert to milliseconds
|
|
417
|
+
response_length=len(events),
|
|
418
|
+
response=response,
|
|
419
|
+
context={},
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
response.failure(f"Unexpected status code: {response.status_code}")
|
|
423
|
+
{%- endif %}
|
|
424
|
+
{%- endif %}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
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
|
+
# mypy: disable-error-code="arg-type"
|
|
16
|
+
|
|
17
|
+
"""Helper functions for testing AgentEngineApp with A2A protocol."""
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import json
|
|
21
|
+
from collections.abc import Awaitable, Callable
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
|
+
|
|
24
|
+
from starlette.requests import Request
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
|
|
28
|
+
|
|
29
|
+
# Test constants
|
|
30
|
+
POLL_MAX_ATTEMPTS = 30
|
|
31
|
+
POLL_INTERVAL_SECONDS = 1.0
|
|
32
|
+
TEST_ARTIFACTS_BUCKET = "test-artifacts-bucket"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def receive_wrapper(data: dict[str, Any] | None) -> Callable[[], Awaitable[dict]]:
|
|
36
|
+
"""Creates a mock ASGI receive callable for testing.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
data: Dictionary to encode as JSON request body
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Async callable that returns mock ASGI receive message
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
async def receive() -> dict:
|
|
46
|
+
byte_data = json.dumps(data).encode("utf-8")
|
|
47
|
+
return {"type": "http.request", "body": byte_data, "more_body": False}
|
|
48
|
+
|
|
49
|
+
return receive
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_post_request(
|
|
53
|
+
data: dict[str, Any] | None = None, path_params: dict[str, str] | None = None
|
|
54
|
+
) -> Request:
|
|
55
|
+
"""Builds a mock Starlette Request object for a POST request with JSON data.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
data: JSON data to include in request body
|
|
59
|
+
path_params: Path parameters to include in request scope
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Mock Starlette Request object
|
|
63
|
+
"""
|
|
64
|
+
scope: dict[str, Any] = {
|
|
65
|
+
"type": "http",
|
|
66
|
+
"http_version": "1.1",
|
|
67
|
+
"headers": [(b"content-type", b"application/json")],
|
|
68
|
+
"app": None,
|
|
69
|
+
}
|
|
70
|
+
if path_params:
|
|
71
|
+
scope["path_params"] = path_params
|
|
72
|
+
receiver = receive_wrapper(data)
|
|
73
|
+
return Request(scope, receiver)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_get_request(path_params: dict[str, str] | None) -> Request:
|
|
77
|
+
"""Builds a mock Starlette Request object for a GET request.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
path_params: Path parameters to include in request scope
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Mock Starlette Request object
|
|
84
|
+
"""
|
|
85
|
+
scope: dict[str, Any] = {
|
|
86
|
+
"type": "http",
|
|
87
|
+
"http_version": "1.1",
|
|
88
|
+
"query_string": b"",
|
|
89
|
+
"app": None,
|
|
90
|
+
}
|
|
91
|
+
if path_params:
|
|
92
|
+
scope["path_params"] = path_params
|
|
93
|
+
|
|
94
|
+
async def receive() -> dict:
|
|
95
|
+
return {"type": "http.disconnect"}
|
|
96
|
+
|
|
97
|
+
return Request(scope, receive)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def poll_task_completion(
|
|
101
|
+
agent_app: "AgentEngineApp",
|
|
102
|
+
task_id: str,
|
|
103
|
+
max_attempts: int = POLL_MAX_ATTEMPTS,
|
|
104
|
+
interval: float = POLL_INTERVAL_SECONDS,
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
"""Poll for task completion and return final response.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
agent_app: The AgentEngineApp instance to poll
|
|
110
|
+
task_id: The task ID to poll for
|
|
111
|
+
max_attempts: Maximum number of polling attempts
|
|
112
|
+
interval: Seconds to wait between polls
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Final task response when completed
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
AssertionError: If task fails or times out
|
|
119
|
+
"""
|
|
120
|
+
for _ in range(max_attempts):
|
|
121
|
+
poll_request = build_get_request({"id": task_id})
|
|
122
|
+
response = await agent_app.on_get_task(
|
|
123
|
+
request=poll_request,
|
|
124
|
+
context=None,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
task_state = response.get("status", {}).get("state", "")
|
|
128
|
+
|
|
129
|
+
if task_state == "TASK_STATE_COMPLETED":
|
|
130
|
+
return response
|
|
131
|
+
elif task_state == "TASK_STATE_FAILED":
|
|
132
|
+
raise AssertionError(f"Task failed: {response}")
|
|
133
|
+
|
|
134
|
+
await asyncio.sleep(interval)
|
|
135
|
+
|
|
136
|
+
raise AssertionError(
|
|
137
|
+
f"Task did not complete within {max_attempts * interval} seconds"
|
|
138
|
+
)
|