google-adk 0.0.1__py3-none-any.whl → 0.0.2__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.
- google/adk/__init__.py +20 -0
- google/adk/agents/__init__.py +32 -0
- google/adk/agents/active_streaming_tool.py +38 -0
- google/adk/agents/base_agent.py +345 -0
- google/adk/agents/callback_context.py +112 -0
- google/adk/agents/invocation_context.py +181 -0
- google/adk/agents/langgraph_agent.py +140 -0
- google/adk/agents/live_request_queue.py +64 -0
- google/adk/agents/llm_agent.py +376 -0
- google/adk/agents/loop_agent.py +62 -0
- google/adk/agents/parallel_agent.py +96 -0
- google/adk/agents/readonly_context.py +46 -0
- google/adk/agents/remote_agent.py +50 -0
- google/adk/agents/run_config.py +87 -0
- google/adk/agents/sequential_agent.py +45 -0
- google/adk/agents/transcription_entry.py +34 -0
- google/adk/artifacts/__init__.py +23 -0
- google/adk/artifacts/base_artifact_service.py +128 -0
- google/adk/artifacts/gcs_artifact_service.py +195 -0
- google/adk/artifacts/in_memory_artifact_service.py +133 -0
- google/adk/auth/__init__.py +22 -0
- google/adk/auth/auth_credential.py +220 -0
- google/adk/auth/auth_handler.py +268 -0
- google/adk/auth/auth_preprocessor.py +116 -0
- google/adk/auth/auth_schemes.py +67 -0
- google/adk/auth/auth_tool.py +55 -0
- google/adk/cli/__init__.py +15 -0
- google/adk/cli/__main__.py +18 -0
- google/adk/cli/agent_graph.py +122 -0
- google/adk/cli/browser/adk_favicon.svg +17 -0
- google/adk/cli/browser/assets/audio-processor.js +51 -0
- google/adk/cli/browser/assets/config/runtime-config.json +3 -0
- google/adk/cli/browser/index.html +33 -0
- google/adk/cli/browser/main-XUU6OGCC.js +75 -0
- google/adk/cli/browser/polyfills-FFHMD2TL.js +18 -0
- google/adk/cli/browser/styles-4VDSPQ37.css +17 -0
- google/adk/cli/cli.py +181 -0
- google/adk/cli/cli_deploy.py +181 -0
- google/adk/cli/cli_eval.py +282 -0
- google/adk/cli/cli_tools_click.py +479 -0
- google/adk/cli/fast_api.py +774 -0
- google/adk/cli/media_streamer/__init__.py +19 -0
- google/adk/cli/media_streamer/index.html +228 -0
- google/adk/cli/utils/__init__.py +49 -0
- google/adk/cli/utils/envs.py +57 -0
- google/adk/cli/utils/evals.py +93 -0
- google/adk/cli/utils/logs.py +72 -0
- google/adk/code_executors/__init__.py +49 -0
- google/adk/code_executors/base_code_executor.py +97 -0
- google/adk/code_executors/code_execution_utils.py +256 -0
- google/adk/code_executors/code_executor_context.py +202 -0
- google/adk/code_executors/container_code_executor.py +196 -0
- google/adk/code_executors/unsafe_local_code_executor.py +71 -0
- google/adk/code_executors/vertex_ai_code_executor.py +234 -0
- google/adk/evaluation/__init__.py +31 -0
- google/adk/evaluation/agent_evaluator.py +329 -0
- google/adk/evaluation/evaluation_constants.py +24 -0
- google/adk/evaluation/evaluation_generator.py +270 -0
- google/adk/evaluation/response_evaluator.py +135 -0
- google/adk/evaluation/trajectory_evaluator.py +184 -0
- google/adk/events/__init__.py +21 -0
- google/adk/events/event.py +130 -0
- google/adk/events/event_actions.py +55 -0
- google/adk/examples/__init__.py +28 -0
- google/adk/examples/base_example_provider.py +35 -0
- google/adk/examples/example.py +27 -0
- google/adk/examples/example_util.py +123 -0
- google/adk/examples/vertex_ai_example_store.py +104 -0
- google/adk/flows/__init__.py +14 -0
- google/adk/flows/llm_flows/__init__.py +20 -0
- google/adk/flows/llm_flows/_base_llm_processor.py +52 -0
- google/adk/flows/llm_flows/_code_execution.py +458 -0
- google/adk/flows/llm_flows/_nl_planning.py +129 -0
- google/adk/flows/llm_flows/agent_transfer.py +132 -0
- google/adk/flows/llm_flows/audio_transcriber.py +109 -0
- google/adk/flows/llm_flows/auto_flow.py +49 -0
- google/adk/flows/llm_flows/base_llm_flow.py +559 -0
- google/adk/flows/llm_flows/basic.py +72 -0
- google/adk/flows/llm_flows/contents.py +370 -0
- google/adk/flows/llm_flows/functions.py +486 -0
- google/adk/flows/llm_flows/identity.py +47 -0
- google/adk/flows/llm_flows/instructions.py +137 -0
- google/adk/flows/llm_flows/single_flow.py +57 -0
- google/adk/memory/__init__.py +35 -0
- google/adk/memory/base_memory_service.py +74 -0
- google/adk/memory/in_memory_memory_service.py +62 -0
- google/adk/memory/vertex_ai_rag_memory_service.py +177 -0
- google/adk/models/__init__.py +31 -0
- google/adk/models/anthropic_llm.py +243 -0
- google/adk/models/base_llm.py +87 -0
- google/adk/models/base_llm_connection.py +76 -0
- google/adk/models/gemini_llm_connection.py +200 -0
- google/adk/models/google_llm.py +331 -0
- google/adk/models/lite_llm.py +673 -0
- google/adk/models/llm_request.py +98 -0
- google/adk/models/llm_response.py +111 -0
- google/adk/models/registry.py +102 -0
- google/adk/planners/__init__.py +23 -0
- google/adk/planners/base_planner.py +66 -0
- google/adk/planners/built_in_planner.py +75 -0
- google/adk/planners/plan_re_act_planner.py +208 -0
- google/adk/runners.py +456 -0
- google/adk/sessions/__init__.py +41 -0
- google/adk/sessions/base_session_service.py +133 -0
- google/adk/sessions/database_session_service.py +522 -0
- google/adk/sessions/in_memory_session_service.py +206 -0
- google/adk/sessions/session.py +54 -0
- google/adk/sessions/state.py +71 -0
- google/adk/sessions/vertex_ai_session_service.py +356 -0
- google/adk/telemetry.py +189 -0
- google/adk/tests/__init__.py +14 -0
- google/adk/tests/integration/.env.example +10 -0
- google/adk/tests/integration/__init__.py +18 -0
- google/adk/tests/integration/conftest.py +119 -0
- google/adk/tests/integration/fixture/__init__.py +14 -0
- google/adk/tests/integration/fixture/agent_with_config/__init__.py +15 -0
- google/adk/tests/integration/fixture/agent_with_config/agent.py +88 -0
- google/adk/tests/integration/fixture/callback_agent/__init__.py +15 -0
- google/adk/tests/integration/fixture/callback_agent/agent.py +105 -0
- google/adk/tests/integration/fixture/context_update_test/OWNERS +1 -0
- google/adk/tests/integration/fixture/context_update_test/__init__.py +15 -0
- google/adk/tests/integration/fixture/context_update_test/agent.py +43 -0
- google/adk/tests/integration/fixture/context_update_test/successful_test.session.json +582 -0
- google/adk/tests/integration/fixture/context_variable_agent/__init__.py +15 -0
- google/adk/tests/integration/fixture/context_variable_agent/agent.py +115 -0
- google/adk/tests/integration/fixture/customer_support_ma/__init__.py +15 -0
- google/adk/tests/integration/fixture/customer_support_ma/agent.py +172 -0
- google/adk/tests/integration/fixture/ecommerce_customer_service_agent/__init__.py +15 -0
- google/adk/tests/integration/fixture/ecommerce_customer_service_agent/agent.py +338 -0
- google/adk/tests/integration/fixture/ecommerce_customer_service_agent/order_query.test.json +69 -0
- google/adk/tests/integration/fixture/ecommerce_customer_service_agent/test_config.json +6 -0
- google/adk/tests/integration/fixture/flow_complex_spark/__init__.py +15 -0
- google/adk/tests/integration/fixture/flow_complex_spark/agent.py +182 -0
- google/adk/tests/integration/fixture/flow_complex_spark/sample.debug.log +243 -0
- google/adk/tests/integration/fixture/flow_complex_spark/sample.session.json +190 -0
- google/adk/tests/integration/fixture/hello_world_agent/__init__.py +15 -0
- google/adk/tests/integration/fixture/hello_world_agent/agent.py +95 -0
- google/adk/tests/integration/fixture/hello_world_agent/roll_die.test.json +24 -0
- google/adk/tests/integration/fixture/hello_world_agent/test_config.json +6 -0
- google/adk/tests/integration/fixture/home_automation_agent/__init__.py +15 -0
- google/adk/tests/integration/fixture/home_automation_agent/agent.py +304 -0
- google/adk/tests/integration/fixture/home_automation_agent/simple_test.test.json +5 -0
- google/adk/tests/integration/fixture/home_automation_agent/simple_test2.test.json +5 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_config.json +5 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_files/dependent_tool_calls.test.json +18 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_files/memorizing_past_events/eval_data.test.json +17 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_files/memorizing_past_events/test_config.json +6 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_files/simple_multi_turn_conversation.test.json +18 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_files/simple_test.test.json +17 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_files/simple_test2.test.json +5 -0
- google/adk/tests/integration/fixture/home_automation_agent/test_files/test_config.json +5 -0
- google/adk/tests/integration/fixture/tool_agent/__init__.py +15 -0
- google/adk/tests/integration/fixture/tool_agent/agent.py +218 -0
- google/adk/tests/integration/fixture/tool_agent/files/Agent_test_plan.pdf +0 -0
- google/adk/tests/integration/fixture/trip_planner_agent/__init__.py +15 -0
- google/adk/tests/integration/fixture/trip_planner_agent/agent.py +110 -0
- google/adk/tests/integration/fixture/trip_planner_agent/initial.session.json +13 -0
- google/adk/tests/integration/fixture/trip_planner_agent/test_config.json +5 -0
- google/adk/tests/integration/fixture/trip_planner_agent/test_files/initial.session.json +13 -0
- google/adk/tests/integration/fixture/trip_planner_agent/test_files/test_config.json +5 -0
- google/adk/tests/integration/fixture/trip_planner_agent/test_files/trip_inquiry_sub_agent.test.json +7 -0
- google/adk/tests/integration/fixture/trip_planner_agent/trip_inquiry.test.json +19 -0
- google/adk/tests/integration/models/__init__.py +14 -0
- google/adk/tests/integration/models/test_google_llm.py +65 -0
- google/adk/tests/integration/test_callback.py +70 -0
- google/adk/tests/integration/test_context_variable.py +67 -0
- google/adk/tests/integration/test_evalute_agent_in_fixture.py +76 -0
- google/adk/tests/integration/test_multi_agent.py +28 -0
- google/adk/tests/integration/test_multi_turn.py +42 -0
- google/adk/tests/integration/test_single_agent.py +23 -0
- google/adk/tests/integration/test_sub_agent.py +26 -0
- google/adk/tests/integration/test_system_instruction.py +177 -0
- google/adk/tests/integration/test_tools.py +287 -0
- google/adk/tests/integration/test_with_test_file.py +34 -0
- google/adk/tests/integration/tools/__init__.py +14 -0
- google/adk/tests/integration/utils/__init__.py +16 -0
- google/adk/tests/integration/utils/asserts.py +75 -0
- google/adk/tests/integration/utils/test_runner.py +97 -0
- google/adk/tests/unittests/__init__.py +14 -0
- google/adk/tests/unittests/agents/__init__.py +14 -0
- google/adk/tests/unittests/agents/test_base_agent.py +407 -0
- google/adk/tests/unittests/agents/test_langgraph_agent.py +191 -0
- google/adk/tests/unittests/agents/test_llm_agent_callbacks.py +138 -0
- google/adk/tests/unittests/agents/test_llm_agent_fields.py +231 -0
- google/adk/tests/unittests/agents/test_loop_agent.py +136 -0
- google/adk/tests/unittests/agents/test_parallel_agent.py +92 -0
- google/adk/tests/unittests/agents/test_sequential_agent.py +114 -0
- google/adk/tests/unittests/artifacts/__init__.py +14 -0
- google/adk/tests/unittests/artifacts/test_artifact_service.py +276 -0
- google/adk/tests/unittests/auth/test_auth_handler.py +575 -0
- google/adk/tests/unittests/conftest.py +73 -0
- google/adk/tests/unittests/fast_api/__init__.py +14 -0
- google/adk/tests/unittests/fast_api/test_fast_api.py +269 -0
- google/adk/tests/unittests/flows/__init__.py +14 -0
- google/adk/tests/unittests/flows/llm_flows/__init__.py +14 -0
- google/adk/tests/unittests/flows/llm_flows/_test_examples.py +142 -0
- google/adk/tests/unittests/flows/llm_flows/test_agent_transfer.py +311 -0
- google/adk/tests/unittests/flows/llm_flows/test_functions_long_running.py +244 -0
- google/adk/tests/unittests/flows/llm_flows/test_functions_request_euc.py +346 -0
- google/adk/tests/unittests/flows/llm_flows/test_functions_sequential.py +93 -0
- google/adk/tests/unittests/flows/llm_flows/test_functions_simple.py +258 -0
- google/adk/tests/unittests/flows/llm_flows/test_identity.py +66 -0
- google/adk/tests/unittests/flows/llm_flows/test_instructions.py +164 -0
- google/adk/tests/unittests/flows/llm_flows/test_model_callbacks.py +142 -0
- google/adk/tests/unittests/flows/llm_flows/test_other_configs.py +46 -0
- google/adk/tests/unittests/flows/llm_flows/test_tool_callbacks.py +269 -0
- google/adk/tests/unittests/models/__init__.py +14 -0
- google/adk/tests/unittests/models/test_google_llm.py +224 -0
- google/adk/tests/unittests/models/test_litellm.py +804 -0
- google/adk/tests/unittests/models/test_models.py +60 -0
- google/adk/tests/unittests/sessions/__init__.py +14 -0
- google/adk/tests/unittests/sessions/test_session_service.py +227 -0
- google/adk/tests/unittests/sessions/test_vertex_ai_session_service.py +246 -0
- google/adk/tests/unittests/streaming/__init__.py +14 -0
- google/adk/tests/unittests/streaming/test_streaming.py +50 -0
- google/adk/tests/unittests/tools/__init__.py +14 -0
- google/adk/tests/unittests/tools/apihub_tool/clients/test_apihub_client.py +499 -0
- google/adk/tests/unittests/tools/apihub_tool/test_apihub_toolset.py +204 -0
- google/adk/tests/unittests/tools/application_integration_tool/clients/test_connections_client.py +600 -0
- google/adk/tests/unittests/tools/application_integration_tool/clients/test_integration_client.py +630 -0
- google/adk/tests/unittests/tools/application_integration_tool/test_application_integration_toolset.py +345 -0
- google/adk/tests/unittests/tools/google_api_tool/__init__.py +13 -0
- google/adk/tests/unittests/tools/google_api_tool/test_googleapi_to_openapi_converter.py +657 -0
- google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_auto_auth_credential_exchanger.py +145 -0
- google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_base_auth_credential_exchanger.py +68 -0
- google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_oauth2_exchanger.py +153 -0
- google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_service_account_exchanger.py +196 -0
- google/adk/tests/unittests/tools/openapi_tool/auth/test_auth_helper.py +573 -0
- google/adk/tests/unittests/tools/openapi_tool/common/test_common.py +436 -0
- google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test.yaml +1367 -0
- google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py +628 -0
- google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_toolset.py +139 -0
- google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_operation_parser.py +406 -0
- google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py +966 -0
- google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_tool_auth_handler.py +201 -0
- google/adk/tests/unittests/tools/retrieval/__init__.py +14 -0
- google/adk/tests/unittests/tools/retrieval/test_vertex_ai_rag_retrieval.py +147 -0
- google/adk/tests/unittests/tools/test_agent_tool.py +167 -0
- google/adk/tests/unittests/tools/test_base_tool.py +141 -0
- google/adk/tests/unittests/tools/test_build_function_declaration.py +277 -0
- google/adk/tests/unittests/utils.py +304 -0
- google/adk/tools/__init__.py +51 -0
- google/adk/tools/_automatic_function_calling_util.py +346 -0
- google/adk/tools/agent_tool.py +176 -0
- google/adk/tools/apihub_tool/__init__.py +19 -0
- google/adk/tools/apihub_tool/apihub_toolset.py +209 -0
- google/adk/tools/apihub_tool/clients/__init__.py +13 -0
- google/adk/tools/apihub_tool/clients/apihub_client.py +332 -0
- google/adk/tools/apihub_tool/clients/secret_client.py +115 -0
- google/adk/tools/application_integration_tool/__init__.py +19 -0
- google/adk/tools/application_integration_tool/application_integration_toolset.py +230 -0
- google/adk/tools/application_integration_tool/clients/connections_client.py +903 -0
- google/adk/tools/application_integration_tool/clients/integration_client.py +253 -0
- google/adk/tools/base_tool.py +144 -0
- google/adk/tools/built_in_code_execution_tool.py +59 -0
- google/adk/tools/crewai_tool.py +72 -0
- google/adk/tools/example_tool.py +62 -0
- google/adk/tools/exit_loop_tool.py +23 -0
- google/adk/tools/function_parameter_parse_util.py +307 -0
- google/adk/tools/function_tool.py +87 -0
- google/adk/tools/get_user_choice_tool.py +28 -0
- google/adk/tools/google_api_tool/__init__.py +14 -0
- google/adk/tools/google_api_tool/google_api_tool.py +59 -0
- google/adk/tools/google_api_tool/google_api_tool_set.py +107 -0
- google/adk/tools/google_api_tool/google_api_tool_sets.py +55 -0
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +521 -0
- google/adk/tools/google_search_tool.py +68 -0
- google/adk/tools/langchain_tool.py +86 -0
- google/adk/tools/load_artifacts_tool.py +113 -0
- google/adk/tools/load_memory_tool.py +58 -0
- google/adk/tools/load_web_page.py +41 -0
- google/adk/tools/long_running_tool.py +39 -0
- google/adk/tools/mcp_tool/__init__.py +42 -0
- google/adk/tools/mcp_tool/conversion_utils.py +161 -0
- google/adk/tools/mcp_tool/mcp_tool.py +113 -0
- google/adk/tools/mcp_tool/mcp_toolset.py +272 -0
- google/adk/tools/openapi_tool/__init__.py +21 -0
- google/adk/tools/openapi_tool/auth/__init__.py +19 -0
- google/adk/tools/openapi_tool/auth/auth_helpers.py +498 -0
- google/adk/tools/openapi_tool/auth/credential_exchangers/__init__.py +25 -0
- google/adk/tools/openapi_tool/auth/credential_exchangers/auto_auth_credential_exchanger.py +105 -0
- google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +55 -0
- google/adk/tools/openapi_tool/auth/credential_exchangers/oauth2_exchanger.py +117 -0
- google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +97 -0
- google/adk/tools/openapi_tool/common/__init__.py +19 -0
- google/adk/tools/openapi_tool/common/common.py +300 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +32 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +231 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +144 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +260 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +496 -0
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +268 -0
- google/adk/tools/preload_memory_tool.py +72 -0
- google/adk/tools/retrieval/__init__.py +36 -0
- google/adk/tools/retrieval/base_retrieval_tool.py +37 -0
- google/adk/tools/retrieval/files_retrieval.py +33 -0
- google/adk/tools/retrieval/llama_index_retrieval.py +41 -0
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +107 -0
- google/adk/tools/tool_context.py +90 -0
- google/adk/tools/toolbox_tool.py +46 -0
- google/adk/tools/transfer_to_agent_tool.py +21 -0
- google/adk/tools/vertex_ai_search_tool.py +96 -0
- google/adk/version.py +16 -0
- google_adk-0.0.1.dist-info/LICENSE.txt → google_adk-0.0.2.dist-info/LICENSE +32 -0
- google_adk-0.0.2.dist-info/METADATA +73 -0
- google_adk-0.0.2.dist-info/RECORD +308 -0
- {google_adk-0.0.1.dist-info → google_adk-0.0.2.dist-info}/WHEEL +1 -2
- google_adk-0.0.2.dist-info/entry_points.txt +3 -0
- agent_kit/__init__.py +0 -0
- google_adk-0.0.1.dist-info/METADATA +0 -15
- google_adk-0.0.1.dist-info/RECORD +0 -6
- google_adk-0.0.1.dist-info/top_level.txt +0 -1
@@ -0,0 +1,209 @@
|
|
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
|
+
|
16
|
+
from typing import Dict, List, Optional
|
17
|
+
|
18
|
+
import yaml
|
19
|
+
|
20
|
+
from ...auth.auth_credential import AuthCredential
|
21
|
+
from ...auth.auth_schemes import AuthScheme
|
22
|
+
from ..openapi_tool.common.common import to_snake_case
|
23
|
+
from ..openapi_tool.openapi_spec_parser.openapi_toolset import OpenAPIToolset
|
24
|
+
from ..openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
|
25
|
+
from .clients.apihub_client import APIHubClient
|
26
|
+
|
27
|
+
|
28
|
+
class APIHubToolset:
|
29
|
+
"""APIHubTool generates tools from a given API Hub resource.
|
30
|
+
|
31
|
+
Examples:
|
32
|
+
|
33
|
+
```
|
34
|
+
apihub_toolset = APIHubToolset(
|
35
|
+
apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
|
36
|
+
service_account_json="...",
|
37
|
+
)
|
38
|
+
|
39
|
+
# Get all available tools
|
40
|
+
agent = LlmAgent(tools=apihub_toolset.get_tools())
|
41
|
+
|
42
|
+
# Get a specific tool
|
43
|
+
agent = LlmAgent(tools=[
|
44
|
+
...
|
45
|
+
apihub_toolset.get_tool('my_tool'),
|
46
|
+
])
|
47
|
+
```
|
48
|
+
|
49
|
+
**apihub_resource_name** is the resource name from API Hub. It must include
|
50
|
+
API name, and can optionally include API version and spec name.
|
51
|
+
- If apihub_resource_name includes a spec resource name, the content of that
|
52
|
+
spec will be used for generating the tools.
|
53
|
+
- If apihub_resource_name includes only an api or a version name, the
|
54
|
+
first spec of the first version of that API will be used.
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(
|
58
|
+
self,
|
59
|
+
*,
|
60
|
+
# Parameters for fetching API Hub resource
|
61
|
+
apihub_resource_name: str,
|
62
|
+
access_token: Optional[str] = None,
|
63
|
+
service_account_json: Optional[str] = None,
|
64
|
+
# Parameters for the toolset itself
|
65
|
+
name: str = '',
|
66
|
+
description: str = '',
|
67
|
+
# Parameters for generating tools
|
68
|
+
lazy_load_spec=False,
|
69
|
+
auth_scheme: Optional[AuthScheme] = None,
|
70
|
+
auth_credential: Optional[AuthCredential] = None,
|
71
|
+
# Optionally, you can provide a custom API Hub client
|
72
|
+
apihub_client: Optional[APIHubClient] = None,
|
73
|
+
):
|
74
|
+
"""Initializes the APIHubTool with the given parameters.
|
75
|
+
|
76
|
+
Examples:
|
77
|
+
```
|
78
|
+
apihub_toolset = APIHubToolset(
|
79
|
+
apihub_resource_name="projects/test-project/locations/us-central1/apis/test-api",
|
80
|
+
service_account_json="...",
|
81
|
+
)
|
82
|
+
|
83
|
+
# Get all available tools
|
84
|
+
agent = LlmAgent(tools=apihub_toolset.get_tools())
|
85
|
+
|
86
|
+
# Get a specific tool
|
87
|
+
agent = LlmAgent(tools=[
|
88
|
+
...
|
89
|
+
apihub_toolset.get_tool('my_tool'),
|
90
|
+
])
|
91
|
+
```
|
92
|
+
|
93
|
+
**apihub_resource_name** is the resource name from API Hub. It must include
|
94
|
+
API name, and can optionally include API version and spec name.
|
95
|
+
- If apihub_resource_name includes a spec resource name, the content of that
|
96
|
+
spec will be used for generating the tools.
|
97
|
+
- If apihub_resource_name includes only an api or a version name, the
|
98
|
+
first spec of the first version of that API will be used.
|
99
|
+
|
100
|
+
Example:
|
101
|
+
* projects/xxx/locations/us-central1/apis/apiname/...
|
102
|
+
* https://console.cloud.google.com/apigee/api-hub/apis/apiname?project=xxx
|
103
|
+
|
104
|
+
Args:
|
105
|
+
apihub_resource_name: The resource name of the API in API Hub.
|
106
|
+
Example: `projects/test-project/locations/us-central1/apis/test-api`.
|
107
|
+
access_token: Google Access token. Generate with gcloud cli `gcloud auth
|
108
|
+
auth print-access-token`. Used for fetching API Specs from API Hub.
|
109
|
+
service_account_json: The service account config as a json string.
|
110
|
+
Required if not using default service credential. It is used for
|
111
|
+
creating the API Hub client and fetching the API Specs from API Hub.
|
112
|
+
apihub_client: Optional custom API Hub client.
|
113
|
+
name: Name of the toolset. Optional.
|
114
|
+
description: Description of the toolset. Optional.
|
115
|
+
auth_scheme: Auth scheme that applies to all the tool in the toolset.
|
116
|
+
auth_credential: Auth credential that applies to all the tool in the
|
117
|
+
toolset.
|
118
|
+
lazy_load_spec: If True, the spec will be loaded lazily when needed.
|
119
|
+
Otherwise, the spec will be loaded immediately and the tools will be
|
120
|
+
generated during initialization.
|
121
|
+
"""
|
122
|
+
self.name = name
|
123
|
+
self.description = description
|
124
|
+
self.apihub_resource_name = apihub_resource_name
|
125
|
+
self.lazy_load_spec = lazy_load_spec
|
126
|
+
self.apihub_client = apihub_client or APIHubClient(
|
127
|
+
access_token=access_token,
|
128
|
+
service_account_json=service_account_json,
|
129
|
+
)
|
130
|
+
|
131
|
+
self.generated_tools: Dict[str, RestApiTool] = {}
|
132
|
+
self.auth_scheme = auth_scheme
|
133
|
+
self.auth_credential = auth_credential
|
134
|
+
|
135
|
+
if not self.lazy_load_spec:
|
136
|
+
self._prepare_tools()
|
137
|
+
|
138
|
+
def get_tool(self, name: str) -> Optional[RestApiTool]:
|
139
|
+
"""Retrieves a specific tool by its name.
|
140
|
+
|
141
|
+
Example:
|
142
|
+
```
|
143
|
+
apihub_tool = apihub_toolset.get_tool('my_tool')
|
144
|
+
```
|
145
|
+
|
146
|
+
Args:
|
147
|
+
name: The name of the tool to retrieve.
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
The tool with the given name, or None if no such tool exists.
|
151
|
+
"""
|
152
|
+
if not self._are_tools_ready():
|
153
|
+
self._prepare_tools()
|
154
|
+
|
155
|
+
return self.generated_tools[name] if name in self.generated_tools else None
|
156
|
+
|
157
|
+
def get_tools(self) -> List[RestApiTool]:
|
158
|
+
"""Retrieves all available tools.
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
A list of all available RestApiTool objects.
|
162
|
+
"""
|
163
|
+
if not self._are_tools_ready():
|
164
|
+
self._prepare_tools()
|
165
|
+
|
166
|
+
return list(self.generated_tools.values())
|
167
|
+
|
168
|
+
def _are_tools_ready(self) -> bool:
|
169
|
+
return not self.lazy_load_spec or self.generated_tools
|
170
|
+
|
171
|
+
def _prepare_tools(self) -> str:
|
172
|
+
"""Fetches the spec from API Hub and generates the tools.
|
173
|
+
|
174
|
+
Returns:
|
175
|
+
True if the tools are ready, False otherwise.
|
176
|
+
"""
|
177
|
+
# For each API, get the first version and the first spec of that version.
|
178
|
+
spec = self.apihub_client.get_spec_content(self.apihub_resource_name)
|
179
|
+
self.generated_tools: Dict[str, RestApiTool] = {}
|
180
|
+
|
181
|
+
tools = self._parse_spec_to_tools(spec)
|
182
|
+
for tool in tools:
|
183
|
+
self.generated_tools[tool.name] = tool
|
184
|
+
|
185
|
+
def _parse_spec_to_tools(self, spec_str: str) -> List[RestApiTool]:
|
186
|
+
"""Parses the spec string to a list of RestApiTool.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
spec_str: The spec string to parse.
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
A list of RestApiTool objects.
|
193
|
+
"""
|
194
|
+
spec_dict = yaml.safe_load(spec_str)
|
195
|
+
if not spec_dict:
|
196
|
+
return []
|
197
|
+
|
198
|
+
self.name = self.name or to_snake_case(
|
199
|
+
spec_dict.get('info', {}).get('title', 'unnamed')
|
200
|
+
)
|
201
|
+
self.description = self.description or spec_dict.get('info', {}).get(
|
202
|
+
'description', ''
|
203
|
+
)
|
204
|
+
tools = OpenAPIToolset(
|
205
|
+
spec_dict=spec_dict,
|
206
|
+
auth_credential=self.auth_credential,
|
207
|
+
auth_scheme=self.auth_scheme,
|
208
|
+
).get_tools()
|
209
|
+
return tools
|
@@ -0,0 +1,13 @@
|
|
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.
|
@@ -0,0 +1,332 @@
|
|
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
|
+
from abc import ABC, abstractmethod
|
16
|
+
import base64
|
17
|
+
import json
|
18
|
+
from typing import Any, Dict, List, Optional, Tuple
|
19
|
+
from urllib.parse import parse_qs, urlparse
|
20
|
+
from google.auth import default as default_service_credential
|
21
|
+
from google.auth.transport.requests import Request
|
22
|
+
from google.oauth2 import service_account
|
23
|
+
import requests
|
24
|
+
|
25
|
+
|
26
|
+
class BaseAPIHubClient(ABC):
|
27
|
+
"""Base class for API Hub clients."""
|
28
|
+
|
29
|
+
@abstractmethod
|
30
|
+
def get_spec_content(self, resource_name: str) -> str:
|
31
|
+
"""From a given resource name, get the soec in the API Hub."""
|
32
|
+
raise NotImplementedError()
|
33
|
+
|
34
|
+
|
35
|
+
class APIHubClient(BaseAPIHubClient):
|
36
|
+
"""Client for interacting with the API Hub service."""
|
37
|
+
|
38
|
+
def __init__(
|
39
|
+
self,
|
40
|
+
*,
|
41
|
+
access_token: Optional[str] = None,
|
42
|
+
service_account_json: Optional[str] = None,
|
43
|
+
):
|
44
|
+
"""Initializes the APIHubClient.
|
45
|
+
|
46
|
+
You must set either access_token or service_account_json. This
|
47
|
+
credential is used for sending request to API Hub API.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
access_token: Google Access token. Generate with gcloud cli `gcloud auth
|
51
|
+
print-access-token`. Useful for local testing.
|
52
|
+
service_account_json: The service account configuration as a dictionary.
|
53
|
+
Required if not using default service credential.
|
54
|
+
"""
|
55
|
+
self.root_url = "https://apihub.googleapis.com/v1"
|
56
|
+
self.credential_cache = None
|
57
|
+
self.access_token, self.service_account = None, None
|
58
|
+
|
59
|
+
if access_token:
|
60
|
+
self.access_token = access_token
|
61
|
+
elif service_account_json:
|
62
|
+
self.service_account = service_account_json
|
63
|
+
|
64
|
+
def get_spec_content(self, path: str) -> str:
|
65
|
+
"""From a given path, get the first spec available in the API Hub.
|
66
|
+
|
67
|
+
- If path includes /apis/apiname, get the first spec of that API
|
68
|
+
- If path includes /apis/apiname/versions/versionname, get the first spec
|
69
|
+
of that API Version
|
70
|
+
- If path includes /apis/apiname/versions/versionname/specs/specname, return
|
71
|
+
that spec
|
72
|
+
|
73
|
+
Path can be resource name (projects/xxx/locations/us-central1/apis/apiname),
|
74
|
+
and URL from the UI
|
75
|
+
(https://console.cloud.google.com/apigee/api-hub/apis/apiname?project=xxx)
|
76
|
+
|
77
|
+
Args:
|
78
|
+
path: The path to the API, API Version, or API Spec.
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
The content of the first spec available in the API Hub.
|
82
|
+
"""
|
83
|
+
apihub_resource_name, api_version_resource_name, api_spec_resource_name = (
|
84
|
+
self._extract_resource_name(path)
|
85
|
+
)
|
86
|
+
|
87
|
+
if apihub_resource_name and not api_version_resource_name:
|
88
|
+
api = self.get_api(apihub_resource_name)
|
89
|
+
versions = api.get("versions", [])
|
90
|
+
if not versions:
|
91
|
+
raise ValueError(
|
92
|
+
f"No versions found in API Hub resource: {apihub_resource_name}"
|
93
|
+
)
|
94
|
+
api_version_resource_name = versions[0]
|
95
|
+
|
96
|
+
if api_version_resource_name and not api_spec_resource_name:
|
97
|
+
api_version = self.get_api_version(api_version_resource_name)
|
98
|
+
spec_resource_names = api_version.get("specs", [])
|
99
|
+
if not spec_resource_names:
|
100
|
+
raise ValueError(
|
101
|
+
f"No specs found in API Hub version: {api_version_resource_name}"
|
102
|
+
)
|
103
|
+
api_spec_resource_name = spec_resource_names[0]
|
104
|
+
|
105
|
+
if api_spec_resource_name:
|
106
|
+
spec_content = self._fetch_spec(api_spec_resource_name)
|
107
|
+
return spec_content
|
108
|
+
|
109
|
+
raise ValueError("No API Hub resource found in path: {path}")
|
110
|
+
|
111
|
+
def list_apis(self, project: str, location: str) -> List[Dict[str, Any]]:
|
112
|
+
"""Lists all APIs in the specified project and location.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
project: The Google Cloud project name.
|
116
|
+
location: The location of the API Hub resources (e.g., 'us-central1').
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
A list of API dictionaries, or an empty list if an error occurs.
|
120
|
+
"""
|
121
|
+
url = f"{self.root_url}/projects/{project}/locations/{location}/apis"
|
122
|
+
headers = {
|
123
|
+
"accept": "application/json, text/plain, */*",
|
124
|
+
"Authorization": f"Bearer {self._get_access_token()}",
|
125
|
+
}
|
126
|
+
response = requests.get(url, headers=headers)
|
127
|
+
response.raise_for_status()
|
128
|
+
apis = response.json().get("apis", [])
|
129
|
+
return apis
|
130
|
+
|
131
|
+
def get_api(self, api_resource_name: str) -> Dict[str, Any]:
|
132
|
+
"""Get API detail by API name.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
api_resource_name: Resource name of this API, like
|
136
|
+
projects/xxx/locations/us-central1/apis/apiname
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
An API and details in a dict.
|
140
|
+
"""
|
141
|
+
url = f"{self.root_url}/{api_resource_name}"
|
142
|
+
headers = {
|
143
|
+
"accept": "application/json, text/plain, */*",
|
144
|
+
"Authorization": f"Bearer {self._get_access_token()}",
|
145
|
+
}
|
146
|
+
response = requests.get(url, headers=headers)
|
147
|
+
response.raise_for_status()
|
148
|
+
apis = response.json()
|
149
|
+
return apis
|
150
|
+
|
151
|
+
def get_api_version(self, api_version_name: str) -> Dict[str, Any]:
|
152
|
+
"""Gets details of a specific API version.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
api_version_name: The resource name of the API version.
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
The API version details as a dictionary, or an empty dictionary if an
|
159
|
+
error occurs.
|
160
|
+
"""
|
161
|
+
url = f"{self.root_url}/{api_version_name}"
|
162
|
+
headers = {
|
163
|
+
"accept": "application/json, text/plain, */*",
|
164
|
+
"Authorization": f"Bearer {self._get_access_token()}",
|
165
|
+
}
|
166
|
+
response = requests.get(url, headers=headers)
|
167
|
+
response.raise_for_status()
|
168
|
+
return response.json()
|
169
|
+
|
170
|
+
def _fetch_spec(self, api_spec_resource_name: str) -> str:
|
171
|
+
"""Retrieves the content of a specific API specification.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
api_spec_resource_name: The resource name of the API spec.
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
The decoded content of the specification as a string, or an empty string
|
178
|
+
if an error occurs.
|
179
|
+
"""
|
180
|
+
url = f"{self.root_url}/{api_spec_resource_name}:contents"
|
181
|
+
headers = {
|
182
|
+
"accept": "application/json, text/plain, */*",
|
183
|
+
"Authorization": f"Bearer {self._get_access_token()}",
|
184
|
+
}
|
185
|
+
response = requests.get(url, headers=headers)
|
186
|
+
response.raise_for_status()
|
187
|
+
content_base64 = response.json().get("contents", "")
|
188
|
+
if content_base64:
|
189
|
+
content_decoded = base64.b64decode(content_base64).decode("utf-8")
|
190
|
+
return content_decoded
|
191
|
+
else:
|
192
|
+
return ""
|
193
|
+
|
194
|
+
def _extract_resource_name(self, url_or_path: str) -> Tuple[str, str, str]:
|
195
|
+
"""Extracts the resource names of an API, API Version, and API Spec from a given URL or path.
|
196
|
+
|
197
|
+
Args:
|
198
|
+
url_or_path: The URL (UI or resource) or path string.
|
199
|
+
|
200
|
+
Returns:
|
201
|
+
A dictionary containing the resource names:
|
202
|
+
{
|
203
|
+
"api_resource_name": "projects/*/locations/*/apis/*",
|
204
|
+
"api_version_resource_name":
|
205
|
+
"projects/*/locations/*/apis/*/versions/*",
|
206
|
+
"api_spec_resource_name":
|
207
|
+
"projects/*/locations/*/apis/*/versions/*/specs/*"
|
208
|
+
}
|
209
|
+
or raises ValueError if extraction fails.
|
210
|
+
|
211
|
+
Raises:
|
212
|
+
ValueError: If the URL or path is invalid or if required components
|
213
|
+
(project, location, api) are missing.
|
214
|
+
"""
|
215
|
+
|
216
|
+
query_params = None
|
217
|
+
try:
|
218
|
+
parsed_url = urlparse(url_or_path)
|
219
|
+
path = parsed_url.path
|
220
|
+
query_params = parse_qs(parsed_url.query)
|
221
|
+
|
222
|
+
# This is a path from UI. Remove unnecessary prefix.
|
223
|
+
if "api-hub/" in path:
|
224
|
+
path = path.split("api-hub")[1]
|
225
|
+
except Exception:
|
226
|
+
path = url_or_path
|
227
|
+
|
228
|
+
path_segments = [segment for segment in path.split("/") if segment]
|
229
|
+
|
230
|
+
project = None
|
231
|
+
location = None
|
232
|
+
api_id = None
|
233
|
+
version_id = None
|
234
|
+
spec_id = None
|
235
|
+
|
236
|
+
if "projects" in path_segments:
|
237
|
+
project_index = path_segments.index("projects")
|
238
|
+
if project_index + 1 < len(path_segments):
|
239
|
+
project = path_segments[project_index + 1]
|
240
|
+
elif query_params and "project" in query_params:
|
241
|
+
project = query_params["project"][0]
|
242
|
+
|
243
|
+
if not project:
|
244
|
+
raise ValueError(
|
245
|
+
"Project ID not found in URL or path in APIHubClient. Input path is"
|
246
|
+
f" '{url_or_path}'. Please make sure there is either"
|
247
|
+
" '/projects/PROJECT_ID' in the path or 'project=PROJECT_ID' query"
|
248
|
+
" param in the input."
|
249
|
+
)
|
250
|
+
|
251
|
+
if "locations" in path_segments:
|
252
|
+
location_index = path_segments.index("locations")
|
253
|
+
if location_index + 1 < len(path_segments):
|
254
|
+
location = path_segments[location_index + 1]
|
255
|
+
if not location:
|
256
|
+
raise ValueError(
|
257
|
+
"Location not found in URL or path in APIHubClient. Input path is"
|
258
|
+
f" '{url_or_path}'. Please make sure there is either"
|
259
|
+
" '/location/LOCATION_ID' in the path."
|
260
|
+
)
|
261
|
+
|
262
|
+
if "apis" in path_segments:
|
263
|
+
api_index = path_segments.index("apis")
|
264
|
+
if api_index + 1 < len(path_segments):
|
265
|
+
api_id = path_segments[api_index + 1]
|
266
|
+
if not api_id:
|
267
|
+
raise ValueError(
|
268
|
+
"API id not found in URL or path in APIHubClient. Input path is"
|
269
|
+
f" '{url_or_path}'. Please make sure there is either"
|
270
|
+
" '/apis/API_ID' in the path."
|
271
|
+
)
|
272
|
+
if "versions" in path_segments:
|
273
|
+
version_index = path_segments.index("versions")
|
274
|
+
if version_index + 1 < len(path_segments):
|
275
|
+
version_id = path_segments[version_index + 1]
|
276
|
+
|
277
|
+
if "specs" in path_segments:
|
278
|
+
spec_index = path_segments.index("specs")
|
279
|
+
if spec_index + 1 < len(path_segments):
|
280
|
+
spec_id = path_segments[spec_index + 1]
|
281
|
+
|
282
|
+
api_resource_name = f"projects/{project}/locations/{location}/apis/{api_id}"
|
283
|
+
api_version_resource_name = (
|
284
|
+
f"{api_resource_name}/versions/{version_id}" if version_id else None
|
285
|
+
)
|
286
|
+
api_spec_resource_name = (
|
287
|
+
f"{api_version_resource_name}/specs/{spec_id}"
|
288
|
+
if version_id and spec_id
|
289
|
+
else None
|
290
|
+
)
|
291
|
+
|
292
|
+
return (
|
293
|
+
api_resource_name,
|
294
|
+
api_version_resource_name,
|
295
|
+
api_spec_resource_name,
|
296
|
+
)
|
297
|
+
|
298
|
+
def _get_access_token(self) -> str:
|
299
|
+
"""Gets the access token for the service account.
|
300
|
+
|
301
|
+
Returns:
|
302
|
+
The access token.
|
303
|
+
"""
|
304
|
+
if self.access_token:
|
305
|
+
return self.access_token
|
306
|
+
|
307
|
+
if self.credential_cache and not self.credential_cache.expired:
|
308
|
+
return self.credential_cache.token
|
309
|
+
|
310
|
+
if self.service_account:
|
311
|
+
try:
|
312
|
+
credentials = service_account.Credentials.from_service_account_info(
|
313
|
+
json.loads(self.service_account),
|
314
|
+
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
315
|
+
)
|
316
|
+
except json.JSONDecodeError as e:
|
317
|
+
raise ValueError(f"Invalid service account JSON: {e}") from e
|
318
|
+
else:
|
319
|
+
try:
|
320
|
+
credentials, _ = default_service_credential()
|
321
|
+
except:
|
322
|
+
credentials = None
|
323
|
+
|
324
|
+
if not credentials:
|
325
|
+
raise ValueError(
|
326
|
+
"Please provide a service account or an access token to API Hub"
|
327
|
+
" client."
|
328
|
+
)
|
329
|
+
|
330
|
+
credentials.refresh(Request())
|
331
|
+
self.credential_cache = credentials
|
332
|
+
return credentials.token
|
@@ -0,0 +1,115 @@
|
|
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
|
+
import json
|
16
|
+
from typing import Optional
|
17
|
+
import google.auth
|
18
|
+
from google.auth import default as default_service_credential
|
19
|
+
import google.auth.transport.requests
|
20
|
+
from google.cloud import secretmanager
|
21
|
+
from google.oauth2 import service_account
|
22
|
+
|
23
|
+
|
24
|
+
class SecretManagerClient:
|
25
|
+
"""A client for interacting with Google Cloud Secret Manager.
|
26
|
+
|
27
|
+
This class provides a simplified interface for retrieving secrets from
|
28
|
+
Secret Manager, handling authentication using either a service account
|
29
|
+
JSON keyfile (passed as a string) or a pre-existing authorization token.
|
30
|
+
|
31
|
+
Attributes:
|
32
|
+
_credentials: Google Cloud credentials object (ServiceAccountCredentials
|
33
|
+
or Credentials).
|
34
|
+
_client: Secret Manager client instance.
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
service_account_json: Optional[str] = None,
|
40
|
+
auth_token: Optional[str] = None,
|
41
|
+
):
|
42
|
+
"""Initializes the SecretManagerClient.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
service_account_json: The content of a service account JSON keyfile (as
|
46
|
+
a string), not the file path. Must be valid JSON.
|
47
|
+
auth_token: An existing Google Cloud authorization token.
|
48
|
+
|
49
|
+
Raises:
|
50
|
+
ValueError: If neither `service_account_json` nor `auth_token` is
|
51
|
+
provided,
|
52
|
+
or if both are provided. Also raised if the service_account_json
|
53
|
+
is not valid JSON.
|
54
|
+
google.auth.exceptions.GoogleAuthError: If authentication fails.
|
55
|
+
"""
|
56
|
+
if service_account_json:
|
57
|
+
try:
|
58
|
+
credentials = service_account.Credentials.from_service_account_info(
|
59
|
+
json.loads(service_account_json)
|
60
|
+
)
|
61
|
+
except json.JSONDecodeError as e:
|
62
|
+
raise ValueError(f"Invalid service account JSON: {e}") from e
|
63
|
+
elif auth_token:
|
64
|
+
credentials = google.auth.credentials.Credentials(
|
65
|
+
token=auth_token,
|
66
|
+
refresh_token=None,
|
67
|
+
token_uri=None,
|
68
|
+
client_id=None,
|
69
|
+
client_secret=None,
|
70
|
+
)
|
71
|
+
request = google.auth.transport.requests.Request()
|
72
|
+
credentials.refresh(request)
|
73
|
+
else:
|
74
|
+
try:
|
75
|
+
credentials, _ = default_service_credential()
|
76
|
+
except Exception as e:
|
77
|
+
raise ValueError(
|
78
|
+
"'service_account_json' or 'auth_token' are both missing, and"
|
79
|
+
f" error occurred while trying to use default credentials: {e}"
|
80
|
+
) from e
|
81
|
+
|
82
|
+
if not credentials:
|
83
|
+
raise ValueError(
|
84
|
+
"Must provide either 'service_account_json' or 'auth_token', not both"
|
85
|
+
" or neither."
|
86
|
+
)
|
87
|
+
|
88
|
+
self._credentials = credentials
|
89
|
+
self._client = secretmanager.SecretManagerServiceClient(
|
90
|
+
credentials=self._credentials
|
91
|
+
)
|
92
|
+
|
93
|
+
def get_secret(self, resource_name: str) -> str:
|
94
|
+
"""Retrieves a secret from Google Cloud Secret Manager.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
resource_name: The full resource name of the secret, in the format
|
98
|
+
"projects/*/secrets/*/versions/*". Usually you want the "latest"
|
99
|
+
version, e.g.,
|
100
|
+
"projects/my-project/secrets/my-secret/versions/latest".
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
The secret payload as a string.
|
104
|
+
|
105
|
+
Raises:
|
106
|
+
google.api_core.exceptions.GoogleAPIError: If the Secret Manager API
|
107
|
+
returns an error (e.g., secret not found, permission denied).
|
108
|
+
Exception: For other unexpected errors.
|
109
|
+
"""
|
110
|
+
try:
|
111
|
+
response = self._client.access_secret_version(name=resource_name)
|
112
|
+
return response.payload.data.decode("UTF-8")
|
113
|
+
except Exception as e:
|
114
|
+
raise e # Re-raise the exception to allow for handling by the caller
|
115
|
+
# Consider logging the exception here before re-raising.
|
@@ -0,0 +1,19 @@
|
|
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
|
+
from .application_integration_toolset import ApplicationIntegrationToolset
|
16
|
+
|
17
|
+
__all__ = [
|
18
|
+
'ApplicationIntegrationToolset',
|
19
|
+
]
|