agentengine-sdk-python 0.6.9__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.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +41 -23
- 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 +252 -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 +533 -726
- 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 +173 -90
- ksadk/cli/cmd_invoke.py +388 -159
- ksadk/cli/cmd_launch.py +64 -28
- ksadk/cli/cmd_mcp.py +103 -64
- ksadk/cli/cmd_model.py +14 -5
- ksadk/cli/cmd_openclaw.py +488 -228
- ksadk/cli/cmd_replay.py +71 -0
- ksadk/cli/cmd_run.py +44 -14
- 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 +38 -16
- 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 +501 -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 +656 -0
- ksadk/conversations/model_context.py +22 -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 -5187
- 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 +52 -39
- ksadk/deployment/manager.py +63 -65
- ksadk/deployment/providers/serverless.py +326 -209
- 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 +267 -0
- ksadk/memory/adk/short_term_memory.py +33 -27
- 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 +1248 -164
- 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 -530
- ksadk/runners/langgraph_runner.py +490 -95
- ksadk/runners/patch_langchain.py +16 -16
- ksadk/runners/remote_runner.py +264 -38
- 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 -3743
- 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-C0meuOdi.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-bj2rZnP5.js → arc-CFKwSk_B.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
- ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-BK8paA20.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.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-_N8bJ-EU.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
- ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-6CVKuTAe.js} +1 -1
- ksadk/server/static/assets/{dist-BMKM66B4.js → dist-BUHRxvDG.js} +1 -1
- ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-BZ9n9AjO.js} +1 -1
- ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-BibcG1V9.js} +1 -1
- ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-Bs-jQcRX.js} +1 -1
- ksadk/server/static/assets/{dist-D99wf6_X.js → dist-C2xjE7cO.js} +1 -1
- ksadk/server/static/assets/{dist-DLMjL01k.js → dist-C7-3jInC.js} +1 -1
- ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-C99THtIA.js} +1 -1
- ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CGcp2ex8.js} +1 -1
- ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-CVQRZjPu.js} +1 -1
- ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-Cf23NY4L.js} +1 -1
- ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-CgF3fj15.js} +1 -1
- ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-Cwt7koer.js} +1 -1
- ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-D82mtD2s.js} +1 -1
- ksadk/server/static/assets/{dist-B67uF2uK.js → dist-DF06ORQo.js} +1 -1
- ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-DHn6nBzr.js} +1 -1
- ksadk/server/static/assets/{dist-S4klkK5W.js → dist-Djgl2wVZ.js} +1 -1
- ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DlzCRy0S.js} +1 -1
- ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-DqNnNFrw.js} +1 -1
- ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-DyvYLMW9.js} +1 -1
- ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-eoW6tZXA.js} +1 -1
- ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-ivOya1LD.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.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-BR5s6Pws.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-DYPc5cvh.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
- ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DATssjp1.js} +1 -1
- ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CvMBw9cA.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.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-DPnuCB8c.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.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-CwSec0Wa.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-agCVobiF.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.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 +86 -40
- ksadk/sessions/base.py +31 -11
- ksadk/sessions/continuity.py +28 -5
- ksadk/sessions/in_memory.py +46 -2
- ksadk/sessions/local_service.py +148 -17
- ksadk/sessions/postgres_service.py +216 -35
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +388 -0
- 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 +136 -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 +264 -114
- ksadk/tracing/exporters/inmemory_exporter.py +62 -51
- ksadk/tracing/exporters/langfuse_exporter.py +91 -76
- ksadk/tracing/setup.py +132 -53
- ksadk/tracing/span_utils.py +5 -2
- ksadk/tui/__init__.py +22 -7
- ksadk/tui/loop.py +1925 -0
- ksadk/tui/stream_render.py +69 -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.6.9.dist-info/RECORD +0 -487
- ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +0 -9
- ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +0 -8
- ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
- ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
- ksadk/server/static/assets/index-BH7-pC0B.js +0 -165
- ksadk/server/static/assets/index-DAURbCsP.css +0 -1
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
- ksadk/tui/app.py +0 -400
- ksadk/tui/clipboard.py +0 -100
- ksadk/tui/widgets/__init__.py +0 -22
- ksadk/tui/widgets/assistant.py +0 -89
- ksadk/tui/widgets/base.py +0 -54
- ksadk/tui/widgets/chat_input.py +0 -246
- ksadk/tui/widgets/history.py +0 -156
- ksadk/tui/widgets/system.py +0 -87
- ksadk/tui/widgets/thinking.py +0 -110
- ksadk/tui/widgets/tool_call.py +0 -161
- ksadk/tui/widgets/user.py +0 -37
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""codex namespace 工具拍平 + 还原(responses↔chat 路径)。
|
|
2
|
+
|
|
3
|
+
codex 0.142+ 用 namespace 工具声明插件/MCP 工具:
|
|
4
|
+
|
|
5
|
+
{"type":"namespace","name":"mcp__x__","tools":[{"type":"function","name":"foo",...}]}
|
|
6
|
+
|
|
7
|
+
并在 input 历史里发 namespace-qualified function_call:
|
|
8
|
+
|
|
9
|
+
{"type":"function_call","name":"foo","namespace":"mcp__x__",...}
|
|
10
|
+
|
|
11
|
+
chat completions 只认顶层 ``function`` 工具(chat 嵌套)和 ``function.name``,
|
|
12
|
+
不认 ``namespace`` 字段。本模块把 namespace 工具**拍平**成顶层 function,
|
|
13
|
+
名字用确定性的 ``<namespace>__<child>``(超长 sha256 截断,与 cc-switch 一致),
|
|
14
|
+
响应侧再把 flat 名字**还原**回 ``{name, namespace}``,让 codex client 能匹配
|
|
15
|
+
自己的 namespace 工具注册表。
|
|
16
|
+
|
|
17
|
+
请求侧拍平 + 响应侧还原都从**同一份 request tools** 经
|
|
18
|
+
:func:`flatten_namespace_tool_name` 派生名字映射,前后一致,无需跨请求状态。
|
|
19
|
+
|
|
20
|
+
参照 cc-switch ``transform_codex_responses_namespace.rs``(用 Python 重写)。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
CHAT_TOOL_NAME_MAX_LEN = 64
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def flatten_namespace_tool_name(namespace: str, name: str) -> str:
|
|
32
|
+
"""``<namespace>__<name>``;超长则尾部接 sha256 截断,保持在 64 字符内。
|
|
33
|
+
|
|
34
|
+
截断按字符边界,保证返回值是合法 UTF-8(不会从多字节字符中间切断)。
|
|
35
|
+
"""
|
|
36
|
+
full = f"{namespace}__{name}"
|
|
37
|
+
if len(full) <= CHAT_TOOL_NAME_MAX_LEN:
|
|
38
|
+
return full
|
|
39
|
+
suffix = "__" + hashlib.sha256(full.encode("utf-8")).hexdigest()[:8]
|
|
40
|
+
prefix_len = CHAT_TOOL_NAME_MAX_LEN - len(suffix)
|
|
41
|
+
prefix = ""
|
|
42
|
+
for ch in full:
|
|
43
|
+
if len(prefix.encode("utf-8")) + len(ch.encode("utf-8")) > prefix_len:
|
|
44
|
+
break
|
|
45
|
+
prefix += ch
|
|
46
|
+
return prefix + suffix
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _namespace_children(tool: dict) -> list[dict]:
|
|
50
|
+
for key in ("tools", "children"):
|
|
51
|
+
kids = tool.get(key)
|
|
52
|
+
if isinstance(kids, list):
|
|
53
|
+
return kids
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_restore_map(tools: list[Any] | None) -> dict[str, dict[str, str]]:
|
|
58
|
+
"""从 request 的 namespace 工具声明建 ``flat -> {namespace, name}`` 映射。
|
|
59
|
+
|
|
60
|
+
两个不同 child 拍平后撞名时,后者覆盖前者(与 cc-switch ``or_insert`` 不同,
|
|
61
|
+
cc-switch 用 first-wins 并对撞名报错;这里取 first-wins + 调用方撞名检测)。
|
|
62
|
+
"""
|
|
63
|
+
restore: dict[str, dict[str, str]] = {}
|
|
64
|
+
if not tools:
|
|
65
|
+
return restore
|
|
66
|
+
for tool in tools:
|
|
67
|
+
if not isinstance(tool, dict) or tool.get("type") != "namespace":
|
|
68
|
+
continue
|
|
69
|
+
namespace = (tool.get("name") or "").strip()
|
|
70
|
+
if not namespace:
|
|
71
|
+
continue
|
|
72
|
+
for child in _namespace_children(tool):
|
|
73
|
+
if not isinstance(child, dict) or child.get("type") != "function":
|
|
74
|
+
continue
|
|
75
|
+
name = (child.get("name") or "").strip()
|
|
76
|
+
if not name:
|
|
77
|
+
continue
|
|
78
|
+
flat = flatten_namespace_tool_name(namespace, name)
|
|
79
|
+
restore.setdefault(flat, {"namespace": namespace, "name": name})
|
|
80
|
+
return restore
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _rewrite_qualified_calls(value: Any, owners: dict[str, dict[str, str]]) -> bool:
|
|
84
|
+
"""递归把 input 里的 namespace-qualified function_call 重写成 flat name。
|
|
85
|
+
|
|
86
|
+
``{type:function_call, name:child, namespace:ns}`` -> ``{type:function_call, name:flat}``
|
|
87
|
+
(去掉 namespace 字段);只在 (ns, name) 与 owners 条目匹配时重写。
|
|
88
|
+
"""
|
|
89
|
+
changed = False
|
|
90
|
+
if isinstance(value, list):
|
|
91
|
+
for item in value:
|
|
92
|
+
changed |= _rewrite_qualified_calls(item, owners)
|
|
93
|
+
elif isinstance(value, dict):
|
|
94
|
+
if value.get("type") == "function_call":
|
|
95
|
+
namespace = (value.get("namespace") or "").strip()
|
|
96
|
+
name = (value.get("name") or "").strip()
|
|
97
|
+
if namespace and name:
|
|
98
|
+
flat = flatten_namespace_tool_name(namespace, name)
|
|
99
|
+
entry = owners.get(flat)
|
|
100
|
+
if entry and entry["namespace"] == namespace and entry["name"] == name:
|
|
101
|
+
value["name"] = flat
|
|
102
|
+
value.pop("namespace", None)
|
|
103
|
+
changed = True
|
|
104
|
+
for child in value.values():
|
|
105
|
+
changed |= _rewrite_qualified_calls(child, owners)
|
|
106
|
+
return changed
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def flatten_request_namespaces(body: dict) -> dict[str, dict[str, str]]:
|
|
110
|
+
"""原地拍平 body 的 namespace 工具,返回 restore_map(供响应侧还原)。
|
|
111
|
+
|
|
112
|
+
- tools:namespace children 提升为顶层 responses function 工具(flat name),
|
|
113
|
+
原 namespace 工具移除;检测撞名(两个 child 拍平后同名)则 raise。
|
|
114
|
+
- input:namespace-qualified function_call 重写为 flat name。
|
|
115
|
+
- tool_choice:namespace-typed 丢弃(chat 不认)。
|
|
116
|
+
|
|
117
|
+
返回 ``flat -> {namespace, name}`` 映射;无 namespace 工具时返回空 dict,
|
|
118
|
+
调用方可据此跳过响应侧还原。
|
|
119
|
+
"""
|
|
120
|
+
tools = body.get("tools")
|
|
121
|
+
if not isinstance(tools, list):
|
|
122
|
+
return {}
|
|
123
|
+
restore = build_restore_map(tools)
|
|
124
|
+
flat_tools: list[dict] = []
|
|
125
|
+
seen_flat: set[str] = set()
|
|
126
|
+
for tool in tools:
|
|
127
|
+
if not isinstance(tool, dict):
|
|
128
|
+
continue
|
|
129
|
+
if tool.get("type") == "namespace":
|
|
130
|
+
namespace = (tool.get("name") or "").strip()
|
|
131
|
+
for child in _namespace_children(tool):
|
|
132
|
+
if not isinstance(child, dict) or child.get("type") != "function":
|
|
133
|
+
continue
|
|
134
|
+
name = (child.get("name") or "").strip()
|
|
135
|
+
if not name or not namespace:
|
|
136
|
+
continue
|
|
137
|
+
flat = flatten_namespace_tool_name(namespace, name)
|
|
138
|
+
if flat in seen_flat:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"namespace 拍平撞名:{flat} 来自不同 child,上游无法消歧"
|
|
141
|
+
)
|
|
142
|
+
seen_flat.add(flat)
|
|
143
|
+
flat_tools.append({
|
|
144
|
+
"type": "function",
|
|
145
|
+
"name": flat,
|
|
146
|
+
"description": child.get("description", ""),
|
|
147
|
+
"parameters": child.get("parameters", {"type": "object", "properties": {}}),
|
|
148
|
+
**({"strict": child["strict"]} if "strict" in child else {}),
|
|
149
|
+
})
|
|
150
|
+
else:
|
|
151
|
+
flat_tools.append(tool)
|
|
152
|
+
body["tools"] = flat_tools
|
|
153
|
+
if restore and isinstance(body.get("input"), list):
|
|
154
|
+
_rewrite_qualified_calls(body["input"], restore)
|
|
155
|
+
tc = body.get("tool_choice")
|
|
156
|
+
if isinstance(tc, dict) and tc.get("type") == "namespace":
|
|
157
|
+
body.pop("tool_choice", None)
|
|
158
|
+
return restore
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def restore_function_call(item: dict, restore_map: dict[str, dict[str, str]]) -> dict:
|
|
162
|
+
"""把响应里 function_call 的 flat name 还原成 ``{name, namespace}``(原地改)。
|
|
163
|
+
|
|
164
|
+
无映射或 name 不在映射中时原样返回(普通 function 工具的 call 不受影响)。
|
|
165
|
+
"""
|
|
166
|
+
if not restore_map or item.get("type") != "function_call":
|
|
167
|
+
return item
|
|
168
|
+
flat = item.get("name")
|
|
169
|
+
entry = restore_map.get(flat) if isinstance(flat, str) else None
|
|
170
|
+
if entry:
|
|
171
|
+
item["name"] = entry["name"]
|
|
172
|
+
item["namespace"] = entry["namespace"]
|
|
173
|
+
return item
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""可独立运行的转换 proxy 入口:读 env → ProxyConfig → uvicorn。
|
|
2
|
+
|
|
3
|
+
用法:
|
|
4
|
+
KSPMAS_API_KEY=xxx python -m ksadk.model_proxy.protocol_proxy # 默认 127.0.0.1:8899
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import uvicorn
|
|
10
|
+
|
|
11
|
+
from .config import ProxyConfig
|
|
12
|
+
from .server import create_app
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
config = ProxyConfig.from_env()
|
|
17
|
+
port = int(os.environ.get("PORT", "8899"))
|
|
18
|
+
uvicorn.run(create_app(config), host="127.0.0.1", port=port, log_level="warning")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
main()
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""app factory + 进程内转换 proxy 生命周期管理。
|
|
2
|
+
|
|
3
|
+
把 HTTP 层与配置/转换解耦:create_app(config) 返回 FastAPI app;
|
|
4
|
+
ProxyServer 在后台线程懒起一个 localhost uvicorn,幂等 + 加锁 + 干净回收(随 runner 退出)。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import socket
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
import uvicorn
|
|
17
|
+
from fastapi import FastAPI, Request
|
|
18
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
19
|
+
|
|
20
|
+
from .config import _LOOPBACK_HOSTS, ProxyConfig
|
|
21
|
+
from .transform import Streamer, UnsupportedToolsError, chat_to_response, responses_to_chat
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _upstream_error(status_code: int) -> JSONResponse:
|
|
27
|
+
"""Return a stable public error without relaying an upstream traceback."""
|
|
28
|
+
return JSONResponse(
|
|
29
|
+
status_code=status_code,
|
|
30
|
+
content={
|
|
31
|
+
"error": {
|
|
32
|
+
"type": "upstream_error",
|
|
33
|
+
"message": "The model upstream rejected the request.",
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_app(config: ProxyConfig) -> FastAPI:
|
|
40
|
+
base = config.upstream_base.rstrip("/")
|
|
41
|
+
headers = {"Authorization": f"Bearer {config.api_key}", "Content-Type": "application/json"}
|
|
42
|
+
app = FastAPI()
|
|
43
|
+
# 应用层取消信号:stop() 时置位,让活动 SSE 的 _stream_gen 主动 break(比单靠 force_exit 可靠)
|
|
44
|
+
cancel = threading.Event()
|
|
45
|
+
app.state.cancel_event = cancel
|
|
46
|
+
|
|
47
|
+
@app.get("/healthz")
|
|
48
|
+
async def healthz():
|
|
49
|
+
return {"ok": True, "upstream": base}
|
|
50
|
+
|
|
51
|
+
@app.post("/v1/chat/completions")
|
|
52
|
+
async def chat_passthrough(req: Request):
|
|
53
|
+
"""chat 直通端点(env 框架发 chat 经代理原样转发上游,不转换)。
|
|
54
|
+
|
|
55
|
+
让代理成为统一模型出口:env 框架(ADK/langchain/deepagents 发 chat)经此直通,
|
|
56
|
+
codex(发 responses)经 /v1/responses 转换。统一出口为后续能力探测/路由打底。
|
|
57
|
+
chat 直通字节级转发 + 流式透传,前缀缓存不受影响。
|
|
58
|
+
"""
|
|
59
|
+
if (
|
|
60
|
+
config.local_token
|
|
61
|
+
and req.headers.get("authorization", "") != f"Bearer {config.local_token}"
|
|
62
|
+
):
|
|
63
|
+
return JSONResponse(
|
|
64
|
+
status_code=401, content={"error": {"type": "authentication_error"}}
|
|
65
|
+
)
|
|
66
|
+
body = await req.json()
|
|
67
|
+
is_stream = bool(body.get("stream"))
|
|
68
|
+
up_headers = {
|
|
69
|
+
"Authorization": f"Bearer {config.api_key}",
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
}
|
|
72
|
+
if is_stream:
|
|
73
|
+
return StreamingResponse(
|
|
74
|
+
_chat_passthrough_stream(config, base, up_headers, body),
|
|
75
|
+
media_type="text/event-stream",
|
|
76
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
|
77
|
+
)
|
|
78
|
+
async with httpx.AsyncClient(timeout=config.timeout) as c:
|
|
79
|
+
r = await c.post(f"{base}/chat/completions", json=body, headers=up_headers)
|
|
80
|
+
if r.is_error:
|
|
81
|
+
logger.warning("chat upstream rejected request: status=%s", r.status_code)
|
|
82
|
+
return _upstream_error(r.status_code)
|
|
83
|
+
return JSONResponse(status_code=r.status_code, content=r.json() if r.content else None)
|
|
84
|
+
|
|
85
|
+
@app.post("/v1/responses")
|
|
86
|
+
async def responses(req: Request):
|
|
87
|
+
# 本地鉴权:codex -> 本地 proxy 用 KSADK_PROXY_TOKEN;上游凭证独立保存于 config.api_key
|
|
88
|
+
if (
|
|
89
|
+
config.local_token
|
|
90
|
+
and req.headers.get("authorization", "") != f"Bearer {config.local_token}"
|
|
91
|
+
):
|
|
92
|
+
return JSONResponse(
|
|
93
|
+
status_code=401,
|
|
94
|
+
content={
|
|
95
|
+
"error": {"type": "authentication_error", "message": "invalid proxy token"}
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
body = await req.json()
|
|
99
|
+
try:
|
|
100
|
+
chat_req, restore_map = responses_to_chat(body)
|
|
101
|
+
except UnsupportedToolsError as e:
|
|
102
|
+
logger.info("responses request uses unsupported tools: %s", e)
|
|
103
|
+
return JSONResponse(
|
|
104
|
+
status_code=400,
|
|
105
|
+
content={
|
|
106
|
+
"error": {
|
|
107
|
+
"type": "unsupported_tools",
|
|
108
|
+
"message": "The request uses tools unsupported by the model upstream.",
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
rid = "resp_" + uuid.uuid4().hex[:24]
|
|
113
|
+
model = body.get("model")
|
|
114
|
+
if body.get("stream"):
|
|
115
|
+
chat_req["stream"] = True
|
|
116
|
+
chat_req["stream_options"] = {"include_usage": True}
|
|
117
|
+
return StreamingResponse(
|
|
118
|
+
_stream_gen(config, base, headers, chat_req, rid, model, cancel, restore_map),
|
|
119
|
+
media_type="text/event-stream",
|
|
120
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
|
121
|
+
)
|
|
122
|
+
async with httpx.AsyncClient(timeout=config.timeout) as c:
|
|
123
|
+
r = await c.post(f"{base}/chat/completions", json=chat_req, headers=headers)
|
|
124
|
+
if r.status_code != 200:
|
|
125
|
+
logger.warning("responses upstream rejected request: status=%s", r.status_code)
|
|
126
|
+
return _upstream_error(r.status_code)
|
|
127
|
+
return JSONResponse(chat_to_response(r.json(), rid, restore_map))
|
|
128
|
+
|
|
129
|
+
return app
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def _chat_passthrough_stream(config: ProxyConfig, base: str, headers: dict, body: dict):
|
|
133
|
+
"""chat SSE 字节级透传(不转换),让前缀缓存/流式格式与直连一致。"""
|
|
134
|
+
try:
|
|
135
|
+
async with httpx.AsyncClient(timeout=config.timeout) as c:
|
|
136
|
+
async with c.stream(
|
|
137
|
+
"POST", f"{base}/chat/completions", json=body, headers=headers
|
|
138
|
+
) as r:
|
|
139
|
+
async for line in r.aiter_lines():
|
|
140
|
+
yield line + "\n"
|
|
141
|
+
except Exception as exc: # noqa: BLE001
|
|
142
|
+
logger.warning("chat upstream stream failed", exc_info=exc)
|
|
143
|
+
yield 'data: {"error":"The model upstream stream failed."}\n\n'
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def _stream_gen(
|
|
147
|
+
config: ProxyConfig,
|
|
148
|
+
base: str,
|
|
149
|
+
headers: dict,
|
|
150
|
+
chat_req: dict,
|
|
151
|
+
rid: str,
|
|
152
|
+
model,
|
|
153
|
+
cancel: threading.Event,
|
|
154
|
+
restore_map: dict | None = None,
|
|
155
|
+
):
|
|
156
|
+
s = Streamer(rid, model, restore_map)
|
|
157
|
+
for e in s.start():
|
|
158
|
+
yield e
|
|
159
|
+
try:
|
|
160
|
+
# 超时必须有界(config.timeout):无限超时的活动 SSE 会让 stop() 后线程仍存活
|
|
161
|
+
async with httpx.AsyncClient(timeout=config.timeout) as c:
|
|
162
|
+
async with c.stream(
|
|
163
|
+
"POST", f"{base}/chat/completions", json=chat_req, headers=headers
|
|
164
|
+
) as r:
|
|
165
|
+
if r.status_code != 200:
|
|
166
|
+
logger.warning("responses upstream stream rejected: status=%s", r.status_code)
|
|
167
|
+
yield Streamer.ev(
|
|
168
|
+
"response.failed",
|
|
169
|
+
{
|
|
170
|
+
"type": "response.failed",
|
|
171
|
+
"response": {
|
|
172
|
+
**s._resp("failed"),
|
|
173
|
+
"error": {"message": "The model upstream rejected the request."},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
return
|
|
178
|
+
async for line in r.aiter_lines():
|
|
179
|
+
if cancel.is_set():
|
|
180
|
+
return # stop() 置位:主动中断活动 SSE,让线程能及时回收
|
|
181
|
+
line = line.strip()
|
|
182
|
+
if not line.startswith("data:"):
|
|
183
|
+
continue
|
|
184
|
+
data = line[5:].strip()
|
|
185
|
+
if data == "[DONE]":
|
|
186
|
+
break
|
|
187
|
+
try:
|
|
188
|
+
chunk = json.loads(data)
|
|
189
|
+
except ValueError:
|
|
190
|
+
continue
|
|
191
|
+
for e in s.handle(chunk):
|
|
192
|
+
yield e
|
|
193
|
+
except Exception as exc: # noqa: BLE001
|
|
194
|
+
logger.warning("responses upstream stream failed", exc_info=exc)
|
|
195
|
+
yield Streamer.ev(
|
|
196
|
+
"response.failed",
|
|
197
|
+
{
|
|
198
|
+
"type": "response.failed",
|
|
199
|
+
"response": {
|
|
200
|
+
**s._resp("failed"),
|
|
201
|
+
"error": {"message": "The model upstream stream failed."},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
return
|
|
206
|
+
for e in s.finalize():
|
|
207
|
+
yield e
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class ProxyServer:
|
|
211
|
+
"""进程内 localhost 转换 proxy 的生命周期管理。
|
|
212
|
+
|
|
213
|
+
- start 幂等(已启动直接返回)+ 加锁,避免重复 start 覆盖 server/thread 造成泄漏;
|
|
214
|
+
- 端口在 start 时 bind 并**一直持有 socket**(传给 uvicorn),避免"先释放再重绑"的 TOCTOU;
|
|
215
|
+
- 启动超时与 stop 都会干净回收线程与 socket。
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
def __init__(self, config: ProxyConfig, host: str = "127.0.0.1", port: int = 0):
|
|
219
|
+
if host not in _LOOPBACK_HOSTS and not config.local_token:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"非回环监听(host={host})必须设置 local_token,否则是无鉴权代持上游凭证的开放代理"
|
|
222
|
+
)
|
|
223
|
+
self.config = config
|
|
224
|
+
self.host = host
|
|
225
|
+
self._requested_port = port
|
|
226
|
+
self.port = port
|
|
227
|
+
self._lock = threading.Lock()
|
|
228
|
+
self._sock: socket.socket | None = None
|
|
229
|
+
self._server: uvicorn.Server | None = None
|
|
230
|
+
self._thread: threading.Thread | None = None
|
|
231
|
+
self._app: FastAPI | None = None
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def base_url(self) -> str:
|
|
235
|
+
return f"http://{self.host}:{self.port}/v1"
|
|
236
|
+
|
|
237
|
+
def start(self, wait_ready: float = 10.0) -> str:
|
|
238
|
+
with self._lock:
|
|
239
|
+
if self._server is not None:
|
|
240
|
+
return self.base_url # 幂等
|
|
241
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
242
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
243
|
+
try:
|
|
244
|
+
sock.bind((self.host, self._requested_port))
|
|
245
|
+
except OSError:
|
|
246
|
+
sock.close()
|
|
247
|
+
raise
|
|
248
|
+
self.port = int(sock.getsockname()[1])
|
|
249
|
+
app = create_app(self.config)
|
|
250
|
+
self._app = app
|
|
251
|
+
# timeout_graceful_shutdown 设上限:stop 时挂在 yield 的 SSE generator 不再消费,
|
|
252
|
+
# 若无限等待连接完成(None 默认)会让 shutdown 卡死、线程回收不掉
|
|
253
|
+
cfg = uvicorn.Config(app, log_level="warning", timeout_graceful_shutdown=1)
|
|
254
|
+
self._sock = sock
|
|
255
|
+
server = uvicorn.Server(cfg)
|
|
256
|
+
self._server = server
|
|
257
|
+
# serve(sockets=[sock]) 跨平台:uvicorn 直接复用已绑定 socket,
|
|
258
|
+
# 不走 Config(fd=...) 那条仅 Unix 可用的 socket.fromfd 路径
|
|
259
|
+
self._thread = threading.Thread(
|
|
260
|
+
target=lambda: asyncio.run(server.serve(sockets=[sock])), daemon=True
|
|
261
|
+
)
|
|
262
|
+
self._thread.start()
|
|
263
|
+
deadline = time.time() + wait_ready
|
|
264
|
+
while time.time() < deadline:
|
|
265
|
+
try:
|
|
266
|
+
if (
|
|
267
|
+
httpx.get(f"http://{self.host}:{self.port}/healthz", timeout=1).status_code
|
|
268
|
+
== 200
|
|
269
|
+
):
|
|
270
|
+
return self.base_url
|
|
271
|
+
except Exception: # noqa: BLE001
|
|
272
|
+
time.sleep(0.05)
|
|
273
|
+
self._teardown() # 启动超时也要清理线程与 socket
|
|
274
|
+
raise RuntimeError(f"ProxyServer 未在 {wait_ready}s 内就绪")
|
|
275
|
+
|
|
276
|
+
def stop(self):
|
|
277
|
+
with self._lock:
|
|
278
|
+
self._teardown()
|
|
279
|
+
|
|
280
|
+
def _teardown(self):
|
|
281
|
+
if self._app is not None:
|
|
282
|
+
# 应用层取消信号:让活动 SSE 的 _stream_gen 主动 break
|
|
283
|
+
self._app.state.cancel_event.set()
|
|
284
|
+
if self._server is not None:
|
|
285
|
+
# should_exit 触发主循环进入 shutdown;force_exit 让 shutdown 跳过等待
|
|
286
|
+
# connections/tasks 完成(活动 SSE 下不再无限等)。两者缺一:只 should_exit
|
|
287
|
+
# 会被活动连接卡死,只 force_exit 主循环根本不进 shutdown。
|
|
288
|
+
self._server.should_exit = True
|
|
289
|
+
self._server.force_exit = True
|
|
290
|
+
if self._thread is not None:
|
|
291
|
+
self._thread.join(timeout=5)
|
|
292
|
+
if self._sock is not None:
|
|
293
|
+
try:
|
|
294
|
+
self._sock.close()
|
|
295
|
+
except OSError:
|
|
296
|
+
pass
|
|
297
|
+
self._server = None
|
|
298
|
+
self._thread = None
|
|
299
|
+
self._sock = None
|
|
300
|
+
self._app = None
|