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,51 @@
|
|
|
1
|
+
"""ksadk 模型出口协议转换层(P1 草案,待 codex review).
|
|
2
|
+
|
|
3
|
+
把 OpenAI Responses API <-> Chat Completions API 的转换内化为 ksadk 的模型出口中间层,
|
|
4
|
+
让只发 responses 的 runtime(codex)能透明使用所有 chat 协议模型,用户零感知。
|
|
5
|
+
|
|
6
|
+
设计见 docs/responses-chat-protocol-proxy-plan.md。
|
|
7
|
+
|
|
8
|
+
模块边界:
|
|
9
|
+
- transform: 纯转换器(无环境依赖),请求/响应/流式状态机。
|
|
10
|
+
- config: ProxyConfig 显式注入,不在导入时读 env。
|
|
11
|
+
- server: create_app(config) app factory + ProxyServer 生命周期管理。
|
|
12
|
+
- protocol_proxy: 可独立运行的入口(读 env → config → uvicorn)。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .bootstrap import setup_proxy_redirect_if_enabled, teardown_proxy_redirect
|
|
16
|
+
from .cache import CapabilityCache
|
|
17
|
+
from .config import ProxyConfig
|
|
18
|
+
from .detect import ModelCapabilities, probe_responses_capability
|
|
19
|
+
from .gate import ProxyGate
|
|
20
|
+
from .namespace import build_restore_map, flatten_namespace_tool_name, flatten_request_namespaces
|
|
21
|
+
from .server import ProxyServer # noqa: F401 (re-export)
|
|
22
|
+
from .transform import (
|
|
23
|
+
Streamer,
|
|
24
|
+
chat_to_response,
|
|
25
|
+
convert_tool_choice,
|
|
26
|
+
convert_tools,
|
|
27
|
+
convert_usage,
|
|
28
|
+
input_to_messages,
|
|
29
|
+
responses_to_chat,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"CapabilityCache",
|
|
34
|
+
"ModelCapabilities",
|
|
35
|
+
"ProxyConfig",
|
|
36
|
+
"ProxyGate",
|
|
37
|
+
"ProxyServer",
|
|
38
|
+
"Streamer",
|
|
39
|
+
"build_restore_map",
|
|
40
|
+
"chat_to_response",
|
|
41
|
+
"convert_tool_choice",
|
|
42
|
+
"convert_tools",
|
|
43
|
+
"convert_usage",
|
|
44
|
+
"flatten_namespace_tool_name",
|
|
45
|
+
"flatten_request_namespaces",
|
|
46
|
+
"input_to_messages",
|
|
47
|
+
"probe_responses_capability",
|
|
48
|
+
"responses_to_chat",
|
|
49
|
+
"setup_proxy_redirect_if_enabled",
|
|
50
|
+
"teardown_proxy_redirect",
|
|
51
|
+
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""env 型框架接线(v2.2):按需懒起 ProxyServer 并重定向 OPENAI_BASE_URL。
|
|
2
|
+
|
|
3
|
+
review v1 约束:setup_environment 在框架检测之前执行,无法判断 runtime。
|
|
4
|
+
故本模块用 :class:`ProxyGate`(模型白名单,不依赖 runtime 判断)控制:
|
|
5
|
+
gate.is_on(model) 为真时起一个进程内 ProxyServer,把 OPENAI_BASE_URL/
|
|
6
|
+
OPENAI_API_BASE 重定向到它;env 框架(ADK/langchain/deepagents 发 chat)经代理
|
|
7
|
+
的 /v1/chat/completions 直通上游(字节级,缓存不受影响),codex 走 v2.1 不经此。
|
|
8
|
+
|
|
9
|
+
默认 gate 关(ProxyGate.enabled=False),不重定向,历史路径零影响。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from .config import ProxyConfig
|
|
18
|
+
from .gate import ProxyGate
|
|
19
|
+
from .server import ProxyServer
|
|
20
|
+
|
|
21
|
+
# 进程级单例:setup_environment 多次调用只起一个 proxy
|
|
22
|
+
_proxy: Optional[ProxyServer] = None
|
|
23
|
+
_original_base: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def setup_proxy_redirect_if_enabled(
|
|
27
|
+
gate: ProxyGate | None = None,
|
|
28
|
+
model: str | None = None,
|
|
29
|
+
upstream_base: str | None = None,
|
|
30
|
+
api_key: str | None = None,
|
|
31
|
+
local_token: str | None = None,
|
|
32
|
+
) -> str | None:
|
|
33
|
+
"""gate 开启且模型在白名单时起 ProxyServer 并重定向 OPENAI_BASE_URL。
|
|
34
|
+
|
|
35
|
+
返回 proxy base_url(已重定向)或 None(未启用)。多次调用幂等(单例)。
|
|
36
|
+
上游凭证来自 ProxyConfig(从 env 读),不下发给子进程(凭证闭合)。
|
|
37
|
+
"""
|
|
38
|
+
global _proxy, _original_base
|
|
39
|
+
if _proxy is not None:
|
|
40
|
+
return _proxy.base_url # 幂等:已起
|
|
41
|
+
gate = gate or ProxyGate.from_env()
|
|
42
|
+
model = model or os.environ.get("OPENAI_MODEL_NAME") or os.environ.get("MODEL_NAME") or ""
|
|
43
|
+
if not gate.is_on(model=model or None):
|
|
44
|
+
return None
|
|
45
|
+
upstream = (
|
|
46
|
+
upstream_base
|
|
47
|
+
or os.environ.get("OPENAI_BASE_URL")
|
|
48
|
+
or os.environ.get("OPENAI_API_BASE")
|
|
49
|
+
or ""
|
|
50
|
+
)
|
|
51
|
+
key = (
|
|
52
|
+
api_key or os.environ.get("OPENAI_API_KEY") or os.environ.get("LLM_API_KEY") or ""
|
|
53
|
+
)
|
|
54
|
+
token = local_token or os.environ.get("KSADK_PROXY_TOKEN") or ""
|
|
55
|
+
if not upstream or not key:
|
|
56
|
+
return None # 缺凭证/上游,不启用(保持原 env)
|
|
57
|
+
cfg = ProxyConfig(upstream_base=upstream, api_key=key, local_token=token)
|
|
58
|
+
srv = ProxyServer(cfg)
|
|
59
|
+
srv.start()
|
|
60
|
+
_proxy = srv
|
|
61
|
+
# 记录原 base 并重定向(双别名都指向 proxy)
|
|
62
|
+
_original_base = os.environ.get("OPENAI_BASE_URL") or os.environ.get("OPENAI_API_BASE")
|
|
63
|
+
os.environ["OPENAI_BASE_URL"] = srv.base_url
|
|
64
|
+
os.environ["OPENAI_API_BASE"] = srv.base_url
|
|
65
|
+
if not token:
|
|
66
|
+
# 非 codex 路径(env 框架)无 token:代理 local_token 为空,仅回环监听才允许
|
|
67
|
+
os.environ["KSADK_PROXY_TOKEN"] = ""
|
|
68
|
+
return srv.base_url
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def teardown_proxy_redirect() -> None:
|
|
72
|
+
"""回收 proxy 并恢复原 OPENAI_BASE_URL(进程退出/卸载时调)。"""
|
|
73
|
+
global _proxy, _original_base
|
|
74
|
+
if _proxy is not None:
|
|
75
|
+
_proxy.stop()
|
|
76
|
+
_proxy = None
|
|
77
|
+
if _original_base is not None:
|
|
78
|
+
os.environ["OPENAI_BASE_URL"] = _original_base
|
|
79
|
+
os.environ["OPENAI_API_BASE"] = _original_base
|
|
80
|
+
_original_base = None
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""能力探测缓存(v2.4):租户隔离键 + TTL + singleflight + 主动失效。
|
|
2
|
+
|
|
3
|
+
review v1 指出 ``(model, base_url)`` 缺 credential/tenant 维度:同一网关不同 key
|
|
4
|
+
能力可能不同(如星流 glm-5.1 的 responses 403 按 consumer 鉴权),404 也可能是
|
|
5
|
+
"模型不存在"而非"endpoint 不存在"。本模块:
|
|
6
|
+
|
|
7
|
+
- 键 = ``(model, base_url, credential_scope)``;credential_scope 用进程内 HMAC
|
|
8
|
+
标识符,避免明文 key 入键/日志,又能区分不同 key。
|
|
9
|
+
- **明确判定**(supported/unsupported)长缓存(TTL);**不确定**(unknown)不缓存,
|
|
10
|
+
下次重探——避免把一次抖动固化成长期误判。
|
|
11
|
+
- **singleflight**:并发同键只探一次,其余等结果(用 per-key Future)。
|
|
12
|
+
- **主动失效**:`invalidate(model, base_url, scope)` 与 `clear()` 入口。
|
|
13
|
+
- 撞名/模型不存在的结构化区分由 detect.py 的错误分类负责,本缓存只存结论。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import hashlib
|
|
20
|
+
import secrets
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import Any, Callable
|
|
25
|
+
|
|
26
|
+
from .detect import ModelCapabilities
|
|
27
|
+
|
|
28
|
+
# The capability cache is process-local, so its credential namespace need not
|
|
29
|
+
# survive a restart. A random salt prevents cache identifiers from being used
|
|
30
|
+
# as an offline oracle for API keys and avoids retaining the original credential.
|
|
31
|
+
_SCOPE_SALT = secrets.token_bytes(16)
|
|
32
|
+
_SCOPE_ITERATIONS = 100_000
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def credential_scope(credential: str) -> str:
|
|
36
|
+
"""Return a process-local, non-secret cache scope for a credential."""
|
|
37
|
+
return hashlib.pbkdf2_hmac(
|
|
38
|
+
"sha256", credential.encode("utf-8"), _SCOPE_SALT, _SCOPE_ITERATIONS, dklen=16
|
|
39
|
+
).hex()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class _Entry:
|
|
44
|
+
caps: ModelCapabilities
|
|
45
|
+
expires_at: float # 0 = 永不过期(明确判定)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CapabilityCache:
|
|
49
|
+
"""能力矩阵缓存:线程安全 + asyncio 安全(单进程内)。"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, ttl: float = 3600.0):
|
|
52
|
+
self._ttl = ttl
|
|
53
|
+
self._lock = threading.Lock()
|
|
54
|
+
self._entries: dict[tuple[str, str, str], _Entry] = {}
|
|
55
|
+
# singleflight:sync per-key Event,async per-key Future
|
|
56
|
+
self._inflight_sync: dict[tuple[str, str, str], threading.Event] = {}
|
|
57
|
+
self._inflight_async: dict[tuple[str, str, str], asyncio.Future] = {}
|
|
58
|
+
|
|
59
|
+
def _key(self, model: str, base: str, scope: str) -> tuple[str, str, str]:
|
|
60
|
+
return (model, base.rstrip("/"), scope)
|
|
61
|
+
|
|
62
|
+
def get(self, model: str, base: str, scope: str) -> ModelCapabilities | None:
|
|
63
|
+
"""命中且未过期返回 caps;过期/不存在/不确定(不缓存)返回 None。"""
|
|
64
|
+
k = self._key(model, base, scope)
|
|
65
|
+
with self._lock:
|
|
66
|
+
e = self._entries.get(k)
|
|
67
|
+
if e is None:
|
|
68
|
+
return None
|
|
69
|
+
if e.expires_at and time.time() > e.expires_at:
|
|
70
|
+
self._entries.pop(k, None)
|
|
71
|
+
return None
|
|
72
|
+
return e.caps
|
|
73
|
+
|
|
74
|
+
def put(self, model: str, base: str, scope: str, caps: ModelCapabilities) -> None:
|
|
75
|
+
"""只缓存明确判定(supported/unsupported);unknown 不缓存。"""
|
|
76
|
+
if caps.verdict == "unknown":
|
|
77
|
+
return
|
|
78
|
+
k = self._key(model, base, scope)
|
|
79
|
+
expires = time.time() + self._ttl if self._ttl > 0 else 0
|
|
80
|
+
with self._lock:
|
|
81
|
+
self._entries[k] = _Entry(caps=caps, expires_at=expires)
|
|
82
|
+
|
|
83
|
+
def invalidate(self, model: str, base: str, scope: str) -> None:
|
|
84
|
+
k = self._key(model, base, scope)
|
|
85
|
+
with self._lock:
|
|
86
|
+
self._entries.pop(k, None)
|
|
87
|
+
|
|
88
|
+
def clear(self) -> None:
|
|
89
|
+
with self._lock:
|
|
90
|
+
self._entries.clear()
|
|
91
|
+
|
|
92
|
+
# ---- singleflight:并发同键只探一次 ----
|
|
93
|
+
def get_or_probe(
|
|
94
|
+
self,
|
|
95
|
+
model: str,
|
|
96
|
+
base: str,
|
|
97
|
+
scope: str,
|
|
98
|
+
probe: Callable[[str, str], ModelCapabilities],
|
|
99
|
+
) -> ModelCapabilities:
|
|
100
|
+
"""命中直接返回;否则 singleflight 探测(并发同键只探一次),结果入缓存。
|
|
101
|
+
|
|
102
|
+
``scope`` is a pre-derived credential namespace. ``probe`` is a sync
|
|
103
|
+
callable(model, base) -> ModelCapabilities and closes over credentials.
|
|
104
|
+
"""
|
|
105
|
+
cached = self.get(model, base, scope)
|
|
106
|
+
if cached is not None:
|
|
107
|
+
return cached
|
|
108
|
+
k = self._key(model, base, scope)
|
|
109
|
+
with self._lock:
|
|
110
|
+
ev = self._inflight_sync.get(k)
|
|
111
|
+
if ev is not None:
|
|
112
|
+
# 已有并发探测在进行:等它完成,读缓存(探测方负责 put)
|
|
113
|
+
pass
|
|
114
|
+
else:
|
|
115
|
+
ev = threading.Event()
|
|
116
|
+
self._inflight_sync[k] = ev
|
|
117
|
+
ev = None # 标记本线程负责探测
|
|
118
|
+
if ev is not None:
|
|
119
|
+
ev.wait(timeout=30.0)
|
|
120
|
+
return self.get(model, base, scope) or ModelCapabilities(verdict="unknown")
|
|
121
|
+
# 本线程负责探测
|
|
122
|
+
try:
|
|
123
|
+
caps = probe(model, base)
|
|
124
|
+
self.put(model, base, scope, caps)
|
|
125
|
+
return caps
|
|
126
|
+
finally:
|
|
127
|
+
with self._lock:
|
|
128
|
+
ev_done = self._inflight_sync.pop(k, None)
|
|
129
|
+
if ev_done is not None:
|
|
130
|
+
ev_done.set()
|
|
131
|
+
|
|
132
|
+
async def aget_or_probe(
|
|
133
|
+
self,
|
|
134
|
+
model: str,
|
|
135
|
+
base: str,
|
|
136
|
+
scope: str,
|
|
137
|
+
probe: Callable[[str, str], Any],
|
|
138
|
+
) -> ModelCapabilities:
|
|
139
|
+
"""async singleflight 版:probe 是 async callable -> ModelCapabilities。"""
|
|
140
|
+
cached = self.get(model, base, scope)
|
|
141
|
+
if cached is not None:
|
|
142
|
+
return cached
|
|
143
|
+
k = self._key(model, base, scope)
|
|
144
|
+
loop = asyncio.get_running_loop()
|
|
145
|
+
with self._lock:
|
|
146
|
+
fut = self._inflight_async.get(k)
|
|
147
|
+
if fut is None:
|
|
148
|
+
fut = loop.create_future()
|
|
149
|
+
self._inflight_async[k] = fut
|
|
150
|
+
own = True
|
|
151
|
+
else:
|
|
152
|
+
own = False
|
|
153
|
+
if not own:
|
|
154
|
+
await fut # type: ignore[no-any-return]
|
|
155
|
+
# owner 已 put 入缓存;从缓存读结果(不用 fut 的值,它是 None 占位)
|
|
156
|
+
return self.get(model, base, scope) or ModelCapabilities(verdict="unknown")
|
|
157
|
+
try:
|
|
158
|
+
caps: ModelCapabilities = await probe(model, base)
|
|
159
|
+
self.put(model, base, scope, caps)
|
|
160
|
+
return caps
|
|
161
|
+
finally:
|
|
162
|
+
with self._lock:
|
|
163
|
+
self._inflight_async.pop(k, None)
|
|
164
|
+
if not fut.done():
|
|
165
|
+
fut.set_result(None) # 唤醒等待者(它们会读缓存)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""ProxyConfig:转换 proxy 的配置(显式注入,不在模块导入时读 env,便于内化与测试)。"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from urllib.parse import urlsplit
|
|
6
|
+
|
|
7
|
+
# HTTPS(凭证安全):不再用明文 HTTP 携带 Bearer key。
|
|
8
|
+
DEFAULT_UPSTREAM_BASE = "https://kspmas.ksyun.com/v1"
|
|
9
|
+
|
|
10
|
+
_LOOPBACK_HOSTS = {"localhost", "127.0.0.1", "::1"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ProxyConfig:
|
|
15
|
+
upstream_base: str = DEFAULT_UPSTREAM_BASE
|
|
16
|
+
api_key: str = "" # 上游凭证(proxy -> 上游)
|
|
17
|
+
local_token: str = "" # 本地 proxy 鉴权 token(codex -> proxy);空则不校验(仅本地调试)
|
|
18
|
+
timeout: float = 180.0
|
|
19
|
+
|
|
20
|
+
def __post_init__(self) -> None:
|
|
21
|
+
self.upstream_base = _require_secure_upstream(self.upstream_base.rstrip("/"))
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_env(cls) -> "ProxyConfig":
|
|
25
|
+
return cls(
|
|
26
|
+
upstream_base=os.environ.get("UPSTREAM_BASE", DEFAULT_UPSTREAM_BASE),
|
|
27
|
+
api_key=os.environ.get("KSPMAS_API_KEY", ""),
|
|
28
|
+
local_token=os.environ.get("KSADK_PROXY_TOKEN", ""),
|
|
29
|
+
timeout=float(os.environ.get("UPSTREAM_TIMEOUT", "180")),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _require_secure_upstream(upstream: str) -> str:
|
|
34
|
+
"""凭证安全:scheme 仅 http/https;非回环主机必须 https(避免明文携带 Bearer)。
|
|
35
|
+
|
|
36
|
+
用 urlsplit 规范化 scheme/hostname,堵 `HTTP://`(大小写)与 `ftp://` 绕过。
|
|
37
|
+
"""
|
|
38
|
+
parts = urlsplit(upstream)
|
|
39
|
+
scheme = parts.scheme.lower()
|
|
40
|
+
host = (parts.hostname or "").lower()
|
|
41
|
+
if scheme not in ("http", "https"):
|
|
42
|
+
raise ValueError(f"不支持的上游 scheme(仅 http/https): {upstream}")
|
|
43
|
+
if scheme == "http" and host not in _LOOPBACK_HOSTS:
|
|
44
|
+
raise ValueError(f"上游必须使用 https(携带凭证,禁明文 http): {upstream}")
|
|
45
|
+
return upstream
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""极简 codex 对话 webui(端到端验证用,非生产入口)。
|
|
2
|
+
|
|
3
|
+
绕过 ksadk web 的框架检测(它不认 codex):直接用 AsyncCodexClient + 一个
|
|
4
|
+
最小 FastAPI 提供 HTML 页 + /chat SSE,在浏览器里展示 codex runtime 经
|
|
5
|
+
model_proxy 代理跑星流 chat 模型的真实对话流。
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
KSADK_CODEX_USE_PROXY=1 OPENAI_API_BASE=https://kspmas.ksyun.com/v1 \
|
|
9
|
+
OPENAI_API_KEY=xxx CODEX_HOME=/tmp/codex_clean_runtime \
|
|
10
|
+
uv run python -m ksadk.model_proxy.demo_webui --port 8877
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
import uvicorn
|
|
20
|
+
from fastapi import FastAPI, Request
|
|
21
|
+
from fastapi.responses import HTMLResponse, StreamingResponse
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# ruff: noqa: E501 内联 HTML/CSS/JS 演示字面量,长行不拆
|
|
26
|
+
|
|
27
|
+
_PAGE = """<!doctype html><html lang="zh"><head><meta charset="utf-8">
|
|
28
|
+
<title>codex runtime e2e</title>
|
|
29
|
+
<style>
|
|
30
|
+
body{font-family:system-ui,sans-serif;max-width:760px;margin:2rem auto;padding:0 1rem}
|
|
31
|
+
#log{border:1px solid #ddd;border-radius:8px;padding:1rem;min-height:200px;white-space:pre-wrap;font-family:ui-monospace,monospace}
|
|
32
|
+
.u{color:#0969da}.a{color:#1a7f37}.t{color:#666}
|
|
33
|
+
input{width:70%;padding:.5rem}button{padding:.5rem 1rem}
|
|
34
|
+
</style></head><body>
|
|
35
|
+
<h3>codex runtime → model_proxy → 星流 chat</h3>
|
|
36
|
+
<form id="f"><input id="q" placeholder="说点什么..." autocomplete="off">
|
|
37
|
+
<button>发送</button></form>
|
|
38
|
+
<div id="log"></div>
|
|
39
|
+
<script>
|
|
40
|
+
const log=document.getElementById('log');
|
|
41
|
+
function add(cls,text){const d=document.createElement('div');d.className=cls;d.textContent=text;log.appendChild(d);log.scrollTop=log.scrollHeight;}
|
|
42
|
+
document.getElementById('f').onsubmit=async e=>{
|
|
43
|
+
e.preventDefault();const q=document.getElementById('q').value;if(!q)return;
|
|
44
|
+
add('u','你: '+q);document.getElementById('q').value='';const t=document.createElement('div');t.className='t';t.textContent='(流式中...)';log.appendChild(t);
|
|
45
|
+
const r=await fetch('/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({q})});
|
|
46
|
+
const reader=r.body.getReader();const dec=new TextDecoder();t.remove();let buf='';
|
|
47
|
+
while(true){const{value,done}=await reader.read();if(done)break;buf+=dec.decode(value);const lines=buf.split('\\n');buf=lines.pop();
|
|
48
|
+
for(const ln of lines){if(!ln.startsWith('data:'))continue;try{const ev=JSON.parse(ln.slice(5).trim());
|
|
49
|
+
if(ev.delta)add('a',ev.delta);if(ev.reply)add('a','✅ '+ev.reply);if(ev.error)add('t','❌ '+ev.error);}catch{}}}
|
|
50
|
+
};
|
|
51
|
+
</script></body></html>"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def create_demo_app() -> FastAPI:
|
|
55
|
+
from ksadk.codex.client import AsyncCodexClient
|
|
56
|
+
|
|
57
|
+
app = FastAPI()
|
|
58
|
+
client: dict = {"c": None, "tid": None}
|
|
59
|
+
|
|
60
|
+
@app.get("/", response_class=HTMLResponse)
|
|
61
|
+
def index():
|
|
62
|
+
return _PAGE
|
|
63
|
+
|
|
64
|
+
@app.post("/chat")
|
|
65
|
+
async def chat(req: Request):
|
|
66
|
+
body = await req.json()
|
|
67
|
+
q = body.get("q", "")
|
|
68
|
+
|
|
69
|
+
async def gen():
|
|
70
|
+
if client["c"] is None:
|
|
71
|
+
c = AsyncCodexClient()
|
|
72
|
+
client["c"] = c
|
|
73
|
+
client["tid"] = await c.start_thread(
|
|
74
|
+
config={
|
|
75
|
+
"model": os.environ.get("OPENAI_MODEL_NAME", "glm-5.2"),
|
|
76
|
+
"sandbox_read_only": True,
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
async for ev in client["c"].run_turn(client["tid"], q):
|
|
81
|
+
m = ev.get("method", "")
|
|
82
|
+
if m == "item/agentMessage/delta":
|
|
83
|
+
yield f"data: {json.dumps({'delta': ev['params'].get('delta', '')}, ensure_ascii=False)}\n\n"
|
|
84
|
+
elif m == "item/completed":
|
|
85
|
+
it = ev["params"]["item"]
|
|
86
|
+
if it.get("type") == "agentMessage" and it.get("text"):
|
|
87
|
+
yield f"data: {json.dumps({'reply': it['text']}, ensure_ascii=False)}\n\n"
|
|
88
|
+
elif m == "error":
|
|
89
|
+
logger.warning("Codex demo runtime reported an error")
|
|
90
|
+
yield 'data: {"error":"Codex runtime failed; see local logs."}\n\n'
|
|
91
|
+
except Exception as e: # noqa: BLE001
|
|
92
|
+
logger.warning("Codex demo runtime failed", exc_info=e)
|
|
93
|
+
yield 'data: {"error":"Codex runtime failed; see local logs."}\n\n'
|
|
94
|
+
|
|
95
|
+
return StreamingResponse(gen(), media_type="text/event-stream")
|
|
96
|
+
|
|
97
|
+
@app.on_event("shutdown")
|
|
98
|
+
async def shutdown():
|
|
99
|
+
if client["c"] is not None:
|
|
100
|
+
await client["c"].close()
|
|
101
|
+
|
|
102
|
+
return app
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main():
|
|
106
|
+
import argparse
|
|
107
|
+
|
|
108
|
+
p = argparse.ArgumentParser()
|
|
109
|
+
p.add_argument("--port", type=int, default=8877)
|
|
110
|
+
args = p.parse_args()
|
|
111
|
+
uvicorn.run(create_demo_app(), host="127.0.0.1", port=args.port, log_level="warning")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
main()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""模型能力矩阵探测(v2.3):替代简单 responses/chat 二分。
|
|
2
|
+
|
|
3
|
+
"支持 responses" 不等于 "该直通"——glm-5.1 原生 responses 丢增量 delta + 拒 namespace 工具,
|
|
4
|
+
经转换层反而更好。本模块用能力矩阵描述一个 (model, base_url, credential) 的真实能力,
|
|
5
|
+
供路由决策(直通 responses / 走转换层 chat / …)。
|
|
6
|
+
|
|
7
|
+
探测铁律(review v1):
|
|
8
|
+
- **功能性 probe 而非状态码 probe**:发最小真请求打 /v1/responses,校验返回是否为
|
|
9
|
+
合法 responses 结构(有 ``output``/``status``),不只看 HTTP 200——网关对不支持的
|
|
10
|
+
端点有时也返回 200 但内容是错误页。
|
|
11
|
+
- **只在 "确凿协议否定" 时翻案**:`404/405/400 unknown endpoint` 才判 "不支持 responses";
|
|
12
|
+
超时/5xx/429/401/403 一律 "unknown",不改变判定(故障 ≠ 能力缺失)。
|
|
13
|
+
- ``stream_delta_ok`` 需真发流式请求才能判定,成本高,本模块默认 None(不探),
|
|
14
|
+
由调用方按需触发或默认走转换层(转换层自己生成完整 delta)。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import time
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Any, Literal
|
|
22
|
+
|
|
23
|
+
import httpx
|
|
24
|
+
|
|
25
|
+
Verdict = Literal["supported", "unsupported", "unknown"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ModelCapabilities:
|
|
30
|
+
"""一个 (model, base_url, credential_scope) 的协议能力矩阵。"""
|
|
31
|
+
|
|
32
|
+
responses_supported: bool | None = None # None = 未知(未探/不确定)
|
|
33
|
+
stream_delta_ok: bool | None = None # 流式增量 delta;None = 未探
|
|
34
|
+
tool_types: set[str] = field(default_factory=set) # 支持的工具 namespace
|
|
35
|
+
preferred_protocol: str = "chat" # "responses" | "chat":路由该走哪条
|
|
36
|
+
checked_at: float = 0.0
|
|
37
|
+
verdict: Verdict = "unknown" # 最近一次探测结论
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _classify_responses_error(status: int, body: str) -> Verdict:
|
|
41
|
+
"""把非 200 响应分类成 supported/unsupported/unknown。
|
|
42
|
+
|
|
43
|
+
只有 "确凿协议否定" 才判 unsupported;故障类一律 unknown(不误固化)。
|
|
44
|
+
"""
|
|
45
|
+
if status in (404, 405):
|
|
46
|
+
return "unsupported"
|
|
47
|
+
if status == 400:
|
|
48
|
+
low = (body or "").lower()
|
|
49
|
+
# 400 且 body 含 "unknown/not supported/endpoint" -> endpoint 不支持
|
|
50
|
+
if any(k in low for k in ("unknown", "not supported", "endpoint", "unrecognized")):
|
|
51
|
+
return "unsupported"
|
|
52
|
+
return "unknown" # 400 但不像协议级否定(可能是请求字段问题)
|
|
53
|
+
# 401/403:鉴权/权限问题,不代表 endpoint 不存在(同 key 不同能力,见 glm-5.1 403)
|
|
54
|
+
if status in (401, 403):
|
|
55
|
+
return "unknown"
|
|
56
|
+
return "unknown" # 5xx/429/其他:故障类
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def probe_responses_capability(
|
|
60
|
+
client: httpx.Client | httpx.AsyncClient,
|
|
61
|
+
base: str,
|
|
62
|
+
key: str,
|
|
63
|
+
model: str,
|
|
64
|
+
timeout: float = 30.0,
|
|
65
|
+
):
|
|
66
|
+
"""功能性 probe /v1/responses,返回能力矩阵(sync) 或 coroutine(async)。
|
|
67
|
+
|
|
68
|
+
- 200 且结构合法(output+status)→ supported,preferred_protocol=responses
|
|
69
|
+
- 200 但非 responses 结构(网关伪 200)→ unsupported,preferred_protocol=chat
|
|
70
|
+
- 404/405/400 unknown → unsupported,preferred_protocol=chat
|
|
71
|
+
- 超时/5xx/429/401/403 → unknown,preferred_protocol 保持默认 chat(保守走转换层)
|
|
72
|
+
|
|
73
|
+
按 client 类型分发:``httpx.Client`` 同步返回 ``ModelCapabilities``;
|
|
74
|
+
``httpx.AsyncClient`` 返回 coroutine(调用方 await)。
|
|
75
|
+
"""
|
|
76
|
+
if isinstance(client, httpx.AsyncClient):
|
|
77
|
+
return _probe_async(client, base, key, model, timeout)
|
|
78
|
+
if isinstance(client, httpx.Client):
|
|
79
|
+
return _probe_sync(client, base, key, model, timeout)
|
|
80
|
+
raise TypeError(f"client 必须是 httpx.Client/AsyncClient,不是 {type(client).__name__}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _probe_sync(
|
|
84
|
+
client: httpx.Client, base: str, key: str, model: str, timeout: float
|
|
85
|
+
) -> ModelCapabilities:
|
|
86
|
+
caps = ModelCapabilities(checked_at=time.time(), verdict="unknown")
|
|
87
|
+
url = f"{base.rstrip('/')}/responses"
|
|
88
|
+
headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
|
|
89
|
+
payload = {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
|
|
90
|
+
try:
|
|
91
|
+
r = client.post(url, json=payload, headers=headers, timeout=timeout)
|
|
92
|
+
except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
|
|
93
|
+
return _finalize(caps)
|
|
94
|
+
try:
|
|
95
|
+
data = r.json()
|
|
96
|
+
except ValueError:
|
|
97
|
+
data = None
|
|
98
|
+
return _apply_response(caps, r.status_code, r.text, data)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def _probe_async(
|
|
102
|
+
client: httpx.AsyncClient, base: str, key: str, model: str, timeout: float
|
|
103
|
+
) -> ModelCapabilities:
|
|
104
|
+
caps = ModelCapabilities(checked_at=time.time(), verdict="unknown")
|
|
105
|
+
url = f"{base.rstrip('/')}/responses"
|
|
106
|
+
headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
|
|
107
|
+
payload = {"model": model, "input": "hi", "max_output_tokens": 1, "stream": False}
|
|
108
|
+
try:
|
|
109
|
+
r = await client.post(url, json=payload, headers=headers, timeout=timeout)
|
|
110
|
+
except (httpx.TimeoutException, httpx.ConnectError, httpx.HTTPError):
|
|
111
|
+
return _finalize(caps)
|
|
112
|
+
try:
|
|
113
|
+
data = r.json()
|
|
114
|
+
except ValueError:
|
|
115
|
+
data = None
|
|
116
|
+
return _apply_response(caps, r.status_code, r.text, data)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _apply_response(
|
|
120
|
+
caps: ModelCapabilities, status: int, text: str, data: Any
|
|
121
|
+
) -> ModelCapabilities:
|
|
122
|
+
if status == 200:
|
|
123
|
+
# 功能性校验:合法 responses 结构有 output + status(不只看 200)
|
|
124
|
+
if isinstance(data, dict) and "output" in data and "status" in data:
|
|
125
|
+
caps.responses_supported = True
|
|
126
|
+
caps.verdict = "supported"
|
|
127
|
+
else:
|
|
128
|
+
# 200 但不是 responses 结构(网关伪 200 / 错误页)
|
|
129
|
+
caps.responses_supported = False
|
|
130
|
+
caps.verdict = "unsupported"
|
|
131
|
+
return _finalize(caps)
|
|
132
|
+
caps.verdict = _classify_responses_error(status, text)
|
|
133
|
+
caps.responses_supported = False if caps.verdict == "unsupported" else None
|
|
134
|
+
return _finalize(caps)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _finalize(caps: ModelCapabilities) -> ModelCapabilities:
|
|
138
|
+
"""根据 verdict 定 preferred_protocol(保守:不确定也走转换层 chat)。"""
|
|
139
|
+
if caps.verdict == "supported" and caps.responses_supported:
|
|
140
|
+
caps.preferred_protocol = "responses"
|
|
141
|
+
else:
|
|
142
|
+
# unsupported 或 unknown 都默认 chat(走转换层):unknown 时走转换层更安全,
|
|
143
|
+
# 因为转换层对 chat 模型是确定可用的,而直连 responses 在 unknown 下有风险
|
|
144
|
+
caps.preferred_protocol = "chat"
|
|
145
|
+
return caps
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""开关与灰度配置(v2.5):默认关闭,按需启用,一键回退直连。
|
|
2
|
+
|
|
3
|
+
review v1 要求:全局默认关,支持按 agent/按模型白名单开启,故障一键回退。
|
|
4
|
+
设计为纯配置判定(env/agentengine.yaml 解析后传入),不含网络/proxy 生命周期——
|
|
5
|
+
后者由 v2.1(codex)/v2.2(env 框架)各自接线时调用。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ProxyGate:
|
|
16
|
+
"""协议转换层的开关/灰度判定(纯函数,不持有运行时状态)。
|
|
17
|
+
|
|
18
|
+
- ``enabled``:全局开关,默认 False。
|
|
19
|
+
- ``agent_allowlist``/``model_allowlist``:细粒度白名单;空=不按此维度放行。
|
|
20
|
+
- ``denylist``:显式拒绝(优先级最高),用于故障一键回退某 agent/模型。
|
|
21
|
+
|
|
22
|
+
判定优先级:denylist > 全局 enabled > 白名单。
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
enabled: bool = False
|
|
26
|
+
agent_allowlist: set[str] = field(default_factory=set)
|
|
27
|
+
model_allowlist: set[str] = field(default_factory=set)
|
|
28
|
+
denylist: set[str] = field(default_factory=set)
|
|
29
|
+
|
|
30
|
+
def is_on(self, agent: str | None = None, model: str | None = None) -> bool:
|
|
31
|
+
"""是否对该 (agent, model) 启用转换层。
|
|
32
|
+
|
|
33
|
+
- denylist 命中(agent 或 model)→ False(一键回退)
|
|
34
|
+
- 全局 enabled=False → False(默认关)
|
|
35
|
+
- 全局 enabled=True → True(除非 denylist)
|
|
36
|
+
- 全局关但白名单命中(agent 或 model)→ True(灰度放行)
|
|
37
|
+
"""
|
|
38
|
+
if agent and agent in self.denylist:
|
|
39
|
+
return False
|
|
40
|
+
if model and model in self.denylist:
|
|
41
|
+
return False
|
|
42
|
+
if self.enabled:
|
|
43
|
+
return True
|
|
44
|
+
if agent and agent in self.agent_allowlist:
|
|
45
|
+
return True
|
|
46
|
+
if model and model in self.model_allowlist:
|
|
47
|
+
return True
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_env(cls) -> "ProxyGate":
|
|
52
|
+
"""从 env 读开关与白名单(逗号分隔)。
|
|
53
|
+
|
|
54
|
+
- ``KSADK_MODEL_PROXY_ENABLED=1`` 全局开
|
|
55
|
+
- ``KSADK_MODEL_PROXY_AGENTS=a,b`` agent 白名单
|
|
56
|
+
- ``KSADK_MODEL_PROXY_MODELS=glm-5.2`` model 白名单
|
|
57
|
+
- ``KSADK_MODEL_PROXY_DENY=c`` 一键回退
|
|
58
|
+
"""
|
|
59
|
+
def _split(name: str) -> set[str]:
|
|
60
|
+
v = os.environ.get(name, "").strip()
|
|
61
|
+
return {x.strip() for x in v.split(",") if x.strip()} if v else set()
|
|
62
|
+
|
|
63
|
+
return cls(
|
|
64
|
+
enabled=os.environ.get("KSADK_MODEL_PROXY_ENABLED") == "1",
|
|
65
|
+
agent_allowlist=_split("KSADK_MODEL_PROXY_AGENTS"),
|
|
66
|
+
model_allowlist=_split("KSADK_MODEL_PROXY_MODELS"),
|
|
67
|
+
denylist=_split("KSADK_MODEL_PROXY_DENY"),
|
|
68
|
+
)
|