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/memory/manager.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Memory Manager - 统一记忆管理接口"""
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import logging
|
|
4
|
+
import os
|
|
5
5
|
from datetime import timedelta
|
|
6
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Dict, List, Optional, Type, cast
|
|
7
7
|
|
|
8
8
|
from ksadk.memory.backends.base import BaseMemoryBackend
|
|
9
9
|
from ksadk.memory.backends.memory import InMemoryBackend
|
|
@@ -24,26 +24,26 @@ def register_backend(name: str, backend_class: Type[BaseMemoryBackend]):
|
|
|
24
24
|
|
|
25
25
|
class MemoryManager:
|
|
26
26
|
"""统一记忆管理器
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
支持可插拔后端,自动从环境变量读取配置。
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
使用示例:
|
|
31
31
|
# 从环境变量自动配置
|
|
32
32
|
memory = MemoryManager.from_env()
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
# 手动配置
|
|
35
35
|
memory = MemoryManager(backend="redis", url="redis://localhost:6379")
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
# 存取数据
|
|
38
38
|
memory.set("key", {"data": "value"}, session_id="sess-123")
|
|
39
39
|
data = memory.get("key", session_id="sess-123")
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
# 消息历史
|
|
42
42
|
memory.add_message("sess-123", "user", "你好")
|
|
43
43
|
memory.add_message("sess-123", "assistant", "你好!有什么可以帮助你的?")
|
|
44
44
|
messages = memory.get_messages("sess-123")
|
|
45
45
|
"""
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
def __init__(
|
|
48
48
|
self,
|
|
49
49
|
backend: str = "memory",
|
|
@@ -53,7 +53,7 @@ class MemoryManager:
|
|
|
53
53
|
**kwargs,
|
|
54
54
|
):
|
|
55
55
|
"""初始化 Memory Manager
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
Args:
|
|
58
58
|
backend: 后端类型 ("memory", "redis")
|
|
59
59
|
url: 后端连接 URL
|
|
@@ -63,7 +63,7 @@ class MemoryManager:
|
|
|
63
63
|
"""
|
|
64
64
|
self.backend_name = backend
|
|
65
65
|
self._backend = self._create_backend(backend, url, prefix, default_ttl, **kwargs)
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
def _create_backend(
|
|
68
68
|
self,
|
|
69
69
|
backend: str,
|
|
@@ -76,25 +76,29 @@ class MemoryManager:
|
|
|
76
76
|
# 延迟导入 Redis backend
|
|
77
77
|
if backend == "redis":
|
|
78
78
|
from ksadk.memory.backends.redis import RedisBackend
|
|
79
|
+
|
|
79
80
|
_BACKENDS["redis"] = RedisBackend
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
if backend not in _BACKENDS:
|
|
82
83
|
raise ValueError(f"Unknown backend: {backend}. Available: {list(_BACKENDS.keys())}")
|
|
83
|
-
|
|
84
|
-
backend_class = _BACKENDS[backend]
|
|
85
|
-
|
|
84
|
+
|
|
85
|
+
backend_class: Any = _BACKENDS[backend]
|
|
86
|
+
|
|
86
87
|
# 根据后端类型传递参数
|
|
87
88
|
if backend == "memory":
|
|
88
|
-
return backend_class()
|
|
89
|
+
return cast(BaseMemoryBackend, backend_class())
|
|
89
90
|
elif backend == "redis":
|
|
90
|
-
return
|
|
91
|
+
return cast(
|
|
92
|
+
BaseMemoryBackend,
|
|
93
|
+
backend_class(url=url, prefix=prefix, default_ttl=default_ttl, **kwargs),
|
|
94
|
+
)
|
|
91
95
|
else:
|
|
92
|
-
return backend_class(**kwargs)
|
|
93
|
-
|
|
96
|
+
return cast(BaseMemoryBackend, backend_class(**kwargs))
|
|
97
|
+
|
|
94
98
|
@classmethod
|
|
95
99
|
def from_env(cls) -> "MemoryManager":
|
|
96
100
|
"""从环境变量创建 MemoryManager
|
|
97
|
-
|
|
101
|
+
|
|
98
102
|
环境变量:
|
|
99
103
|
KSADK_MEMORY_BACKEND: 后端类型 (默认 "memory")
|
|
100
104
|
KSADK_MEMORY_URL: 连接 URL (如 redis://localhost:6379)
|
|
@@ -104,27 +108,27 @@ class MemoryManager:
|
|
|
104
108
|
backend = os.environ.get("KSADK_MEMORY_BACKEND", "memory")
|
|
105
109
|
url = os.environ.get("KSADK_MEMORY_URL", "")
|
|
106
110
|
prefix = os.environ.get("KSADK_MEMORY_PREFIX", "ksadk:memory:")
|
|
107
|
-
|
|
111
|
+
|
|
108
112
|
ttl = None
|
|
109
113
|
ttl_str = os.environ.get("KSADK_MEMORY_TTL", "")
|
|
110
114
|
if ttl_str:
|
|
111
115
|
ttl = timedelta(seconds=int(ttl_str))
|
|
112
|
-
|
|
116
|
+
|
|
113
117
|
logger.debug(f"MemoryManager.from_env: backend={backend}, url={url[:20]}...")
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
return cls(
|
|
116
120
|
backend=backend,
|
|
117
121
|
url=url or None,
|
|
118
122
|
prefix=prefix,
|
|
119
123
|
default_ttl=ttl,
|
|
120
124
|
)
|
|
121
|
-
|
|
125
|
+
|
|
122
126
|
# ===== 键值操作 =====
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
def get(self, key: str, session_id: Optional[str] = None) -> Optional[Any]:
|
|
125
129
|
"""获取值"""
|
|
126
130
|
return self._backend.get(key, session_id)
|
|
127
|
-
|
|
131
|
+
|
|
128
132
|
def set(
|
|
129
133
|
self,
|
|
130
134
|
key: str,
|
|
@@ -133,24 +137,24 @@ class MemoryManager:
|
|
|
133
137
|
ttl: Optional[timedelta] = None,
|
|
134
138
|
) -> bool:
|
|
135
139
|
"""设置值"""
|
|
136
|
-
return self._backend.set(key, value, session_id, ttl)
|
|
137
|
-
|
|
140
|
+
return bool(self._backend.set(key, value, session_id, ttl))
|
|
141
|
+
|
|
138
142
|
def delete(self, key: str, session_id: Optional[str] = None) -> bool:
|
|
139
143
|
"""删除键"""
|
|
140
|
-
return self._backend.delete(key, session_id)
|
|
141
|
-
|
|
144
|
+
return bool(self._backend.delete(key, session_id))
|
|
145
|
+
|
|
142
146
|
def exists(self, key: str, session_id: Optional[str] = None) -> bool:
|
|
143
147
|
"""检查键是否存在"""
|
|
144
|
-
return self._backend.exists(key, session_id)
|
|
145
|
-
|
|
148
|
+
return bool(self._backend.exists(key, session_id))
|
|
149
|
+
|
|
146
150
|
# ===== 会话操作 =====
|
|
147
|
-
|
|
151
|
+
|
|
148
152
|
def clear_session(self, session_id: str) -> bool:
|
|
149
153
|
"""清除会话所有数据"""
|
|
150
|
-
return self._backend.clear_session(session_id)
|
|
151
|
-
|
|
154
|
+
return bool(self._backend.clear_session(session_id))
|
|
155
|
+
|
|
152
156
|
# ===== 消息历史 =====
|
|
153
|
-
|
|
157
|
+
|
|
154
158
|
def add_message(
|
|
155
159
|
self,
|
|
156
160
|
session_id: str,
|
|
@@ -159,39 +163,40 @@ class MemoryManager:
|
|
|
159
163
|
metadata: Optional[Dict] = None,
|
|
160
164
|
) -> bool:
|
|
161
165
|
"""添加消息"""
|
|
162
|
-
return self._backend.add_message(session_id, role, content, metadata)
|
|
163
|
-
|
|
166
|
+
return bool(self._backend.add_message(session_id, role, content, metadata))
|
|
167
|
+
|
|
164
168
|
def get_messages(
|
|
165
169
|
self,
|
|
166
170
|
session_id: str,
|
|
167
171
|
limit: Optional[int] = None,
|
|
168
172
|
) -> List[Dict]:
|
|
169
173
|
"""获取消息历史"""
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
messages = self._backend.get_messages(session_id, limit)
|
|
175
|
+
return list(messages)
|
|
176
|
+
|
|
172
177
|
# ===== 便捷方法 =====
|
|
173
|
-
|
|
178
|
+
|
|
174
179
|
def get_state(self, session_id: str) -> Dict:
|
|
175
180
|
"""获取会话状态 (短期记忆)"""
|
|
176
181
|
return self.get("__state__", session_id) or {}
|
|
177
|
-
|
|
182
|
+
|
|
178
183
|
def set_state(self, session_id: str, state: Dict, ttl: Optional[timedelta] = None) -> bool:
|
|
179
184
|
"""设置会话状态"""
|
|
180
185
|
return self.set("__state__", state, session_id, ttl)
|
|
181
|
-
|
|
186
|
+
|
|
182
187
|
def update_state(self, session_id: str, updates: Dict) -> bool:
|
|
183
188
|
"""更新会话状态 (合并)"""
|
|
184
189
|
current = self.get_state(session_id)
|
|
185
190
|
current.update(updates)
|
|
186
191
|
return self.set_state(session_id, current)
|
|
187
|
-
|
|
192
|
+
|
|
188
193
|
def close(self) -> None:
|
|
189
194
|
"""关闭连接"""
|
|
190
195
|
self._backend.close()
|
|
191
|
-
|
|
196
|
+
|
|
192
197
|
def __enter__(self):
|
|
193
198
|
return self
|
|
194
|
-
|
|
199
|
+
|
|
195
200
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
196
201
|
self.close()
|
|
197
202
|
|
|
@@ -202,10 +207,10 @@ _global_manager: Optional[MemoryManager] = None
|
|
|
202
207
|
|
|
203
208
|
def get_memory_manager() -> MemoryManager:
|
|
204
209
|
"""获取全局 MemoryManager 单例
|
|
205
|
-
|
|
210
|
+
|
|
206
211
|
在 Agent 代码中使用:
|
|
207
212
|
from ksadk.memory import get_memory_manager
|
|
208
|
-
|
|
213
|
+
|
|
209
214
|
memory = get_memory_manager()
|
|
210
215
|
memory.set("key", "value")
|
|
211
216
|
"""
|
ksadk/memory/service.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
7
|
import os
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, cast
|
|
9
9
|
|
|
10
10
|
from ksadk.common.aicp_env import resolve_aicp_connection
|
|
11
11
|
from ksadk.memory.adk.backends.base_ltm_backend import BaseLongTermMemoryBackend
|
|
@@ -49,7 +49,9 @@ class LongTermMemoryService:
|
|
|
49
49
|
self.backend_config = dict(backend_config or {})
|
|
50
50
|
self.top_k = top_k
|
|
51
51
|
self.app_name = app_name
|
|
52
|
-
backend_index =
|
|
52
|
+
backend_index = (
|
|
53
|
+
getattr(backend, "index", "") if isinstance(backend, BaseLongTermMemoryBackend) else ""
|
|
54
|
+
)
|
|
53
55
|
self.index = index or backend_index or app_name or "default_app"
|
|
54
56
|
self._backend = self._resolve_backend()
|
|
55
57
|
|
|
@@ -75,12 +77,10 @@ class LongTermMemoryService:
|
|
|
75
77
|
connection = resolve_aicp_connection("KSADK_LTM")
|
|
76
78
|
backend_config = {
|
|
77
79
|
"access_key": (
|
|
78
|
-
os.environ.get("KSADK_LTM_ACCESS_KEY")
|
|
79
|
-
or os.environ.get("KSYUN_ACCESS_KEY", "")
|
|
80
|
+
os.environ.get("KSADK_LTM_ACCESS_KEY") or os.environ.get("KSYUN_ACCESS_KEY", "")
|
|
80
81
|
),
|
|
81
82
|
"secret_key": (
|
|
82
|
-
os.environ.get("KSADK_LTM_SECRET_KEY")
|
|
83
|
-
or os.environ.get("KSYUN_SECRET_KEY", "")
|
|
83
|
+
os.environ.get("KSADK_LTM_SECRET_KEY") or os.environ.get("KSYUN_SECRET_KEY", "")
|
|
84
84
|
),
|
|
85
85
|
"region": connection["region"],
|
|
86
86
|
"endpoint": connection["endpoint"],
|
|
@@ -109,7 +109,7 @@ class LongTermMemoryService:
|
|
|
109
109
|
backend_cls = get_long_term_memory_backend_cls(str(self.backend))
|
|
110
110
|
config = dict(self.backend_config)
|
|
111
111
|
config.setdefault("index", self.index)
|
|
112
|
-
return backend_cls(**config)
|
|
112
|
+
return cast(BaseLongTermMemoryBackend, backend_cls(**config))
|
|
113
113
|
|
|
114
114
|
def search_entries(self, *, user_id: str, query: str, top_k: int | None = None) -> list[str]:
|
|
115
115
|
return self._backend.search_memory(
|
|
@@ -142,7 +142,9 @@ class LongTermMemoryService:
|
|
|
142
142
|
)
|
|
143
143
|
)
|
|
144
144
|
|
|
145
|
-
def save_text(
|
|
145
|
+
def save_text(
|
|
146
|
+
self, *, user_id: str, content: str, metadata: dict[str, Any] | None = None
|
|
147
|
+
) -> bool:
|
|
146
148
|
payload = {
|
|
147
149
|
"role": "user",
|
|
148
150
|
"parts": [{"text": content}],
|
ksadk/model_policy.py
CHANGED
|
@@ -58,10 +58,7 @@ DEFAULT_MODEL_POLICY: dict[str, Any] = {
|
|
|
58
58
|
def _deep_merge(base: dict[str, Any], override: Mapping[str, Any]) -> dict[str, Any]:
|
|
59
59
|
merged = copy.deepcopy(base)
|
|
60
60
|
for key, value in override.items():
|
|
61
|
-
if (
|
|
62
|
-
isinstance(value, Mapping)
|
|
63
|
-
and isinstance(merged.get(key), dict)
|
|
64
|
-
):
|
|
61
|
+
if isinstance(value, Mapping) and isinstance(merged.get(key), dict):
|
|
65
62
|
merged[key] = _deep_merge(merged[key], value)
|
|
66
63
|
else:
|
|
67
64
|
merged[key] = copy.deepcopy(value)
|
|
@@ -196,7 +193,12 @@ def build_runtime_model_policy_env(
|
|
|
196
193
|
multimodal = _role_model(normalized, "multimodal")
|
|
197
194
|
has_primary = any(
|
|
198
195
|
str(env.get(key) or "").strip()
|
|
199
|
-
for key in (
|
|
196
|
+
for key in (
|
|
197
|
+
"OPENCLAW_DEFAULT_MODEL",
|
|
198
|
+
"HERMES_DEFAULT_MODEL",
|
|
199
|
+
"OPENAI_MODEL_NAME",
|
|
200
|
+
"MODEL_NAME",
|
|
201
|
+
)
|
|
200
202
|
)
|
|
201
203
|
runtime_name = str(runtime or "").strip().lower()
|
|
202
204
|
if runtime_name == "openclaw":
|
|
@@ -206,7 +208,10 @@ def build_runtime_model_policy_env(
|
|
|
206
208
|
env.setdefault("OPENCLAW_FALLBACK_MODEL", _provider_ref(fallback))
|
|
207
209
|
if multimodal:
|
|
208
210
|
env.setdefault("OPENCLAW_IMAGE_MODEL", _provider_ref(multimodal))
|
|
209
|
-
env.setdefault(
|
|
211
|
+
env.setdefault(
|
|
212
|
+
"OPENCLAW_MODEL_CATALOG_JSON",
|
|
213
|
+
json.dumps(_catalog_from_policy(normalized), ensure_ascii=False),
|
|
214
|
+
)
|
|
210
215
|
return env
|
|
211
216
|
if runtime_name == "hermes":
|
|
212
217
|
if primary and not has_primary:
|
|
@@ -214,7 +219,10 @@ def build_runtime_model_policy_env(
|
|
|
214
219
|
env["HERMES_DEFAULT_MODEL"] = primary
|
|
215
220
|
if fallback:
|
|
216
221
|
env.setdefault("HERMES_FALLBACK_MODEL", fallback)
|
|
217
|
-
env.setdefault(
|
|
222
|
+
env.setdefault(
|
|
223
|
+
"HERMES_MODEL_CATALOG_JSON",
|
|
224
|
+
json.dumps(_catalog_from_policy(normalized), ensure_ascii=False),
|
|
225
|
+
)
|
|
218
226
|
return env
|
|
219
227
|
if primary and not has_primary:
|
|
220
228
|
env["OPENAI_MODEL_NAME"] = primary
|
|
@@ -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
|