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
ksadk/harness/config.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""HarnessApp yaml 配置 — 最小子集 + 严格校验 (goal-08)。
|
|
2
|
+
|
|
3
|
+
本期硬边界(防膨胀):**只支持最小子集** ``model`` / ``prompt``(instruction)/
|
|
4
|
+
``mcp_tools`` / ``sandbox``。超出子集的字段(memory/knowledge/workflow/tracing 等)
|
|
5
|
+
**明确报"暂不支持"**,不硬翻、不静默忽略。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
import yaml # type: ignore[import-untyped]
|
|
15
|
+
|
|
16
|
+
#: 顶层允许的最小子集字段。
|
|
17
|
+
_ALLOWED_TOP_LEVEL = frozenset({"model", "prompt", "mcp_tools", "sandbox", "runtime"})
|
|
18
|
+
#: runtime 允许的值。
|
|
19
|
+
_ALLOWED_RUNTIME = frozenset({"yaml", "codex"})
|
|
20
|
+
#: mcp_tools 单项允许字段。
|
|
21
|
+
_ALLOWED_MCP_TOOL = frozenset({"name", "url", "api_key", "tool_filter", "tool_name_prefix"})
|
|
22
|
+
#: sandbox 允许字段。
|
|
23
|
+
_ALLOWED_SANDBOX = frozenset({"read_only"})
|
|
24
|
+
|
|
25
|
+
#: 已知但本期不支持的字段,给出更明确的指引。
|
|
26
|
+
_KNOWN_UNSUPPORTED = {
|
|
27
|
+
"memory": "memory(记忆)暂不支持,后续阶段提供",
|
|
28
|
+
"knowledge": "knowledge(知识库)暂不支持,后续阶段提供",
|
|
29
|
+
"workflow": "workflow(工作流编排)暂不支持,后续阶段提供",
|
|
30
|
+
"tracing": "tracing(可观测)暂不支持,后续阶段提供",
|
|
31
|
+
"skills": "skills(技能)暂不支持,后续阶段提供",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class HarnessConfigError(ValueError):
|
|
36
|
+
"""HarnessApp yaml 配置错误(含"暂不支持"指引)。"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class McpToolSpec:
|
|
41
|
+
"""MCP 工具条目(最小子集)。"""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
url: str
|
|
45
|
+
api_key: Optional[str] = None
|
|
46
|
+
tool_filter: tuple[str, ...] = ()
|
|
47
|
+
tool_name_prefix: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class SandboxPolicy:
|
|
52
|
+
"""sandbox 策略。默认 read-only(goal-08:sandbox 默认 read-only)。"""
|
|
53
|
+
|
|
54
|
+
read_only: bool = True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class HarnessConfig:
|
|
59
|
+
"""HarnessApp 的最小 yaml 配置。"""
|
|
60
|
+
|
|
61
|
+
model: str
|
|
62
|
+
prompt: str
|
|
63
|
+
mcp_tools: tuple[McpToolSpec, ...] = ()
|
|
64
|
+
sandbox: SandboxPolicy = field(default_factory=SandboxPolicy)
|
|
65
|
+
runtime: str = "yaml"
|
|
66
|
+
"""runtime 后端:``yaml``(YamlAgentRunner+LiteLLM)| ``codex``(CodexRunner+codex CLI)。"""
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_dict(cls, data: dict[str, Any], *, source: str = "yaml") -> "HarnessConfig":
|
|
70
|
+
if not isinstance(data, dict):
|
|
71
|
+
raise HarnessConfigError(f"{source}: 顶层必须是 mapping,得到 {type(data).__name__}")
|
|
72
|
+
|
|
73
|
+
_reject_unknown_keys(data, _ALLOWED_TOP_LEVEL, where=source)
|
|
74
|
+
|
|
75
|
+
model = _require_str(data, "model", source)
|
|
76
|
+
prompt = _require_str(data, "prompt", source)
|
|
77
|
+
|
|
78
|
+
runtime = str(data.get("runtime", "yaml")).strip().lower()
|
|
79
|
+
if runtime not in _ALLOWED_RUNTIME:
|
|
80
|
+
raise HarnessConfigError(
|
|
81
|
+
f"{source}: runtime 仅支持 {sorted(_ALLOWED_RUNTIME)},得到 {runtime!r}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
mcp_tools = tuple(
|
|
85
|
+
_parse_mcp_tool(item, index, source)
|
|
86
|
+
for index, item in enumerate(
|
|
87
|
+
_require_list(data.get("mcp_tools", []), "mcp_tools", source)
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
sandbox = _parse_sandbox(data.get("sandbox", {}), source)
|
|
91
|
+
return cls(
|
|
92
|
+
model=model, prompt=prompt, mcp_tools=mcp_tools, sandbox=sandbox, runtime=runtime
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def from_yaml(cls, path: str | Path) -> "HarnessConfig":
|
|
97
|
+
path = Path(path)
|
|
98
|
+
try:
|
|
99
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
100
|
+
except yaml.YAMLError as exc:
|
|
101
|
+
raise HarnessConfigError(f"{path}: YAML 解析失败: {exc}") from exc
|
|
102
|
+
if data is None:
|
|
103
|
+
data = {}
|
|
104
|
+
return cls.from_dict(data, source=str(path))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _reject_unknown_keys(data: dict[str, Any], allowed: frozenset[str], *, where: str) -> None:
|
|
108
|
+
for key in data:
|
|
109
|
+
if key in allowed:
|
|
110
|
+
continue
|
|
111
|
+
if key in _KNOWN_UNSUPPORTED:
|
|
112
|
+
raise HarnessConfigError(f"{where}: 字段 {key!r} —— {_KNOWN_UNSUPPORTED[key]}")
|
|
113
|
+
raise HarnessConfigError(
|
|
114
|
+
f"{where}: 未知字段 {key!r};本期最小子集仅支持 {sorted(allowed)},"
|
|
115
|
+
"超出子集的字段不予支持(不静默忽略)"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _require_str(data: dict[str, Any], key: str, where: str) -> str:
|
|
120
|
+
value = data.get(key)
|
|
121
|
+
if not isinstance(value, str) or not value.strip():
|
|
122
|
+
raise HarnessConfigError(f"{where}: 字段 {key!r} 必填且必须是非空字符串")
|
|
123
|
+
return value.strip()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _require_list(value: Any, key: str, where: str) -> list:
|
|
127
|
+
if value is None:
|
|
128
|
+
return []
|
|
129
|
+
if not isinstance(value, list):
|
|
130
|
+
raise HarnessConfigError(f"{where}: 字段 {key!r} 必须是 list")
|
|
131
|
+
return value
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _parse_mcp_tool(item: Any, index: int, where: str) -> McpToolSpec:
|
|
135
|
+
if not isinstance(item, dict):
|
|
136
|
+
raise HarnessConfigError(f"{where}: mcp_tools[{index}] 必须是 mapping")
|
|
137
|
+
_reject_unknown_keys(item, _ALLOWED_MCP_TOOL, where=f"{where}.mcp_tools[{index}]")
|
|
138
|
+
name = _require_str(item, "name", f"{where}.mcp_tools[{index}]")
|
|
139
|
+
url = _require_str(item, "url", f"{where}.mcp_tools[{index}]")
|
|
140
|
+
tool_filter = item.get("tool_filter", ())
|
|
141
|
+
if not isinstance(tool_filter, (list, tuple)) or not all(
|
|
142
|
+
isinstance(t, str) for t in tool_filter
|
|
143
|
+
):
|
|
144
|
+
raise HarnessConfigError(f"{where}.mcp_tools[{index}].tool_filter 必须是字符串 list")
|
|
145
|
+
api_key = item.get("api_key")
|
|
146
|
+
if api_key is not None and not isinstance(api_key, str):
|
|
147
|
+
raise HarnessConfigError(f"{where}.mcp_tools[{index}].api_key 必须是字符串")
|
|
148
|
+
prefix = item.get("tool_name_prefix")
|
|
149
|
+
if prefix is not None and not isinstance(prefix, str):
|
|
150
|
+
raise HarnessConfigError(f"{where}.mcp_tools[{index}].tool_name_prefix 必须是字符串")
|
|
151
|
+
return McpToolSpec(
|
|
152
|
+
name=name,
|
|
153
|
+
url=url,
|
|
154
|
+
api_key=api_key,
|
|
155
|
+
tool_filter=tuple(tool_filter),
|
|
156
|
+
tool_name_prefix=prefix,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _parse_sandbox(value: Any, where: str) -> SandboxPolicy:
|
|
161
|
+
if value is None:
|
|
162
|
+
return SandboxPolicy()
|
|
163
|
+
if not isinstance(value, dict):
|
|
164
|
+
raise HarnessConfigError(f"{where}: sandbox 必须是 mapping")
|
|
165
|
+
_reject_unknown_keys(value, _ALLOWED_SANDBOX, where=f"{where}.sandbox")
|
|
166
|
+
read_only = value.get("read_only", True)
|
|
167
|
+
if not isinstance(read_only, bool):
|
|
168
|
+
raise HarnessConfigError(f"{where}.sandbox.read_only 必须是 bool")
|
|
169
|
+
if not read_only:
|
|
170
|
+
raise HarnessConfigError(
|
|
171
|
+
f"{where}.sandbox.read_only=false 暂不支持;Harness 当前只提供执行层强制的只读策略"
|
|
172
|
+
)
|
|
173
|
+
return SandboxPolicy(read_only=read_only)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
__all__ = [
|
|
177
|
+
"HarnessConfig",
|
|
178
|
+
"HarnessConfigError",
|
|
179
|
+
"McpToolSpec",
|
|
180
|
+
"SandboxPolicy",
|
|
181
|
+
]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Reasoning boundary used by the Harness runner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Protocol, Sequence
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class HarnessToolCall:
|
|
13
|
+
call_id: str
|
|
14
|
+
name: str
|
|
15
|
+
arguments: dict[str, Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class HarnessReasoningTurn:
|
|
20
|
+
final_text: str | None = None
|
|
21
|
+
tool_calls: tuple[HarnessToolCall, ...] = ()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HarnessReasoner(Protocol):
|
|
25
|
+
async def complete(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
model: str,
|
|
29
|
+
prompt: str,
|
|
30
|
+
messages: Sequence[dict[str, Any]],
|
|
31
|
+
tools: Sequence[Any],
|
|
32
|
+
) -> HarnessReasoningTurn: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LiteLLMHarnessReasoner:
|
|
36
|
+
"""Use the project's OpenAI-compatible LiteLLM configuration for tool reasoning."""
|
|
37
|
+
|
|
38
|
+
async def complete(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
model: str,
|
|
42
|
+
prompt: str,
|
|
43
|
+
messages: Sequence[dict[str, Any]],
|
|
44
|
+
tools: Sequence[Any],
|
|
45
|
+
) -> HarnessReasoningTurn:
|
|
46
|
+
del prompt
|
|
47
|
+
try:
|
|
48
|
+
from litellm import acompletion
|
|
49
|
+
except ImportError as exc: # pragma: no cover - depends on optional install
|
|
50
|
+
raise RuntimeError(
|
|
51
|
+
"Harness reasoning requires the 'adk' extra (litellm); "
|
|
52
|
+
"install ksadk[adk] or inject a HarnessReasoner"
|
|
53
|
+
) from exc
|
|
54
|
+
|
|
55
|
+
resolved_model = model if "/" in model else f"openai/{model}"
|
|
56
|
+
kwargs: dict[str, Any] = {
|
|
57
|
+
"model": resolved_model,
|
|
58
|
+
"messages": list(messages),
|
|
59
|
+
"tools": [tool.openai_schema for tool in tools],
|
|
60
|
+
"tool_choice": "auto",
|
|
61
|
+
}
|
|
62
|
+
base_url = os.getenv("OPENAI_BASE_URL")
|
|
63
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
|
64
|
+
if base_url:
|
|
65
|
+
kwargs["base_url"] = base_url
|
|
66
|
+
if api_key:
|
|
67
|
+
kwargs["api_key"] = api_key
|
|
68
|
+
response = await acompletion(**kwargs)
|
|
69
|
+
choices = getattr(response, "choices", None) or []
|
|
70
|
+
if not choices:
|
|
71
|
+
raise RuntimeError(f"Harness model {model!r} returned no choices")
|
|
72
|
+
message = choices[0].message
|
|
73
|
+
calls: list[HarnessToolCall] = []
|
|
74
|
+
for index, call in enumerate(getattr(message, "tool_calls", None) or []):
|
|
75
|
+
function = getattr(call, "function", None)
|
|
76
|
+
name = str(getattr(function, "name", "") or "").strip()
|
|
77
|
+
raw_arguments = getattr(function, "arguments", None) or "{}"
|
|
78
|
+
try:
|
|
79
|
+
arguments = (
|
|
80
|
+
json.loads(raw_arguments) if isinstance(raw_arguments, str) else raw_arguments
|
|
81
|
+
)
|
|
82
|
+
except json.JSONDecodeError as exc:
|
|
83
|
+
raise RuntimeError(
|
|
84
|
+
f"Harness model emitted invalid JSON arguments for tool {name!r}"
|
|
85
|
+
) from exc
|
|
86
|
+
if not name or not isinstance(arguments, dict):
|
|
87
|
+
raise RuntimeError("Harness model emitted an invalid tool call")
|
|
88
|
+
calls.append(
|
|
89
|
+
HarnessToolCall(
|
|
90
|
+
call_id=str(getattr(call, "id", "") or f"tool-call-{index}"),
|
|
91
|
+
name=name,
|
|
92
|
+
arguments=dict(arguments),
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
content = getattr(message, "content", None)
|
|
96
|
+
final_text = str(content) if content is not None else None
|
|
97
|
+
return HarnessReasoningTurn(final_text=final_text, tool_calls=tuple(calls))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
__all__ = [
|
|
101
|
+
"HarnessReasoner",
|
|
102
|
+
"HarnessReasoningTurn",
|
|
103
|
+
"HarnessToolCall",
|
|
104
|
+
"LiteLLMHarnessReasoner",
|
|
105
|
+
]
|
ksadk/harness/runner.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Runner that composes model reasoning with real MCP and sandbox tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from types import SimpleNamespace
|
|
9
|
+
from typing import Any, AsyncIterator, Dict
|
|
10
|
+
|
|
11
|
+
from ksadk.harness.config import HarnessConfig
|
|
12
|
+
from ksadk.harness.reasoner import (
|
|
13
|
+
HarnessReasoner,
|
|
14
|
+
HarnessReasoningTurn,
|
|
15
|
+
HarnessToolCall,
|
|
16
|
+
LiteLLMHarnessReasoner,
|
|
17
|
+
)
|
|
18
|
+
from ksadk.harness.sandbox import HarnessSandboxExecutor
|
|
19
|
+
from ksadk.harness.tools import HarnessTool, load_mcp_tools, sandbox_tools, tool_result_text
|
|
20
|
+
from ksadk.runners.base_runner import BaseRunner
|
|
21
|
+
|
|
22
|
+
_MAX_REASONING_TURNS = 8
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class YamlAgentRunner(BaseRunner):
|
|
26
|
+
"""Execute one Harness config through the shared runner/runtime contract."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
config: HarnessConfig,
|
|
31
|
+
*,
|
|
32
|
+
agent_name: str = "harness-agent",
|
|
33
|
+
reasoner: HarnessReasoner | None = None,
|
|
34
|
+
workspace_root: str | Path = ".",
|
|
35
|
+
) -> None:
|
|
36
|
+
super().__init__(
|
|
37
|
+
detection_result=SimpleNamespace(
|
|
38
|
+
name=agent_name,
|
|
39
|
+
type=SimpleNamespace(value="harness"),
|
|
40
|
+
),
|
|
41
|
+
project_dir=str(workspace_root),
|
|
42
|
+
)
|
|
43
|
+
self._config = config
|
|
44
|
+
self._reasoner = reasoner or LiteLLMHarnessReasoner()
|
|
45
|
+
self._sandbox = HarnessSandboxExecutor(
|
|
46
|
+
workspace_root=workspace_root,
|
|
47
|
+
read_only=config.sandbox.read_only,
|
|
48
|
+
)
|
|
49
|
+
self._loaded = False
|
|
50
|
+
self._load_lock = asyncio.Lock()
|
|
51
|
+
self._tools: tuple[HarnessTool, ...] | None = None
|
|
52
|
+
self._mcp_toolsets: list[Any] = []
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def harness_config(self) -> HarnessConfig:
|
|
56
|
+
return self._config
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def sandbox(self) -> HarnessSandboxExecutor:
|
|
60
|
+
return self._sandbox
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def workspace_root(self) -> Path:
|
|
64
|
+
return self._sandbox.workspace_root
|
|
65
|
+
|
|
66
|
+
def load_agent(self) -> None:
|
|
67
|
+
# MCP discovery is async and therefore happens lazily on first invocation.
|
|
68
|
+
self._loaded = True
|
|
69
|
+
|
|
70
|
+
def prepare_for_request(self, model: str | None) -> None:
|
|
71
|
+
# Request model is carried in input_data. Do not mutate process-wide model env.
|
|
72
|
+
del model
|
|
73
|
+
|
|
74
|
+
def _effective(self, input_data: Dict[str, Any]) -> tuple[str, str]:
|
|
75
|
+
metadata = input_data.get("metadata") or {}
|
|
76
|
+
if not isinstance(metadata, dict):
|
|
77
|
+
metadata = {}
|
|
78
|
+
model = str(
|
|
79
|
+
metadata.get("model_override") or input_data.get("model") or self._config.model
|
|
80
|
+
).strip()
|
|
81
|
+
prompt = str(
|
|
82
|
+
metadata.get("prompt_override") or input_data.get("instructions") or self._config.prompt
|
|
83
|
+
).strip()
|
|
84
|
+
return model, prompt
|
|
85
|
+
|
|
86
|
+
async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
87
|
+
tools = await self._ensure_tools()
|
|
88
|
+
model, prompt = self._effective(input_data)
|
|
89
|
+
user_input = str(input_data.get("input") or "")
|
|
90
|
+
messages: list[dict[str, Any]] = [
|
|
91
|
+
{"role": "system", "content": prompt},
|
|
92
|
+
{"role": "user", "content": user_input},
|
|
93
|
+
]
|
|
94
|
+
execution_log: list[dict[str, Any]] = []
|
|
95
|
+
|
|
96
|
+
for _turn_number in range(_MAX_REASONING_TURNS):
|
|
97
|
+
turn = await self._reasoner.complete(
|
|
98
|
+
model=model,
|
|
99
|
+
prompt=prompt,
|
|
100
|
+
messages=tuple(messages),
|
|
101
|
+
tools=tools,
|
|
102
|
+
)
|
|
103
|
+
if turn.tool_calls:
|
|
104
|
+
messages.append(
|
|
105
|
+
{
|
|
106
|
+
"role": "assistant",
|
|
107
|
+
"content": turn.final_text,
|
|
108
|
+
"tool_calls": [
|
|
109
|
+
{
|
|
110
|
+
"id": call.call_id,
|
|
111
|
+
"type": "function",
|
|
112
|
+
"function": {
|
|
113
|
+
"name": call.name,
|
|
114
|
+
"arguments": json.dumps(call.arguments, ensure_ascii=False),
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
for call in turn.tool_calls
|
|
118
|
+
],
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
for call in turn.tool_calls:
|
|
122
|
+
tool = next((item for item in tools if item.name == call.name), None)
|
|
123
|
+
if tool is None:
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
f"Harness tool {call.name!r} is not available; it may be filtered "
|
|
126
|
+
"or was not exposed by its MCP server"
|
|
127
|
+
)
|
|
128
|
+
result = await tool.call(call.arguments, call_id=call.call_id)
|
|
129
|
+
content = tool_result_text(result)
|
|
130
|
+
execution_log.append(
|
|
131
|
+
{
|
|
132
|
+
"call_id": call.call_id,
|
|
133
|
+
"name": call.name,
|
|
134
|
+
"source": tool.source,
|
|
135
|
+
"result": result,
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
messages.append(
|
|
139
|
+
{
|
|
140
|
+
"role": "tool",
|
|
141
|
+
"tool_call_id": call.call_id,
|
|
142
|
+
"name": call.name,
|
|
143
|
+
"content": content,
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
continue
|
|
147
|
+
if turn.final_text is None:
|
|
148
|
+
raise RuntimeError(
|
|
149
|
+
"Harness reasoner returned neither a final response nor a tool call"
|
|
150
|
+
)
|
|
151
|
+
return {
|
|
152
|
+
"output": turn.final_text,
|
|
153
|
+
"model": model,
|
|
154
|
+
"prompt": prompt,
|
|
155
|
+
"tool_calls": execution_log,
|
|
156
|
+
"sandbox_read_only": self._config.sandbox.read_only,
|
|
157
|
+
}
|
|
158
|
+
raise RuntimeError(f"Harness reasoning exceeded {_MAX_REASONING_TURNS} turns")
|
|
159
|
+
|
|
160
|
+
async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
|
|
161
|
+
result = await self.invoke(input_data)
|
|
162
|
+
yield {"delta": result["output"], "type": "text"}
|
|
163
|
+
yield {
|
|
164
|
+
"output": result["output"],
|
|
165
|
+
"type": "final",
|
|
166
|
+
"model": result["model"],
|
|
167
|
+
"tool_calls": result["tool_calls"],
|
|
168
|
+
"sandbox_read_only": result["sandbox_read_only"],
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async def close(self) -> None:
|
|
172
|
+
toolsets, self._mcp_toolsets = self._mcp_toolsets, []
|
|
173
|
+
self._tools = None
|
|
174
|
+
if toolsets:
|
|
175
|
+
await asyncio.gather(*(toolset.close() for toolset in toolsets), return_exceptions=True)
|
|
176
|
+
|
|
177
|
+
async def _ensure_tools(self) -> tuple[HarnessTool, ...]:
|
|
178
|
+
if self._tools is not None:
|
|
179
|
+
return self._tools
|
|
180
|
+
async with self._load_lock:
|
|
181
|
+
if self._tools is not None:
|
|
182
|
+
return self._tools
|
|
183
|
+
tools = sandbox_tools(self._sandbox)
|
|
184
|
+
try:
|
|
185
|
+
for spec in self._config.mcp_tools:
|
|
186
|
+
toolset, discovered = await load_mcp_tools(spec)
|
|
187
|
+
self._mcp_toolsets.append(toolset)
|
|
188
|
+
tools.extend(discovered)
|
|
189
|
+
duplicates = sorted(
|
|
190
|
+
{
|
|
191
|
+
tool.name
|
|
192
|
+
for tool in tools
|
|
193
|
+
if sum(item.name == tool.name for item in tools) > 1
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
if duplicates:
|
|
197
|
+
raise RuntimeError(f"Harness tool names are not unique: {duplicates}")
|
|
198
|
+
except Exception:
|
|
199
|
+
await self.close()
|
|
200
|
+
raise
|
|
201
|
+
self._tools = tuple(tools)
|
|
202
|
+
return self._tools
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
__all__ = [
|
|
206
|
+
"HarnessReasoner",
|
|
207
|
+
"HarnessReasoningTurn",
|
|
208
|
+
"HarnessToolCall",
|
|
209
|
+
"LiteLLMHarnessReasoner",
|
|
210
|
+
"YamlAgentRunner",
|
|
211
|
+
]
|
ksadk/harness/sandbox.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Invocation-owned execution policy for Harness sandbox tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import shlex
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SandboxPolicyDenied(PermissionError):
|
|
14
|
+
"""Raised before execution when a Harness sandbox action violates policy."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_READ_COMMANDS = frozenset({"cat", "head", "ls", "pwd", "stat", "tail", "wc"})
|
|
18
|
+
_NO_PATH_COMMANDS = frozenset({"pwd"})
|
|
19
|
+
_REQUIRE_PATH_COMMANDS = frozenset({"cat", "head", "stat", "tail", "wc"})
|
|
20
|
+
_VALUE_OPTIONS = frozenset({"-n", "--bytes", "--lines", "--max-unchanged-stats"})
|
|
21
|
+
_SHELL_OPERATORS = frozenset({"|", "||", "&", "&&", ";", ">", ">>", "<", "<<"})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HarnessSandboxExecutor:
|
|
25
|
+
"""Run a deliberately small read-only command surface inside one workspace.
|
|
26
|
+
|
|
27
|
+
Commands are executed without a shell. Path operands are resolved before the
|
|
28
|
+
child process starts, including symlinks, so reads cannot escape the configured
|
|
29
|
+
workspace. The narrow command surface also excludes network clients and process
|
|
30
|
+
launchers entirely.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, *, workspace_root: str | Path, read_only: bool = True) -> None:
|
|
34
|
+
self._workspace_root = Path(workspace_root).expanduser().resolve()
|
|
35
|
+
self._workspace_root.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
self._read_only = read_only
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def workspace_root(self) -> Path:
|
|
40
|
+
return self._workspace_root
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def read_only(self) -> bool:
|
|
44
|
+
return self._read_only
|
|
45
|
+
|
|
46
|
+
async def read_file(self, path: str) -> dict[str, Any]:
|
|
47
|
+
target = self._resolve_path(path)
|
|
48
|
+
content = await asyncio.to_thread(target.read_text, encoding="utf-8")
|
|
49
|
+
return {
|
|
50
|
+
"ok": True,
|
|
51
|
+
"path": target.relative_to(self._workspace_root).as_posix(),
|
|
52
|
+
"content": content,
|
|
53
|
+
"policy": "read-only",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async def run_command(self, command: str) -> dict[str, Any]:
|
|
57
|
+
argv = self._validate_command(command)
|
|
58
|
+
completed = await asyncio.to_thread(
|
|
59
|
+
subprocess.run,
|
|
60
|
+
argv,
|
|
61
|
+
cwd=self._workspace_root,
|
|
62
|
+
env={"PATH": os.environ.get("PATH", "")},
|
|
63
|
+
capture_output=True,
|
|
64
|
+
text=True,
|
|
65
|
+
timeout=10,
|
|
66
|
+
check=False,
|
|
67
|
+
)
|
|
68
|
+
return {
|
|
69
|
+
"ok": completed.returncode == 0,
|
|
70
|
+
"command": " ".join(argv),
|
|
71
|
+
"stdout": completed.stdout,
|
|
72
|
+
"stderr": completed.stderr,
|
|
73
|
+
"exit_code": completed.returncode,
|
|
74
|
+
"policy": "read-only",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def _validate_command(self, command: str) -> list[str]:
|
|
78
|
+
try:
|
|
79
|
+
argv = shlex.split(str(command or ""), posix=True)
|
|
80
|
+
except ValueError as exc:
|
|
81
|
+
raise SandboxPolicyDenied(f"read-only sandbox denied malformed command: {exc}") from exc
|
|
82
|
+
if not argv:
|
|
83
|
+
raise SandboxPolicyDenied("read-only sandbox denied empty command")
|
|
84
|
+
if any(argument in _SHELL_OPERATORS for argument in argv):
|
|
85
|
+
raise SandboxPolicyDenied(
|
|
86
|
+
"read-only sandbox denied shell redirection, pipelines, or command chaining"
|
|
87
|
+
)
|
|
88
|
+
executable = argv[0]
|
|
89
|
+
if executable not in _READ_COMMANDS:
|
|
90
|
+
raise SandboxPolicyDenied(
|
|
91
|
+
f"read-only sandbox denied command {executable!r}; network, write, and "
|
|
92
|
+
"dangerous process actions are not allowed"
|
|
93
|
+
)
|
|
94
|
+
if executable in _NO_PATH_COMMANDS:
|
|
95
|
+
if len(argv) != 1:
|
|
96
|
+
raise SandboxPolicyDenied(
|
|
97
|
+
f"read-only sandbox denied arguments for command {executable!r}"
|
|
98
|
+
)
|
|
99
|
+
return argv
|
|
100
|
+
|
|
101
|
+
normalized = [executable]
|
|
102
|
+
skip_option_value = False
|
|
103
|
+
saw_path = False
|
|
104
|
+
for argument in argv[1:]:
|
|
105
|
+
if skip_option_value:
|
|
106
|
+
normalized.append(argument)
|
|
107
|
+
skip_option_value = False
|
|
108
|
+
continue
|
|
109
|
+
if argument == "--":
|
|
110
|
+
normalized.append(argument)
|
|
111
|
+
continue
|
|
112
|
+
if argument in _VALUE_OPTIONS:
|
|
113
|
+
normalized.append(argument)
|
|
114
|
+
skip_option_value = True
|
|
115
|
+
continue
|
|
116
|
+
if argument.startswith("-"):
|
|
117
|
+
normalized.append(argument)
|
|
118
|
+
continue
|
|
119
|
+
target = self._resolve_path(argument)
|
|
120
|
+
normalized.append(str(target))
|
|
121
|
+
saw_path = True
|
|
122
|
+
if executable in _REQUIRE_PATH_COMMANDS and not saw_path:
|
|
123
|
+
raise SandboxPolicyDenied(
|
|
124
|
+
f"read-only sandbox denied command {executable!r} without a workspace path"
|
|
125
|
+
)
|
|
126
|
+
return normalized
|
|
127
|
+
|
|
128
|
+
def _resolve_path(self, path: str) -> Path:
|
|
129
|
+
raw = str(path or "").strip()
|
|
130
|
+
if not raw:
|
|
131
|
+
raise SandboxPolicyDenied("read-only sandbox requires a workspace path")
|
|
132
|
+
candidate = Path(raw)
|
|
133
|
+
if not candidate.is_absolute():
|
|
134
|
+
candidate = self._workspace_root / candidate
|
|
135
|
+
resolved = candidate.expanduser().resolve()
|
|
136
|
+
if resolved != self._workspace_root and self._workspace_root not in resolved.parents:
|
|
137
|
+
raise SandboxPolicyDenied(f"read-only sandbox denied path outside workspace: {raw!r}")
|
|
138
|
+
return resolved
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = ["HarnessSandboxExecutor", "SandboxPolicyDenied"]
|