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,713 @@
|
|
|
1
|
+
"""Shared runtime server helpers, integrated resources, and health routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import io
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import uuid
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Mapping, Optional
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
15
|
+
from fastapi.responses import FileResponse, Response
|
|
16
|
+
|
|
17
|
+
from ksadk.conversations.attachments import compact_attachment_result_for_session
|
|
18
|
+
from ksadk.conversations.model_context import normalize_model_metadata
|
|
19
|
+
from ksadk.runners.base_runner import BaseRunner
|
|
20
|
+
from ksadk.runtime_state import load_state as load_runtime_state
|
|
21
|
+
from ksadk.server.factory import (
|
|
22
|
+
RuntimeAppState,
|
|
23
|
+
bind_runtime_state,
|
|
24
|
+
get_runner,
|
|
25
|
+
get_state,
|
|
26
|
+
)
|
|
27
|
+
from ksadk.server.terminal_sessions import (
|
|
28
|
+
TerminalSessionManager,
|
|
29
|
+
native_terminal_supported,
|
|
30
|
+
register_terminal_routes,
|
|
31
|
+
)
|
|
32
|
+
from ksadk.sessions import Session
|
|
33
|
+
from ksadk.sessions.local_service import resolve_local_session_dir
|
|
34
|
+
from ksadk.ui_config import UI_PROFILE_CUSTOM, resolve_ui_config
|
|
35
|
+
from ksadk_runtime_common.workspace_files import (
|
|
36
|
+
create_workspace_files_router,
|
|
37
|
+
workspace_files_enabled,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from . import dependencies as deps
|
|
41
|
+
from .routers import health_meta_router
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
STATIC_DIR = Path(__file__).parent.parent / "static"
|
|
45
|
+
|
|
46
|
+
_RESERVED_UI_PATHS = {"/", "/chat", "/build", "/deploy"}
|
|
47
|
+
_CUSTOM_API_PROXY_ENV_KEYS = ("KSADK_USER_BACKEND_URL", "LUOLUO_USER_BACKEND_URL")
|
|
48
|
+
_HOP_BY_HOP_HEADERS = {
|
|
49
|
+
"connection",
|
|
50
|
+
"keep-alive",
|
|
51
|
+
"proxy-authenticate",
|
|
52
|
+
"proxy-authorization",
|
|
53
|
+
"te",
|
|
54
|
+
"trailer",
|
|
55
|
+
"transfer-encoding",
|
|
56
|
+
"upgrade",
|
|
57
|
+
"host",
|
|
58
|
+
"content-length",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_TEXT_MIME_PREFIXES = ("text/",)
|
|
62
|
+
_TEXT_MIME_TYPES = {
|
|
63
|
+
"application/json",
|
|
64
|
+
"application/pdf",
|
|
65
|
+
"application/xml",
|
|
66
|
+
"application/yaml",
|
|
67
|
+
"application/x-yaml",
|
|
68
|
+
"application/x-ndjson",
|
|
69
|
+
}
|
|
70
|
+
_TEXT_FILE_EXTENSIONS = {
|
|
71
|
+
".txt",
|
|
72
|
+
".md",
|
|
73
|
+
".markdown",
|
|
74
|
+
".json",
|
|
75
|
+
".yaml",
|
|
76
|
+
".yml",
|
|
77
|
+
".csv",
|
|
78
|
+
".tsv",
|
|
79
|
+
".log",
|
|
80
|
+
".py",
|
|
81
|
+
".js",
|
|
82
|
+
".ts",
|
|
83
|
+
".jsx",
|
|
84
|
+
".tsx",
|
|
85
|
+
".html",
|
|
86
|
+
".css",
|
|
87
|
+
".sql",
|
|
88
|
+
".xml",
|
|
89
|
+
".sh",
|
|
90
|
+
}
|
|
91
|
+
_MAX_INLINE_BASE64_CHARS = 4_000_000
|
|
92
|
+
_MAX_INLINE_TEXT_CHARS = 20_000
|
|
93
|
+
_MAX_REFERENCE_TEXT_BYTES = 3_000_000
|
|
94
|
+
_UPLOAD_URI_SCHEME = "ksadk-upload://"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _workspace_root_dir() -> Path:
|
|
98
|
+
return Path(resolve_local_session_dir()) / "workspace"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_NATIVE_TUI_FRAMEWORKS = {"hermes", "openclaw"}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _current_framework() -> str:
|
|
105
|
+
runner = get_state().runner
|
|
106
|
+
if not runner:
|
|
107
|
+
return ""
|
|
108
|
+
detection_type = getattr(getattr(runner, "detection_result", None), "type", None)
|
|
109
|
+
return str(getattr(detection_type, "value", detection_type) or "").strip().lower()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _build_native_terminal_capability(framework: str) -> dict[str, Any]:
|
|
113
|
+
enabled = (
|
|
114
|
+
native_terminal_supported()
|
|
115
|
+
and str(framework or "").strip().lower() in _NATIVE_TUI_FRAMEWORKS
|
|
116
|
+
)
|
|
117
|
+
return {
|
|
118
|
+
"Enabled": enabled,
|
|
119
|
+
"Mode": "tui" if enabled else None,
|
|
120
|
+
"Protocol": "ks-terminal.v1",
|
|
121
|
+
"Path": "/_ksadk/terminal/ws" if enabled else None,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _register_integrated_routers(app: FastAPI, state: RuntimeAppState) -> None:
|
|
126
|
+
"""装配随 app 走的内嵌 router(workspace files + terminal ws)。
|
|
127
|
+
|
|
128
|
+
goal-01:由模块级 ``app.include_router(...)`` 改为在 factory 的 configure
|
|
129
|
+
回调里调用,普通 app 与 HarnessApp 各自装配,互不共享。
|
|
130
|
+
"""
|
|
131
|
+
app.include_router(
|
|
132
|
+
create_workspace_files_router(
|
|
133
|
+
root_getter=_workspace_root_dir,
|
|
134
|
+
enabled_getter=lambda: workspace_files_enabled(default=True),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
state.terminal_manager = TerminalSessionManager(
|
|
138
|
+
workspace_root_getter=_workspace_root_dir,
|
|
139
|
+
framework_getter=_current_framework,
|
|
140
|
+
)
|
|
141
|
+
register_terminal_routes(
|
|
142
|
+
app,
|
|
143
|
+
state.terminal_manager,
|
|
144
|
+
bind_context=lambda: bind_runtime_state(state),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def set_runner(r: BaseRunner, *, loaded: bool = False):
|
|
149
|
+
"""Bind a runner and record whether its agent has already been loaded."""
|
|
150
|
+
state = get_state()
|
|
151
|
+
state.runner = r
|
|
152
|
+
state.runner_loaded = loaded
|
|
153
|
+
from ksadk.server.factory import wire_default_agui_for_runner
|
|
154
|
+
|
|
155
|
+
wire_default_agui_for_runner(state, r)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _ensure_runner_loaded() -> BaseRunner:
|
|
159
|
+
return get_runner()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _resolve_active_runner() -> BaseRunner:
|
|
163
|
+
return get_runner()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _prepare_runner_for_model(active_runner: BaseRunner, model: Optional[str]) -> None:
|
|
167
|
+
try:
|
|
168
|
+
active_runner.prepare_for_request(model)
|
|
169
|
+
except Exception as exc:
|
|
170
|
+
logger.warning("Runner 模型切换失败: %s", exc)
|
|
171
|
+
raise HTTPException(status_code=500, detail=str(exc) or "Runner 模型切换失败") from exc
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _resolve_current_model() -> tuple[Optional[str], Optional[str]]:
|
|
175
|
+
candidates = (
|
|
176
|
+
("OPENAI_MODEL_NAME", os.getenv("OPENAI_MODEL_NAME")),
|
|
177
|
+
("MODEL_NAME", os.getenv("MODEL_NAME")),
|
|
178
|
+
("COZE_MODEL_NAME", os.getenv("COZE_MODEL_NAME")),
|
|
179
|
+
)
|
|
180
|
+
for source, value in candidates:
|
|
181
|
+
model = str(value or "").strip()
|
|
182
|
+
if model:
|
|
183
|
+
return model, source
|
|
184
|
+
return None, None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _build_bootstrap_model_payload() -> Optional[dict[str, Any]]:
|
|
188
|
+
current_model, source = _resolve_current_model()
|
|
189
|
+
if not current_model:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
payload = dict(normalize_model_metadata({"id": current_model}))
|
|
193
|
+
payload["source"] = source
|
|
194
|
+
return payload
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _runner_project_dir() -> Path:
|
|
198
|
+
runner = get_state().runner
|
|
199
|
+
if runner and getattr(runner, "project_dir", None):
|
|
200
|
+
try:
|
|
201
|
+
return Path(str(runner.project_dir)).resolve()
|
|
202
|
+
except Exception:
|
|
203
|
+
pass
|
|
204
|
+
return Path(".").resolve()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _default_custom_ui_bundle_dir(project_dir: Path) -> Path:
|
|
208
|
+
return project_dir / "research-ui" / "dist"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _ui_state_with_env_fallback(state: dict[str, Any]) -> dict[str, Any]:
|
|
212
|
+
merged = dict(state or {})
|
|
213
|
+
env_fallbacks = {
|
|
214
|
+
"ui_profile": os.environ.get("KSADK_UI_PROFILE"),
|
|
215
|
+
"ui_path": os.environ.get("KSADK_UI_PATH"),
|
|
216
|
+
"ui_url": os.environ.get("KSADK_UI_URL"),
|
|
217
|
+
"ui_bundle_path": os.environ.get("KSADK_UI_BUNDLE_PATH"),
|
|
218
|
+
}
|
|
219
|
+
for key, value in env_fallbacks.items():
|
|
220
|
+
if value and not merged.get(key):
|
|
221
|
+
merged[key] = value
|
|
222
|
+
return merged
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _resolve_agent_ui_spec() -> dict[str, Any]:
|
|
226
|
+
project_dir = _runner_project_dir()
|
|
227
|
+
state = _ui_state_with_env_fallback(load_runtime_state(project_dir))
|
|
228
|
+
framework = _current_framework()
|
|
229
|
+
auto_custom_bundle_dir = _default_custom_ui_bundle_dir(project_dir)
|
|
230
|
+
if (
|
|
231
|
+
not state.get("ui_profile")
|
|
232
|
+
and not state.get("ui_path")
|
|
233
|
+
and not state.get("ui_url")
|
|
234
|
+
and (auto_custom_bundle_dir / "index.html").exists()
|
|
235
|
+
):
|
|
236
|
+
state["ui_profile"] = UI_PROFILE_CUSTOM
|
|
237
|
+
state["ui_path"] = "/"
|
|
238
|
+
state["ui_bundle_path"] = str(auto_custom_bundle_dir)
|
|
239
|
+
config = resolve_ui_config(
|
|
240
|
+
framework=framework,
|
|
241
|
+
state=state,
|
|
242
|
+
cli_profile=None,
|
|
243
|
+
cli_path=None,
|
|
244
|
+
cli_url=None,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if config.profile == UI_PROFILE_CUSTOM:
|
|
248
|
+
bundle_dir_value = state.get("ui_bundle_path") or state.get("ui_bundle_dir")
|
|
249
|
+
bundle_dir = None
|
|
250
|
+
if bundle_dir_value:
|
|
251
|
+
candidate = Path(str(bundle_dir_value))
|
|
252
|
+
if not candidate.is_absolute():
|
|
253
|
+
candidate = project_dir / candidate
|
|
254
|
+
if candidate.exists():
|
|
255
|
+
bundle_dir = candidate.resolve()
|
|
256
|
+
if bundle_dir is None:
|
|
257
|
+
bundle_dir = _default_custom_ui_bundle_dir(project_dir)
|
|
258
|
+
index_file = bundle_dir / "index.html"
|
|
259
|
+
enabled = bundle_dir.exists() and index_file.exists()
|
|
260
|
+
return {
|
|
261
|
+
"enabled": enabled,
|
|
262
|
+
"profile": config.profile,
|
|
263
|
+
"ui_profile": config.profile,
|
|
264
|
+
"path": config.path,
|
|
265
|
+
"ui_path": config.path,
|
|
266
|
+
"url": config.url,
|
|
267
|
+
"ui_url": config.url,
|
|
268
|
+
"bundle_path": str(bundle_dir),
|
|
269
|
+
"ui_bundle_path": str(bundle_dir),
|
|
270
|
+
"index_path": str(index_file),
|
|
271
|
+
"source": "custom",
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
bundle_dir = STATIC_DIR
|
|
275
|
+
index_file = bundle_dir / "index.html"
|
|
276
|
+
enabled = bundle_dir.exists() and index_file.exists()
|
|
277
|
+
return {
|
|
278
|
+
"enabled": enabled,
|
|
279
|
+
"profile": config.profile,
|
|
280
|
+
"ui_profile": config.profile,
|
|
281
|
+
"path": config.path,
|
|
282
|
+
"ui_path": config.path,
|
|
283
|
+
"url": config.url,
|
|
284
|
+
"ui_url": config.url,
|
|
285
|
+
"bundle_path": str(bundle_dir),
|
|
286
|
+
"ui_bundle_path": str(bundle_dir),
|
|
287
|
+
"index_path": str(index_file),
|
|
288
|
+
"source": "builtin",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _normalize_request_ui_path(request_path: str) -> str:
|
|
293
|
+
path = "/" + str(request_path or "").lstrip("/")
|
|
294
|
+
return path if path != "//" else "/"
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _is_custom_ui_static_asset_path(relative_path: str) -> bool:
|
|
298
|
+
path = str(relative_path or "").strip("/")
|
|
299
|
+
if not path:
|
|
300
|
+
return False
|
|
301
|
+
first_segment = path.split("/", 1)[0]
|
|
302
|
+
return first_segment == "assets" or bool(Path(path).suffix)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _find_ui_static_asset(bundle_dir: Path, requested_relative_path: str) -> Path | None:
|
|
306
|
+
"""Look up an asset without constructing a filesystem path from HTTP input."""
|
|
307
|
+
requested = str(requested_relative_path or "").strip("/")
|
|
308
|
+
if not requested:
|
|
309
|
+
return None
|
|
310
|
+
root = bundle_dir.resolve()
|
|
311
|
+
if not root.is_dir():
|
|
312
|
+
return None
|
|
313
|
+
for asset in root.rglob("*"):
|
|
314
|
+
if not asset.is_file():
|
|
315
|
+
continue
|
|
316
|
+
resolved = asset.resolve()
|
|
317
|
+
try:
|
|
318
|
+
relative = resolved.relative_to(root).as_posix()
|
|
319
|
+
except ValueError:
|
|
320
|
+
continue
|
|
321
|
+
if relative == requested:
|
|
322
|
+
return resolved
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _resolve_ui_static_response(request_path: str) -> Optional[FileResponse]:
|
|
327
|
+
spec = _resolve_agent_ui_spec()
|
|
328
|
+
if not spec.get("enabled"):
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
bundle_dir = Path(str(spec["bundle_path"]))
|
|
332
|
+
index_file = Path(str(spec["index_path"]))
|
|
333
|
+
path = _normalize_request_ui_path(request_path)
|
|
334
|
+
|
|
335
|
+
if spec.get("source") == "custom":
|
|
336
|
+
ui_path = _normalize_request_ui_path(str(spec.get("path") or "/")).rstrip("/") or "/"
|
|
337
|
+
if path == ui_path or path == f"{ui_path}/":
|
|
338
|
+
return FileResponse(index_file)
|
|
339
|
+
if ui_path != "/" and not path.startswith(f"{ui_path}/"):
|
|
340
|
+
return None
|
|
341
|
+
relative = path[len(ui_path) :].lstrip("/") if ui_path != "/" else path.lstrip("/")
|
|
342
|
+
if not relative:
|
|
343
|
+
return FileResponse(index_file)
|
|
344
|
+
asset = _find_ui_static_asset(bundle_dir, relative)
|
|
345
|
+
if asset is not None:
|
|
346
|
+
return FileResponse(asset)
|
|
347
|
+
if not _is_custom_ui_static_asset_path(relative):
|
|
348
|
+
return FileResponse(index_file)
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
if path in _RESERVED_UI_PATHS:
|
|
352
|
+
return FileResponse(index_file)
|
|
353
|
+
|
|
354
|
+
asset = _find_ui_static_asset(bundle_dir, path.lstrip("/"))
|
|
355
|
+
if asset is not None:
|
|
356
|
+
return FileResponse(asset)
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _is_textual_mime(mime_type: str) -> bool:
|
|
361
|
+
mime = (mime_type or "").lower()
|
|
362
|
+
if not mime:
|
|
363
|
+
return False
|
|
364
|
+
return mime.startswith(_TEXT_MIME_PREFIXES) or mime in _TEXT_MIME_TYPES
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _looks_like_textual_attachment(mime_type: str, display_name: str) -> bool:
|
|
368
|
+
suffix = Path(display_name or "").suffix.lower()
|
|
369
|
+
return _is_textual_mime(mime_type) or suffix in _TEXT_FILE_EXTENSIONS
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _extract_pdf_text(raw: bytes) -> str:
|
|
373
|
+
try:
|
|
374
|
+
from pypdf import PdfReader
|
|
375
|
+
except Exception:
|
|
376
|
+
return ""
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
reader = PdfReader(io.BytesIO(raw))
|
|
380
|
+
except Exception:
|
|
381
|
+
return ""
|
|
382
|
+
|
|
383
|
+
segments: List[str] = []
|
|
384
|
+
for page in reader.pages[:10]:
|
|
385
|
+
try:
|
|
386
|
+
page_text = page.extract_text() or ""
|
|
387
|
+
except Exception:
|
|
388
|
+
page_text = ""
|
|
389
|
+
if page_text:
|
|
390
|
+
segments.append(page_text)
|
|
391
|
+
|
|
392
|
+
return "\n".join(segments).strip()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _decode_inline_data(data_b64: str) -> bytes:
|
|
396
|
+
return bytes(base64.b64decode((data_b64 or "").strip() + "==="))
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _resolve_uploads_dir() -> Path:
|
|
400
|
+
uploads_dir = Path(resolve_local_session_dir()) / "files"
|
|
401
|
+
uploads_dir.mkdir(parents=True, exist_ok=True)
|
|
402
|
+
return uploads_dir
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _resolve_attachment_storage_path(file_uri: str) -> Optional[Path]:
|
|
406
|
+
normalized_uri = (file_uri or "").strip()
|
|
407
|
+
if not normalized_uri:
|
|
408
|
+
return None
|
|
409
|
+
|
|
410
|
+
if normalized_uri.startswith("local:"):
|
|
411
|
+
path = Path(normalized_uri[6:]).expanduser()
|
|
412
|
+
return path.resolve()
|
|
413
|
+
|
|
414
|
+
if normalized_uri.startswith(_UPLOAD_URI_SCHEME):
|
|
415
|
+
file_id = normalized_uri.removeprefix(_UPLOAD_URI_SCHEME).strip("/")
|
|
416
|
+
if not file_id:
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
for candidate in sorted(_resolve_uploads_dir().glob(f"{file_id}*")):
|
|
420
|
+
if candidate.is_file():
|
|
421
|
+
return candidate.resolve()
|
|
422
|
+
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _custom_api_proxy_base_url() -> str:
|
|
427
|
+
for key in _CUSTOM_API_PROXY_ENV_KEYS:
|
|
428
|
+
value = os.environ.get(key)
|
|
429
|
+
if value and value.strip():
|
|
430
|
+
return value.strip().rstrip("/")
|
|
431
|
+
return ""
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _proxy_headers(headers: Mapping[str, str]) -> dict[str, str]:
|
|
435
|
+
return {key: value for key, value in headers.items() if key.lower() not in _HOP_BY_HOP_HEADERS}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _response_headers(headers: Mapping[str, str]) -> dict[str, str]:
|
|
439
|
+
return {key: value for key, value in headers.items() if key.lower() not in _HOP_BY_HOP_HEADERS}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@health_meta_router.api_route(
|
|
443
|
+
"/api/{proxy_path:path}",
|
|
444
|
+
methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
445
|
+
include_in_schema=False,
|
|
446
|
+
)
|
|
447
|
+
async def custom_api_proxy(proxy_path: str, request: Request):
|
|
448
|
+
base_url = _custom_api_proxy_base_url()
|
|
449
|
+
if not base_url:
|
|
450
|
+
raise HTTPException(status_code=404, detail="Custom API backend is not configured")
|
|
451
|
+
|
|
452
|
+
path = f"/api/{proxy_path.lstrip('/')}"
|
|
453
|
+
query = request.url.query
|
|
454
|
+
target_url = f"{base_url}{path}"
|
|
455
|
+
if query:
|
|
456
|
+
target_url = f"{target_url}?{query}"
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
460
|
+
upstream = await client.request(
|
|
461
|
+
request.method,
|
|
462
|
+
target_url,
|
|
463
|
+
content=await request.body(),
|
|
464
|
+
headers=_proxy_headers(request.headers),
|
|
465
|
+
)
|
|
466
|
+
except httpx.HTTPError as exc:
|
|
467
|
+
raise HTTPException(
|
|
468
|
+
status_code=502, detail=f"Custom API backend unavailable: {exc}"
|
|
469
|
+
) from exc
|
|
470
|
+
|
|
471
|
+
return Response(
|
|
472
|
+
content=upstream.content,
|
|
473
|
+
status_code=upstream.status_code,
|
|
474
|
+
headers=_response_headers(upstream.headers),
|
|
475
|
+
media_type=upstream.headers.get("content-type"),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _read_attachment_bytes(
|
|
480
|
+
storage_path: Optional[Path], *, size_limit: Optional[int] = None
|
|
481
|
+
) -> Optional[bytes]:
|
|
482
|
+
if storage_path is None or not storage_path.is_file():
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
if size_limit is not None and storage_path.stat().st_size > size_limit:
|
|
487
|
+
return None
|
|
488
|
+
return storage_path.read_bytes()
|
|
489
|
+
except OSError:
|
|
490
|
+
return None
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _extract_inline_attachment_text(*, display_name: str, mime_type: str, raw: bytes) -> str:
|
|
494
|
+
if mime_type == "application/pdf" or display_name.lower().endswith(".pdf"):
|
|
495
|
+
text = _extract_pdf_text(raw)
|
|
496
|
+
if not text:
|
|
497
|
+
return ""
|
|
498
|
+
if len(text) > _MAX_INLINE_TEXT_CHARS:
|
|
499
|
+
return text[:_MAX_INLINE_TEXT_CHARS] + "\n...[内容已截断]"
|
|
500
|
+
return text
|
|
501
|
+
|
|
502
|
+
if _looks_like_textual_attachment(mime_type, display_name):
|
|
503
|
+
text = raw.decode("utf-8", errors="ignore")
|
|
504
|
+
if len(text) > _MAX_INLINE_TEXT_CHARS:
|
|
505
|
+
return text[:_MAX_INLINE_TEXT_CHARS] + "\n...[内容已截断]"
|
|
506
|
+
return text
|
|
507
|
+
|
|
508
|
+
return ""
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _attachment_prompt_text(attachment: Dict[str, Any]) -> str:
|
|
512
|
+
display_name = str(attachment.get("display_name") or "uploaded_file")
|
|
513
|
+
mime_type = str(attachment.get("mime_type") or "application/octet-stream")
|
|
514
|
+
transport = str(attachment.get("transport") or "")
|
|
515
|
+
|
|
516
|
+
if transport == "inline":
|
|
517
|
+
data_b64 = str(attachment.get("data") or "").strip()
|
|
518
|
+
if len(data_b64) > _MAX_INLINE_BASE64_CHARS:
|
|
519
|
+
return (
|
|
520
|
+
f"[上传文件: {display_name}, mime={mime_type or 'unknown'}, 内容过大,未直接展开]"
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
try:
|
|
524
|
+
raw = _decode_inline_data(data_b64)
|
|
525
|
+
except Exception:
|
|
526
|
+
return f"[上传文件: {display_name}, 内容解码失败]"
|
|
527
|
+
|
|
528
|
+
text = _extract_inline_attachment_text(
|
|
529
|
+
display_name=display_name,
|
|
530
|
+
mime_type=mime_type,
|
|
531
|
+
raw=raw,
|
|
532
|
+
)
|
|
533
|
+
if text:
|
|
534
|
+
return f"[上传文件: {display_name}]\n{text}"
|
|
535
|
+
return (
|
|
536
|
+
"[上传文件: "
|
|
537
|
+
f"{display_name}, "
|
|
538
|
+
f"mime={mime_type or 'application/octet-stream'}, "
|
|
539
|
+
f"bytes={len(raw)}]"
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
storage_path_value = attachment.get("storage_path")
|
|
543
|
+
storage_path = Path(str(storage_path_value)) if storage_path_value else None
|
|
544
|
+
size_bytes = attachment.get("size_bytes")
|
|
545
|
+
if size_bytes is None and storage_path is not None and storage_path.exists():
|
|
546
|
+
try:
|
|
547
|
+
size_bytes = storage_path.stat().st_size
|
|
548
|
+
except OSError:
|
|
549
|
+
size_bytes = None
|
|
550
|
+
|
|
551
|
+
reference_bytes = _read_attachment_bytes(storage_path, size_limit=_MAX_REFERENCE_TEXT_BYTES)
|
|
552
|
+
if reference_bytes is not None:
|
|
553
|
+
text = _extract_inline_attachment_text(
|
|
554
|
+
display_name=display_name,
|
|
555
|
+
mime_type=mime_type,
|
|
556
|
+
raw=reference_bytes,
|
|
557
|
+
)
|
|
558
|
+
if text:
|
|
559
|
+
return f"[上传文件: {display_name}]\n{text}"
|
|
560
|
+
return (
|
|
561
|
+
"[上传文件: "
|
|
562
|
+
f"{display_name}, "
|
|
563
|
+
f"mime={mime_type or 'application/octet-stream'}, "
|
|
564
|
+
f"bytes={len(reference_bytes)}]"
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
if size_bytes and size_bytes > _MAX_REFERENCE_TEXT_BYTES:
|
|
568
|
+
return (
|
|
569
|
+
"[上传文件: "
|
|
570
|
+
f"{display_name}, "
|
|
571
|
+
f"mime={mime_type or 'unknown'}, "
|
|
572
|
+
f"bytes={size_bytes}, "
|
|
573
|
+
"内容过大,未直接展开]"
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
file_uri = attachment.get("file_uri") or ""
|
|
577
|
+
return f"[上传文件引用: {display_name or file_uri}, mime={mime_type or 'unknown'}]"
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _extract_user_input_from_parts(parts: List[Any]) -> str:
|
|
581
|
+
"""兼容旧测试/旧调用点,统一复用 conversations 层的规范化逻辑。"""
|
|
582
|
+
|
|
583
|
+
return str(deps.conversation().extract_user_input_from_parts(parts))
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _attachment_from_part(part: Any) -> Optional[Dict[str, Any]]:
|
|
587
|
+
"""兼容旧入口,真实实现已经收口到 conversations.normalize。"""
|
|
588
|
+
|
|
589
|
+
attachment = deps.conversation().attachment_from_part(part)
|
|
590
|
+
return dict(attachment) if isinstance(attachment, Mapping) else None
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
async def _hydrate_session(session: Optional[Session]) -> Optional[Session]:
|
|
594
|
+
if not session:
|
|
595
|
+
return None
|
|
596
|
+
session.events = await deps.resolve_session_service().get_events(session.id)
|
|
597
|
+
return session
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
async def _ensure_session(agent_id: str, user_id: str, session_id: Optional[str]) -> Session:
|
|
601
|
+
service = deps.resolve_session_service()
|
|
602
|
+
if session_id:
|
|
603
|
+
existing = await service.get_session(session_id)
|
|
604
|
+
if existing:
|
|
605
|
+
if existing.agent_id != agent_id or existing.user_id != user_id:
|
|
606
|
+
raise HTTPException(
|
|
607
|
+
status_code=409,
|
|
608
|
+
detail="Session id belongs to a different agent or user",
|
|
609
|
+
)
|
|
610
|
+
return await _hydrate_session(existing) or existing
|
|
611
|
+
created = await service.create_session(agent_id, user_id, session_id=session_id)
|
|
612
|
+
return await _hydrate_session(created) or created
|
|
613
|
+
|
|
614
|
+
created = await service.create_session(agent_id, user_id)
|
|
615
|
+
return await _hydrate_session(created) or created
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _sanitize_session_state_for_action(state: Mapping[str, Any] | None) -> dict[str, Any]:
|
|
619
|
+
sanitized = dict(state or {})
|
|
620
|
+
attachment_context = sanitized.get(deps.conversation().runtime.ATTACHMENT_CONTEXT_STATE_KEY)
|
|
621
|
+
if not isinstance(attachment_context, Mapping):
|
|
622
|
+
return sanitized
|
|
623
|
+
|
|
624
|
+
attachments = [
|
|
625
|
+
deps.conversation().compact_attachment_for_session(item)
|
|
626
|
+
for item in attachment_context.get("attachments") or []
|
|
627
|
+
if isinstance(item, dict)
|
|
628
|
+
]
|
|
629
|
+
attachment_results = [
|
|
630
|
+
compact_attachment_result_for_session(item)
|
|
631
|
+
for item in attachment_context.get("attachment_results") or []
|
|
632
|
+
if isinstance(item, dict)
|
|
633
|
+
]
|
|
634
|
+
sanitized[deps.conversation().runtime.ATTACHMENT_CONTEXT_STATE_KEY] = {
|
|
635
|
+
"attachments": attachments,
|
|
636
|
+
"attachment_results": attachment_results,
|
|
637
|
+
}
|
|
638
|
+
return sanitized
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _request_id() -> str:
|
|
642
|
+
return f"req-{uuid.uuid4().hex[:12]}"
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _action_response(
|
|
646
|
+
action: str, data: Any, *, request_id: Optional[str] = None, message: str = "Success"
|
|
647
|
+
) -> dict:
|
|
648
|
+
payload = {
|
|
649
|
+
"Code": 0,
|
|
650
|
+
"Message": message,
|
|
651
|
+
"RequestId": request_id or _request_id(),
|
|
652
|
+
"Data": data,
|
|
653
|
+
}
|
|
654
|
+
if action:
|
|
655
|
+
payload["Action"] = action
|
|
656
|
+
return payload
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
async def _workspace_runtime_request(
|
|
660
|
+
method: str,
|
|
661
|
+
runtime_path: str,
|
|
662
|
+
*,
|
|
663
|
+
params: Optional[Dict[str, Any]] = None,
|
|
664
|
+
files: Optional[Dict[str, Any]] = None,
|
|
665
|
+
) -> httpx.Response:
|
|
666
|
+
runtime_app = get_state().app
|
|
667
|
+
if runtime_app is None:
|
|
668
|
+
raise RuntimeError("runtime app is not bound to the current request")
|
|
669
|
+
transport = httpx.ASGITransport(app=runtime_app)
|
|
670
|
+
async with httpx.AsyncClient(transport=transport, base_url="http://ksadk.local") as client:
|
|
671
|
+
response = await client.request(
|
|
672
|
+
method,
|
|
673
|
+
runtime_path,
|
|
674
|
+
params=params,
|
|
675
|
+
files=files,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if response.status_code >= 400:
|
|
679
|
+
detail = response.text
|
|
680
|
+
try:
|
|
681
|
+
payload = response.json()
|
|
682
|
+
except Exception:
|
|
683
|
+
payload = None
|
|
684
|
+
if isinstance(payload, dict):
|
|
685
|
+
detail = str(payload.get("detail") or detail)
|
|
686
|
+
raise HTTPException(
|
|
687
|
+
status_code=response.status_code, detail=detail or "Workspace request failed"
|
|
688
|
+
)
|
|
689
|
+
return response
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
# ============================================================
|
|
693
|
+
# Core ADK API Endpoints
|
|
694
|
+
# ============================================================
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
@health_meta_router.get("/health")
|
|
698
|
+
async def health_check():
|
|
699
|
+
runner = get_state().runner
|
|
700
|
+
framework = "unknown"
|
|
701
|
+
agent_name = "unknown"
|
|
702
|
+
if runner and hasattr(runner, "detection_result"):
|
|
703
|
+
framework = runner.detection_result.type.value # langgraph, langchain, adk
|
|
704
|
+
agent_name = runner.detection_result.name
|
|
705
|
+
return {"status": "ok", "framework": framework, "agent": agent_name}
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
@health_meta_router.get("/list-apps")
|
|
709
|
+
async def list_apps(relative_path: str = "./"):
|
|
710
|
+
"""Return available apps. For KsADK single-agent mode, returns the current agent."""
|
|
711
|
+
runner = get_state().runner
|
|
712
|
+
name = runner.detection_result.name if runner else "default_agent"
|
|
713
|
+
return [name]
|