agentengine-sdk-python 0.7.0__py3-none-any.whl → 0.8.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.
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +36 -20
- agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
- ksadk/a2a/__init__.py +178 -11
- ksadk/a2a/bootstrap.py +334 -0
- ksadk/a2a/card.py +120 -0
- ksadk/a2a/context_store.py +203 -0
- ksadk/a2a/control_plane.py +818 -0
- ksadk/a2a/credential.py +99 -0
- ksadk/a2a/event_adapter.py +149 -0
- ksadk/a2a/executor.py +406 -89
- ksadk/a2a/external_transport.py +516 -0
- ksadk/a2a/identity.py +304 -0
- ksadk/a2a/ids.py +22 -0
- ksadk/a2a/langgraph.py +285 -0
- ksadk/a2a/resume_store.py +201 -0
- ksadk/a2a/routes.py +105 -0
- ksadk/a2a/server.py +107 -60
- ksadk/a2a/space_client.py +1101 -0
- ksadk/a2a/task_adapter.py +423 -0
- ksadk/a2a/task_event_dispatcher.py +167 -0
- ksadk/a2a/task_event_outbox.py +326 -0
- ksadk/a2a/task_store.py +206 -0
- ksadk/a2ui/__init__.py +27 -0
- ksadk/a2ui/core.py +186 -0
- ksadk/a2ui/models.py +166 -0
- ksadk/a2ui/renderer.py +198 -0
- ksadk/agui/__init__.py +9 -0
- ksadk/agui/a2ui_projection.py +92 -0
- ksadk/agui/agent.py +857 -0
- ksadk/agui/config.py +86 -0
- ksadk/agui/routes.py +55 -0
- ksadk/api/client.py +233 -191
- ksadk/builders/__init__.py +2 -0
- ksadk/builders/base.py +16 -14
- ksadk/builders/code_builder.py +318 -236
- ksadk/builders/container_builder.py +187 -167
- ksadk/builders/framework_requirements.py +29 -11
- ksadk/builders/ks3_uploader.py +58 -27
- ksadk/builders/managed_runtime_builder.py +145 -0
- ksadk/builders/mcp_builder.py +91 -92
- ksadk/builders/requirements_utils.py +1 -5
- ksadk/cli/__init__.py +89 -171
- ksadk/cli/cmd_a2a.py +254 -70
- ksadk/cli/cmd_agent.py +19 -5
- ksadk/cli/cmd_build.py +158 -29
- ksadk/cli/cmd_completion.py +47 -35
- ksadk/cli/cmd_config.py +267 -198
- ksadk/cli/cmd_create.py +500 -720
- ksadk/cli/cmd_dashboard.py +120 -45
- ksadk/cli/cmd_deploy.py +117 -58
- ksadk/cli/cmd_destroy.py +44 -14
- ksadk/cli/cmd_files.py +63 -24
- ksadk/cli/cmd_hermes.py +168 -89
- ksadk/cli/cmd_invoke.py +184 -122
- ksadk/cli/cmd_launch.py +64 -28
- ksadk/cli/cmd_mcp.py +98 -64
- ksadk/cli/cmd_model.py +14 -5
- ksadk/cli/cmd_openclaw.py +483 -227
- ksadk/cli/cmd_replay.py +71 -0
- ksadk/cli/cmd_run.py +33 -10
- ksadk/cli/cmd_status.py +80 -31
- ksadk/cli/cmd_version.py +20 -13
- ksadk/cli/cmd_web.py +89 -13
- ksadk/cli/deploy_utils.py +20 -20
- ksadk/cli/dry_run.py +5 -3
- ksadk/cli/error_utils.py +37 -12
- ksadk/cli/global_options.py +1 -2
- ksadk/cli/local_runtime.py +1 -2
- ksadk/cli/model_catalog.py +6 -2
- ksadk/cli/network_options.py +34 -14
- ksadk/cli/resource_common.py +6 -2
- ksadk/cli/storage.py +0 -1
- ksadk/cli/ui.py +5 -3
- ksadk/cli/workflow_common.py +51 -17
- ksadk/codex/__init__.py +12 -0
- ksadk/codex/client.py +408 -0
- ksadk/codex/phase.py +143 -0
- ksadk/codex/runtime.py +449 -0
- ksadk/common/aicp_env.py +0 -1
- ksadk/common/auth.py +2 -2
- ksadk/common/constants.py +1 -3
- ksadk/common/llm_utils.py +17 -13
- ksadk/compat/__init__.py +5 -0
- ksadk/compat/adk_compat.py +200 -0
- ksadk/compat/copilotkit_a2ui.py +330 -0
- ksadk/configs/__init__.py +12 -12
- ksadk/configs/env_registry.py +500 -100
- ksadk/configs/global_config.py +2 -1
- ksadk/configs/settings.py +151 -124
- ksadk/conversations/__init__.py +7 -5
- ksadk/conversations/attachment_storage.py +16 -11
- ksadk/conversations/attachments.py +44 -36
- ksadk/conversations/compaction_pipeline.py +12 -12
- ksadk/conversations/compaction_prompt.py +9 -5
- ksadk/conversations/context.py +221 -6
- ksadk/conversations/message_projection.py +438 -51
- ksadk/conversations/model_context.py +12 -6
- ksadk/conversations/model_options.py +0 -1
- ksadk/conversations/normalize.py +20 -16
- ksadk/conversations/reasoning_markup.py +1 -1
- ksadk/conversations/runtime.py +339 -5255
- ksadk/conversations/runtime_compaction.py +333 -0
- ksadk/conversations/runtime_constants.py +27 -0
- ksadk/conversations/runtime_governance.py +185 -0
- ksadk/conversations/runtime_input.py +652 -0
- ksadk/conversations/runtime_invocation.py +395 -0
- ksadk/conversations/runtime_metadata.py +475 -0
- ksadk/conversations/runtime_observability.py +355 -0
- ksadk/conversations/runtime_payloads.py +272 -0
- ksadk/conversations/runtime_persistence.py +440 -0
- ksadk/conversations/runtime_preparation.py +418 -0
- ksadk/conversations/runtime_resume.py +816 -0
- ksadk/conversations/runtime_stream_events.py +902 -0
- ksadk/conversations/runtime_streaming.py +547 -0
- ksadk/conversations/semantic_summary.py +17 -5
- ksadk/conversations/session_title.py +6 -3
- ksadk/deployment/__init__.py +15 -16
- ksadk/deployment/agent_access.py +12 -23
- ksadk/deployment/base.py +51 -38
- ksadk/deployment/manager.py +63 -65
- ksadk/deployment/providers/serverless.py +309 -202
- ksadk/deployment/registry.py +38 -32
- ksadk/deployment/state.py +11 -11
- ksadk/deployment/ui_config.py +1 -1
- ksadk/detection/__init__.py +1 -1
- ksadk/detection/detector.py +146 -83
- ksadk/detection/mcp_detector.py +9 -6
- ksadk/events/__init__.py +25 -0
- ksadk/events/parser.py +191 -0
- ksadk/events/replay.py +37 -0
- ksadk/events/runtime_event.py +264 -0
- ksadk/events/store.py +317 -0
- ksadk/harness/__init__.py +34 -0
- ksadk/harness/app.py +278 -0
- ksadk/harness/config.py +181 -0
- ksadk/harness/reasoner.py +105 -0
- ksadk/harness/runner.py +211 -0
- ksadk/harness/sandbox.py +141 -0
- ksadk/harness/tools.py +222 -0
- ksadk/hermes_terminal.py +23 -12
- ksadk/identity/resolver.py +15 -9
- ksadk/ids.py +32 -0
- ksadk/knowledge_base/adk_tool.py +1 -1
- ksadk/knowledge_base/client.py +23 -29
- ksadk/managed_runtime.py +188 -0
- ksadk/markdown.py +5 -2
- ksadk/mcp_runtime/__init__.py +9 -6
- ksadk/memory/__init__.py +1 -1
- ksadk/memory/adk/__init__.py +2 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
- ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
- ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
- ksadk/memory/adk/long_term_memory.py +31 -35
- ksadk/memory/adk/resilient_session_service.py +4 -5
- ksadk/memory/adk/short_term_memory.py +12 -21
- ksadk/memory/adk_tool.py +3 -2
- ksadk/memory/backends/base.py +19 -19
- ksadk/memory/backends/memory.py +24 -25
- ksadk/memory/backends/redis.py +31 -30
- ksadk/memory/langchain_tool.py +2 -1
- ksadk/memory/manager.py +52 -47
- ksadk/memory/service.py +10 -8
- ksadk/model_policy.py +15 -7
- ksadk/model_proxy/__init__.py +51 -0
- ksadk/model_proxy/bootstrap.py +80 -0
- ksadk/model_proxy/cache.py +165 -0
- ksadk/model_proxy/config.py +45 -0
- ksadk/model_proxy/demo_webui.py +115 -0
- ksadk/model_proxy/detect.py +145 -0
- ksadk/model_proxy/gate.py +68 -0
- ksadk/model_proxy/namespace.py +173 -0
- ksadk/model_proxy/protocol_proxy.py +22 -0
- ksadk/model_proxy/server.py +300 -0
- ksadk/model_proxy/transform.py +691 -0
- ksadk/openclaw_gateway.py +29 -14
- ksadk/runners/adk_runner.py +345 -120
- ksadk/runners/base_runner.py +46 -29
- ksadk/runners/codex_runner.py +132 -0
- ksadk/runners/deepagents_runner.py +0 -1
- ksadk/runners/factory.py +17 -5
- ksadk/runners/langchain_runner.py +29 -742
- ksadk/runners/langgraph_runner.py +352 -90
- ksadk/runners/patch_langchain.py +16 -16
- ksadk/runners/remote_runner.py +134 -33
- ksadk/runners/usage_accumulator.py +1 -0
- ksadk/runners/utils/langfuse.py +3 -3
- ksadk/runners/utils/loader.py +7 -7
- ksadk/runtime/__init__.py +41 -0
- ksadk/runtime/adapter.py +345 -0
- ksadk/runtime/framework_adapters.py +124 -0
- ksadk/runtime/preprocessing.py +118 -0
- ksadk/runtime/runner_adapter.py +873 -0
- ksadk/runtime_context.py +18 -3
- ksadk/runtime_state.py +0 -1
- ksadk/sandbox/__init__.py +4 -1
- ksadk/sandbox/backends/__init__.py +4 -1
- ksadk/sandbox/backends/e2b.py +13 -5
- ksadk/sandbox/backends/local_process.py +11 -3
- ksadk/sandbox/base.py +10 -15
- ksadk/sandbox/factory.py +14 -4
- ksadk/sandbox/registry.py +57 -6
- ksadk/server/api_models.py +15 -5
- ksadk/server/app.py +455 -3885
- ksadk/server/composition.py +21 -0
- ksadk/server/factory.py +624 -0
- ksadk/server/routes/__init__.py +1 -0
- ksadk/server/routes/checkpoint_resolution.py +116 -0
- ksadk/server/routes/common.py +713 -0
- ksadk/server/routes/control.py +327 -0
- ksadk/server/routes/dependencies.py +98 -0
- ksadk/server/routes/misc.py +448 -0
- ksadk/server/routes/models.py +443 -0
- ksadk/server/routes/openai_compat.py +207 -0
- ksadk/server/routes/projection.py +795 -0
- ksadk/server/routes/routers.py +52 -0
- ksadk/server/routes/run.py +726 -0
- ksadk/server/routes/sessions.py +649 -0
- ksadk/server/routes/streaming.py +223 -0
- ksadk/server/routes/workspace.py +381 -0
- ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
- ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
- ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
- ksadk/server/static/assets/{arc-BA13sHc9.js → arc-CFKwSk_B.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
- ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-BK8paA20.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
- ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-6CVKuTAe.js} +1 -1
- ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-BUHRxvDG.js} +1 -1
- ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-BZ9n9AjO.js} +1 -1
- ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BibcG1V9.js} +1 -1
- ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-Bs-jQcRX.js} +1 -1
- ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-C2xjE7cO.js} +1 -1
- ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-C7-3jInC.js} +1 -1
- ksadk/server/static/assets/{dist-D-xOD048.js → dist-C99THtIA.js} +1 -1
- ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-CGcp2ex8.js} +1 -1
- ksadk/server/static/assets/{dist-DyOkZmze.js → dist-CVQRZjPu.js} +1 -1
- ksadk/server/static/assets/{dist-BssVmjIo.js → dist-Cf23NY4L.js} +1 -1
- ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-CgF3fj15.js} +1 -1
- ksadk/server/static/assets/{dist-2hInLS_J.js → dist-Cwt7koer.js} +1 -1
- ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-D82mtD2s.js} +1 -1
- ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-DF06ORQo.js} +1 -1
- ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-DHn6nBzr.js} +1 -1
- ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-Djgl2wVZ.js} +1 -1
- ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-DlzCRy0S.js} +1 -1
- ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-DqNnNFrw.js} +1 -1
- ksadk/server/static/assets/{dist-D57wnGWd.js → dist-DyvYLMW9.js} +1 -1
- ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-eoW6tZXA.js} +1 -1
- ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-ivOya1LD.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
- ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
- ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
- ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-DATssjp1.js} +1 -1
- ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-CvMBw9cA.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
- ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
- ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/server/terminal_sessions.py +111 -58
- ksadk/sessions/__init__.py +69 -19
- ksadk/sessions/base.py +31 -11
- ksadk/sessions/continuity.py +2 -3
- ksadk/sessions/in_memory.py +46 -2
- ksadk/sessions/local_service.py +148 -17
- ksadk/sessions/postgres_service.py +143 -25
- ksadk/sessions/resilient.py +36 -8
- ksadk/skills/models.py +17 -5
- ksadk/skills/package_store.py +5 -2
- ksadk/skills/runtime/agent.py +20 -5
- ksadk/skills/runtime/artifacts.py +1 -5
- ksadk/skills/runtime/backends/e2b.py +10 -4
- ksadk/skills/runtime/backends/local.py +15 -3
- ksadk/skills/runtime/base.py +1 -2
- ksadk/skills/runtime/executor.py +10 -3
- ksadk/skills/runtime/factory.py +4 -2
- ksadk/skills/runtime/loader.py +1 -2
- ksadk/skills/runtime/registry.py +15 -10
- ksadk/skills/runtime/request.py +3 -1
- ksadk/skills/service_client.py +38 -17
- ksadk/skills/tool_defs.py +15 -10
- ksadk/terminal_exec_policy.py +3 -4
- ksadk/tools/gateway.py +92 -15
- ksadk/tools/result_budget.py +12 -4
- ksadk/toolsets/__init__.py +134 -36
- ksadk/toolsets/platform.py +21 -5
- ksadk/toolsets/sandbox.py +82 -16
- ksadk/toolsets/skills.py +200 -39
- ksadk/toolsets/web.py +123 -26
- ksadk/toolsets/workspace.py +212 -101
- ksadk/tracing/exporters/inmemory_exporter.py +62 -51
- ksadk/tracing/exporters/langfuse_exporter.py +91 -76
- ksadk/tracing/setup.py +69 -53
- ksadk/tracing/span_utils.py +5 -2
- ksadk/tui/loop.py +78 -30
- ksadk/tui/stream_render.py +1 -0
- ksadk/ui_config.py +5 -3
- ksadk/version.py +1 -1
- ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
- agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
- ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
- ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
- ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
- ksadk/server/static/assets/channel-CTuydARX.js +0 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
- ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
- ksadk/server/static/assets/index-DAURbCsP.css +0 -1
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
- ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
- ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""Lease-based external A2A transport boundary owned by the Runtime network guard."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import ipaddress
|
|
7
|
+
import socket
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from collections.abc import Awaitable, Sequence
|
|
10
|
+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, AsyncIterator, Callable
|
|
13
|
+
from urllib.parse import urlsplit
|
|
14
|
+
|
|
15
|
+
import httpcore
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from ksadk.a2a.control_plane import A2ARouteInterface
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class A2ATransportLease:
|
|
23
|
+
"""A single validated external route operation."""
|
|
24
|
+
|
|
25
|
+
httpx_client: httpx.AsyncClient
|
|
26
|
+
effective_interface: A2ARouteInterface
|
|
27
|
+
route_kind: str
|
|
28
|
+
policy_revision: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class A2AExternalTransport(ABC):
|
|
32
|
+
"""SSRF-safe external transport supplied by the Runtime network guard."""
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def open_for_route(
|
|
36
|
+
self,
|
|
37
|
+
route: A2ARouteInterface,
|
|
38
|
+
*,
|
|
39
|
+
route_kind: str,
|
|
40
|
+
) -> AbstractAsyncContextManager[A2ATransportLease]:
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class A2ARouteOpener(ABC):
|
|
45
|
+
"""Platform network guard operation that has completed DNS/VPC policy validation."""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def open_validated_route(
|
|
49
|
+
self,
|
|
50
|
+
route: A2ARouteInterface,
|
|
51
|
+
*,
|
|
52
|
+
route_kind: str,
|
|
53
|
+
) -> AbstractAsyncContextManager[A2ATransportLease]:
|
|
54
|
+
raise NotImplementedError
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CallableA2ARouteOpener(A2ARouteOpener):
|
|
58
|
+
"""Adapter for an injected Runtime network guard operation."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
open_route: Callable[..., AbstractAsyncContextManager[A2ATransportLease]],
|
|
63
|
+
) -> None:
|
|
64
|
+
self._open_route = open_route
|
|
65
|
+
|
|
66
|
+
def open_validated_route(
|
|
67
|
+
self,
|
|
68
|
+
route: A2ARouteInterface,
|
|
69
|
+
*,
|
|
70
|
+
route_kind: str,
|
|
71
|
+
) -> AbstractAsyncContextManager[A2ATransportLease]:
|
|
72
|
+
return self._open_route(route, route_kind=route_kind)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class GuardedA2AExternalTransport(A2AExternalTransport):
|
|
76
|
+
"""Validates a Runtime network-guard lease before exposing its HTTP client."""
|
|
77
|
+
|
|
78
|
+
def __init__(self, route_opener: A2ARouteOpener) -> None:
|
|
79
|
+
self._route_opener = route_opener
|
|
80
|
+
|
|
81
|
+
@asynccontextmanager
|
|
82
|
+
async def open_for_route(
|
|
83
|
+
self,
|
|
84
|
+
route: A2ARouteInterface,
|
|
85
|
+
*,
|
|
86
|
+
route_kind: str,
|
|
87
|
+
) -> AsyncIterator[A2ATransportLease]:
|
|
88
|
+
if route_kind not in {"external_public", "external_vpc"}:
|
|
89
|
+
raise ValueError(f"external transport does not support route kind {route_kind!r}")
|
|
90
|
+
async with self._route_opener.open_validated_route(
|
|
91
|
+
route,
|
|
92
|
+
route_kind=route_kind,
|
|
93
|
+
) as lease:
|
|
94
|
+
self._validate_lease(route, route_kind, lease)
|
|
95
|
+
yield lease
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _validate_lease(
|
|
99
|
+
requested: A2ARouteInterface,
|
|
100
|
+
route_kind: str,
|
|
101
|
+
lease: A2ATransportLease,
|
|
102
|
+
) -> None:
|
|
103
|
+
if lease.route_kind != route_kind:
|
|
104
|
+
raise ValueError("network guard lease route kind does not match requested route")
|
|
105
|
+
if not lease.policy_revision.strip():
|
|
106
|
+
raise ValueError("network guard lease is missing policy revision")
|
|
107
|
+
if _canonical_url(lease.effective_interface.url) != _canonical_url(requested.url):
|
|
108
|
+
raise ValueError("network guard lease route does not match requested route")
|
|
109
|
+
if lease.httpx_client.follow_redirects:
|
|
110
|
+
raise ValueError("external A2A transport must not follow redirects automatically")
|
|
111
|
+
if getattr(lease.httpx_client, "_trust_env", None) is not False:
|
|
112
|
+
raise ValueError("external A2A transport must disable environment proxies")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
_RUNTIME_LOCAL_POLICY_REVISION = "runtime-local-dns-pinned-v1"
|
|
116
|
+
_EXTERNAL_CONNECT_TIMEOUT_SECONDS = 5.0
|
|
117
|
+
_EXTERNAL_READ_TIMEOUT_SECONDS = 30.0
|
|
118
|
+
_EXTERNAL_OPERATION_TIMEOUT_SECONDS = 300.0
|
|
119
|
+
_MAX_EXTERNAL_RESPONSE_BYTES = 8 * 1024 * 1024
|
|
120
|
+
_MAX_EXTERNAL_JSON_DEPTH = 64
|
|
121
|
+
_MAX_EXTERNAL_JSON_STRING_BYTES = 1024 * 1024
|
|
122
|
+
ERR_VPC_EGRESS_DIALER_REQUIRED = "A2A_VPC_EGRESS_DIALER_REQUIRED"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def _resolve_hostname(host: str, port: int) -> Sequence[str]:
|
|
126
|
+
"""Resolve one operation hostname without retaining an SDK-level DNS cache."""
|
|
127
|
+
|
|
128
|
+
loop = asyncio.get_running_loop()
|
|
129
|
+
results = await loop.getaddrinfo(host, port, type=socket.SOCK_STREAM)
|
|
130
|
+
addresses = tuple(dict.fromkeys(str(item[4][0]) for item in results))
|
|
131
|
+
if not addresses:
|
|
132
|
+
raise OSError(f"DNS lookup returned no addresses for {host!r}")
|
|
133
|
+
return addresses
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class _PinnedDNSNetworkBackend(httpcore.AsyncNetworkBackend):
|
|
137
|
+
"""Dials a verified IP while httpcore retains the hostname for TLS/SNI."""
|
|
138
|
+
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
delegate: httpcore.AsyncNetworkBackend,
|
|
142
|
+
*,
|
|
143
|
+
expected_hostname: str,
|
|
144
|
+
expected_port: int,
|
|
145
|
+
pinned_ip: str,
|
|
146
|
+
) -> None:
|
|
147
|
+
self._delegate = delegate
|
|
148
|
+
self._expected_hostname = expected_hostname
|
|
149
|
+
self._expected_port = expected_port
|
|
150
|
+
self._pinned_ip = pinned_ip
|
|
151
|
+
|
|
152
|
+
async def connect_tcp(
|
|
153
|
+
self,
|
|
154
|
+
host: str,
|
|
155
|
+
port: int,
|
|
156
|
+
timeout: float | None = None,
|
|
157
|
+
local_address: str | None = None,
|
|
158
|
+
socket_options: Any = None,
|
|
159
|
+
) -> httpcore.AsyncNetworkStream:
|
|
160
|
+
if host.lower() != self._expected_hostname or port != self._expected_port:
|
|
161
|
+
raise RuntimeError("external A2A transport attempted to dial an unapproved origin")
|
|
162
|
+
return await self._delegate.connect_tcp(
|
|
163
|
+
self._pinned_ip,
|
|
164
|
+
port,
|
|
165
|
+
timeout=timeout,
|
|
166
|
+
local_address=local_address,
|
|
167
|
+
socket_options=socket_options,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
async def connect_unix_socket(
|
|
171
|
+
self,
|
|
172
|
+
path: str,
|
|
173
|
+
timeout: float | None = None,
|
|
174
|
+
socket_options: Any = None,
|
|
175
|
+
) -> httpcore.AsyncNetworkStream:
|
|
176
|
+
return await self._delegate.connect_unix_socket(
|
|
177
|
+
path,
|
|
178
|
+
timeout=timeout,
|
|
179
|
+
socket_options=socket_options,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
async def sleep(self, seconds: float) -> None:
|
|
183
|
+
await self._delegate.sleep(seconds)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class _BoundedResponseStream(httpx.AsyncByteStream):
|
|
187
|
+
"""Enforces one operation deadline and a raw response-body limit."""
|
|
188
|
+
|
|
189
|
+
def __init__(
|
|
190
|
+
self,
|
|
191
|
+
delegate: httpx.AsyncByteStream,
|
|
192
|
+
*,
|
|
193
|
+
max_response_bytes: int,
|
|
194
|
+
deadline: float,
|
|
195
|
+
) -> None:
|
|
196
|
+
self._delegate = delegate
|
|
197
|
+
self._max_response_bytes = max_response_bytes
|
|
198
|
+
self._deadline = deadline
|
|
199
|
+
self._received_bytes = 0
|
|
200
|
+
self._json_limits = _JSONPayloadLimits()
|
|
201
|
+
|
|
202
|
+
async def __aiter__(self) -> AsyncIterator[bytes]:
|
|
203
|
+
iterator = self._delegate.__aiter__()
|
|
204
|
+
while True:
|
|
205
|
+
remaining = self._deadline - asyncio.get_running_loop().time()
|
|
206
|
+
if remaining <= 0:
|
|
207
|
+
await self.aclose()
|
|
208
|
+
raise httpx.ReadTimeout(
|
|
209
|
+
"external A2A operation exceeded its total response deadline"
|
|
210
|
+
)
|
|
211
|
+
try:
|
|
212
|
+
chunk = await asyncio.wait_for(anext(iterator), timeout=remaining)
|
|
213
|
+
except StopAsyncIteration:
|
|
214
|
+
return
|
|
215
|
+
except TimeoutError as exc:
|
|
216
|
+
await self.aclose()
|
|
217
|
+
raise httpx.ReadTimeout(
|
|
218
|
+
"external A2A operation exceeded its total response deadline"
|
|
219
|
+
) from exc
|
|
220
|
+
self._received_bytes += len(chunk)
|
|
221
|
+
if self._received_bytes > self._max_response_bytes:
|
|
222
|
+
await self.aclose()
|
|
223
|
+
raise httpx.RemoteProtocolError(
|
|
224
|
+
"external A2A response exceeds configured size limit"
|
|
225
|
+
)
|
|
226
|
+
self._json_limits.observe(chunk)
|
|
227
|
+
yield chunk
|
|
228
|
+
|
|
229
|
+
async def aclose(self) -> None:
|
|
230
|
+
await self._delegate.aclose()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class _JSONPayloadLimits:
|
|
234
|
+
"""Lightweight structural guard before the A2A SDK parses external JSON/SSE."""
|
|
235
|
+
|
|
236
|
+
def __init__(self) -> None:
|
|
237
|
+
self._depth = 0
|
|
238
|
+
self._in_string = False
|
|
239
|
+
self._escaped = False
|
|
240
|
+
self._string_bytes = 0
|
|
241
|
+
|
|
242
|
+
def observe(self, chunk: bytes) -> None:
|
|
243
|
+
for byte in chunk:
|
|
244
|
+
if self._in_string:
|
|
245
|
+
self._string_bytes += 1
|
|
246
|
+
if self._string_bytes > _MAX_EXTERNAL_JSON_STRING_BYTES:
|
|
247
|
+
raise httpx.RemoteProtocolError(
|
|
248
|
+
"external A2A JSON string exceeds 1 MiB"
|
|
249
|
+
)
|
|
250
|
+
if self._escaped:
|
|
251
|
+
self._escaped = False
|
|
252
|
+
elif byte == ord("\\"):
|
|
253
|
+
self._escaped = True
|
|
254
|
+
elif byte == ord('"'):
|
|
255
|
+
self._in_string = False
|
|
256
|
+
self._string_bytes = 0
|
|
257
|
+
continue
|
|
258
|
+
if byte == ord('"'):
|
|
259
|
+
self._in_string = True
|
|
260
|
+
self._string_bytes = 0
|
|
261
|
+
elif byte in (ord("{"), ord("[")):
|
|
262
|
+
self._depth += 1
|
|
263
|
+
if self._depth > _MAX_EXTERNAL_JSON_DEPTH:
|
|
264
|
+
raise httpx.RemoteProtocolError("external A2A JSON exceeds depth 64")
|
|
265
|
+
elif byte in (ord("}"), ord("]")):
|
|
266
|
+
self._depth = max(0, self._depth - 1)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class _OriginLockedRedirectRejectingTransport(httpx.AsyncBaseTransport):
|
|
270
|
+
"""Restricts a lease client to its resolved origin and rejects all redirects."""
|
|
271
|
+
|
|
272
|
+
def __init__(
|
|
273
|
+
self,
|
|
274
|
+
delegate: httpx.AsyncBaseTransport,
|
|
275
|
+
*,
|
|
276
|
+
hostname: str,
|
|
277
|
+
port: int,
|
|
278
|
+
max_response_bytes: int,
|
|
279
|
+
operation_timeout_seconds: float,
|
|
280
|
+
) -> None:
|
|
281
|
+
self._delegate = delegate
|
|
282
|
+
self._hostname = hostname
|
|
283
|
+
self._port = port
|
|
284
|
+
self._max_response_bytes = max_response_bytes
|
|
285
|
+
self._operation_timeout_seconds = operation_timeout_seconds
|
|
286
|
+
|
|
287
|
+
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
288
|
+
request_port = request.url.port or (443 if request.url.scheme == "https" else 80)
|
|
289
|
+
if (
|
|
290
|
+
request.url.scheme != "https"
|
|
291
|
+
or request.url.host.lower() != self._hostname
|
|
292
|
+
or request_port != self._port
|
|
293
|
+
):
|
|
294
|
+
raise RuntimeError("external A2A transport lease attempted to use an unapproved origin")
|
|
295
|
+
request.headers["Accept-Encoding"] = "identity"
|
|
296
|
+
deadline = asyncio.get_running_loop().time() + self._operation_timeout_seconds
|
|
297
|
+
try:
|
|
298
|
+
response = await asyncio.wait_for(
|
|
299
|
+
self._delegate.handle_async_request(request),
|
|
300
|
+
timeout=self._operation_timeout_seconds,
|
|
301
|
+
)
|
|
302
|
+
except TimeoutError as exc:
|
|
303
|
+
raise httpx.ReadTimeout(
|
|
304
|
+
"external A2A operation exceeded its total response deadline"
|
|
305
|
+
) from exc
|
|
306
|
+
if 300 <= response.status_code < 400:
|
|
307
|
+
await response.aclose()
|
|
308
|
+
raise httpx.RemoteProtocolError("A2A external redirect responses are not allowed")
|
|
309
|
+
declared_length = response.headers.get("content-length")
|
|
310
|
+
if declared_length is not None:
|
|
311
|
+
try:
|
|
312
|
+
exceeds_limit = int(declared_length) > self._max_response_bytes
|
|
313
|
+
except ValueError as exc:
|
|
314
|
+
await response.aclose()
|
|
315
|
+
raise httpx.RemoteProtocolError(
|
|
316
|
+
"external A2A response has an invalid Content-Length"
|
|
317
|
+
) from exc
|
|
318
|
+
if exceeds_limit:
|
|
319
|
+
await response.aclose()
|
|
320
|
+
raise httpx.RemoteProtocolError(
|
|
321
|
+
"external A2A response exceeds configured size limit"
|
|
322
|
+
)
|
|
323
|
+
if not isinstance(response.stream, httpx.AsyncByteStream):
|
|
324
|
+
await response.aclose()
|
|
325
|
+
raise RuntimeError("external A2A transport received a non-async response stream")
|
|
326
|
+
response.stream = _BoundedResponseStream(
|
|
327
|
+
response.stream,
|
|
328
|
+
max_response_bytes=self._max_response_bytes,
|
|
329
|
+
deadline=deadline,
|
|
330
|
+
)
|
|
331
|
+
return response
|
|
332
|
+
|
|
333
|
+
async def aclose(self) -> None:
|
|
334
|
+
await self._delegate.aclose()
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class RuntimeLocalA2AExternalTransport(A2AExternalTransport):
|
|
338
|
+
"""Runtime-local guarded HTTPS transport for public external Agents over NAT.
|
|
339
|
+
|
|
340
|
+
The Runtime resolves every external operation itself, rejects any non-global
|
|
341
|
+
DNS result, then pins the selected IP in the actual TCP backend. The HTTP
|
|
342
|
+
origin remains the original hostname so certificate validation, ``Host``,
|
|
343
|
+
and TLS SNI are not weakened by the pinning.
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
def __init__(
|
|
347
|
+
self,
|
|
348
|
+
*,
|
|
349
|
+
resolve_hostname: Callable[[str, int], Awaitable[Sequence[str]]] = _resolve_hostname,
|
|
350
|
+
network_backend: httpcore.AsyncNetworkBackend | None = None,
|
|
351
|
+
connect_timeout_seconds: float = _EXTERNAL_CONNECT_TIMEOUT_SECONDS,
|
|
352
|
+
read_timeout_seconds: float = _EXTERNAL_READ_TIMEOUT_SECONDS,
|
|
353
|
+
operation_timeout_seconds: float = _EXTERNAL_OPERATION_TIMEOUT_SECONDS,
|
|
354
|
+
max_response_bytes: int = _MAX_EXTERNAL_RESPONSE_BYTES,
|
|
355
|
+
) -> None:
|
|
356
|
+
if (
|
|
357
|
+
connect_timeout_seconds <= 0
|
|
358
|
+
or read_timeout_seconds <= 0
|
|
359
|
+
or operation_timeout_seconds <= 0
|
|
360
|
+
or max_response_bytes <= 0
|
|
361
|
+
):
|
|
362
|
+
raise ValueError("external A2A transport limits must be positive")
|
|
363
|
+
self._resolve_hostname = resolve_hostname
|
|
364
|
+
self._network_backend = network_backend or httpcore.AnyIOBackend()
|
|
365
|
+
self._connect_timeout_seconds = connect_timeout_seconds
|
|
366
|
+
self._read_timeout_seconds = read_timeout_seconds
|
|
367
|
+
self._operation_timeout_seconds = operation_timeout_seconds
|
|
368
|
+
self._max_response_bytes = max_response_bytes
|
|
369
|
+
|
|
370
|
+
@asynccontextmanager
|
|
371
|
+
async def open_for_route(
|
|
372
|
+
self,
|
|
373
|
+
route: A2ARouteInterface,
|
|
374
|
+
*,
|
|
375
|
+
route_kind: str,
|
|
376
|
+
) -> AsyncIterator[A2ATransportLease]:
|
|
377
|
+
hostname, port = self._validated_public_origin(route, route_kind=route_kind)
|
|
378
|
+
try:
|
|
379
|
+
resolved = await asyncio.wait_for(
|
|
380
|
+
self._resolve_hostname(hostname, port),
|
|
381
|
+
timeout=self._connect_timeout_seconds,
|
|
382
|
+
)
|
|
383
|
+
except TimeoutError as exc:
|
|
384
|
+
raise httpx.ConnectTimeout("external A2A DNS resolution timed out") from exc
|
|
385
|
+
pinned_ip = self._select_global_ip(resolved, hostname=hostname)
|
|
386
|
+
client = self._build_client(hostname=hostname, port=port, pinned_ip=pinned_ip)
|
|
387
|
+
try:
|
|
388
|
+
yield A2ATransportLease(
|
|
389
|
+
httpx_client=client,
|
|
390
|
+
effective_interface=route,
|
|
391
|
+
route_kind=route_kind,
|
|
392
|
+
policy_revision=_RUNTIME_LOCAL_POLICY_REVISION,
|
|
393
|
+
)
|
|
394
|
+
finally:
|
|
395
|
+
await client.aclose()
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def _validated_public_origin(
|
|
399
|
+
route: A2ARouteInterface,
|
|
400
|
+
*,
|
|
401
|
+
route_kind: str,
|
|
402
|
+
) -> tuple[str, int]:
|
|
403
|
+
if route_kind == "external_vpc":
|
|
404
|
+
raise RuntimeError(
|
|
405
|
+
f"{ERR_VPC_EGRESS_DIALER_REQUIRED}: external_vpc routes require a "
|
|
406
|
+
"platform-injected VPC dialer"
|
|
407
|
+
)
|
|
408
|
+
if route_kind != "external_public":
|
|
409
|
+
raise ValueError(
|
|
410
|
+
"RuntimeLocalA2AExternalTransport supports only external_public routes"
|
|
411
|
+
)
|
|
412
|
+
parsed = urlsplit(route.url)
|
|
413
|
+
try:
|
|
414
|
+
port = parsed.port
|
|
415
|
+
except ValueError as exc:
|
|
416
|
+
raise ValueError("external public A2A route has an invalid port") from exc
|
|
417
|
+
if (
|
|
418
|
+
parsed.scheme != "https"
|
|
419
|
+
or not parsed.hostname
|
|
420
|
+
or parsed.username is not None
|
|
421
|
+
or parsed.password is not None
|
|
422
|
+
or parsed.fragment
|
|
423
|
+
or parsed.netloc.rsplit("@", 1)[-1].endswith(":")
|
|
424
|
+
or port is not None and not 1 <= port <= 65535
|
|
425
|
+
):
|
|
426
|
+
raise ValueError("external public A2A route must be an absolute HTTPS URL")
|
|
427
|
+
try:
|
|
428
|
+
hostname = parsed.hostname.encode("idna").decode("ascii").lower()
|
|
429
|
+
except UnicodeError as exc:
|
|
430
|
+
raise ValueError("external public A2A route has an invalid hostname") from exc
|
|
431
|
+
return hostname, port or 443
|
|
432
|
+
|
|
433
|
+
@staticmethod
|
|
434
|
+
def _select_global_ip(addresses: Sequence[str], *, hostname: str) -> str:
|
|
435
|
+
if not addresses:
|
|
436
|
+
raise PermissionError(f"external public A2A DNS returned no addresses for {hostname!r}")
|
|
437
|
+
normalized: list[str] = []
|
|
438
|
+
for value in addresses:
|
|
439
|
+
try:
|
|
440
|
+
address = ipaddress.ip_address(value)
|
|
441
|
+
except ValueError as exc:
|
|
442
|
+
raise PermissionError(
|
|
443
|
+
f"external public A2A DNS returned an invalid address for {hostname!r}"
|
|
444
|
+
) from exc
|
|
445
|
+
if not address.is_global:
|
|
446
|
+
raise PermissionError(
|
|
447
|
+
"external public A2A DNS results must contain only globally routable IPs"
|
|
448
|
+
)
|
|
449
|
+
normalized.append(str(address))
|
|
450
|
+
return normalized[0]
|
|
451
|
+
|
|
452
|
+
def _build_client(self, *, hostname: str, port: int, pinned_ip: str) -> httpx.AsyncClient:
|
|
453
|
+
# httpx has no public network-backend hook. Rebuild its direct httpcore pool
|
|
454
|
+
# to preserve httpx's SSL configuration while replacing only TCP dialing.
|
|
455
|
+
base_transport = httpx.AsyncHTTPTransport(
|
|
456
|
+
trust_env=False,
|
|
457
|
+
http1=True,
|
|
458
|
+
http2=False,
|
|
459
|
+
limits=httpx.Limits(max_connections=1, max_keepalive_connections=0),
|
|
460
|
+
)
|
|
461
|
+
existing_pool = getattr(base_transport, "_pool", None)
|
|
462
|
+
if not isinstance(existing_pool, httpcore.AsyncConnectionPool):
|
|
463
|
+
raise RuntimeError("httpx direct async transport is unavailable for A2A DNS pinning")
|
|
464
|
+
base_transport._pool = httpcore.AsyncConnectionPool( # type: ignore[attr-defined]
|
|
465
|
+
ssl_context=existing_pool._ssl_context,
|
|
466
|
+
max_connections=1,
|
|
467
|
+
max_keepalive_connections=0,
|
|
468
|
+
keepalive_expiry=existing_pool._keepalive_expiry,
|
|
469
|
+
http1=True,
|
|
470
|
+
http2=False,
|
|
471
|
+
retries=0,
|
|
472
|
+
network_backend=_PinnedDNSNetworkBackend(
|
|
473
|
+
self._network_backend,
|
|
474
|
+
expected_hostname=hostname,
|
|
475
|
+
expected_port=port,
|
|
476
|
+
pinned_ip=pinned_ip,
|
|
477
|
+
),
|
|
478
|
+
)
|
|
479
|
+
transport = _OriginLockedRedirectRejectingTransport(
|
|
480
|
+
base_transport,
|
|
481
|
+
hostname=hostname,
|
|
482
|
+
port=port,
|
|
483
|
+
max_response_bytes=self._max_response_bytes,
|
|
484
|
+
operation_timeout_seconds=self._operation_timeout_seconds,
|
|
485
|
+
)
|
|
486
|
+
return httpx.AsyncClient(
|
|
487
|
+
transport=transport,
|
|
488
|
+
follow_redirects=False,
|
|
489
|
+
trust_env=False,
|
|
490
|
+
timeout=httpx.Timeout(
|
|
491
|
+
connect=self._connect_timeout_seconds,
|
|
492
|
+
read=self._read_timeout_seconds,
|
|
493
|
+
write=self._read_timeout_seconds,
|
|
494
|
+
pool=self._connect_timeout_seconds,
|
|
495
|
+
),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _canonical_url(value: str) -> tuple[str, str, int | None, str, str]:
|
|
500
|
+
parsed = urlsplit(value)
|
|
501
|
+
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
|
502
|
+
raise ValueError("external A2A route must use an absolute HTTP(S) URL")
|
|
503
|
+
default_port = 443 if parsed.scheme == "https" else 80
|
|
504
|
+
port = parsed.port or default_port
|
|
505
|
+
return parsed.scheme, parsed.hostname.lower(), port, parsed.path or "/", parsed.query
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
__all__ = [
|
|
509
|
+
"A2AExternalTransport",
|
|
510
|
+
"A2ARouteOpener",
|
|
511
|
+
"A2ATransportLease",
|
|
512
|
+
"CallableA2ARouteOpener",
|
|
513
|
+
"ERR_VPC_EGRESS_DIALER_REQUIRED",
|
|
514
|
+
"GuardedA2AExternalTransport",
|
|
515
|
+
"RuntimeLocalA2AExternalTransport",
|
|
516
|
+
]
|