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
ksadk/cli/cmd_invoke.py
CHANGED
|
@@ -4,16 +4,18 @@ agentengine invoke - 与已部署的 Agent 进行交互
|
|
|
4
4
|
支持 OpenAI 兼容格式调用,支持流式输出
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import click
|
|
8
7
|
import asyncio
|
|
9
8
|
import io
|
|
10
9
|
import json
|
|
11
10
|
import os
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
12
13
|
from datetime import datetime
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Any, Callable, Dict, Optional
|
|
15
|
-
|
|
16
|
-
import
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
17
19
|
from ksadk.api import AgentEngineAPIError, AgentEngineClient
|
|
18
20
|
from ksadk.cli.agent_ref import merge_agent_inputs, resolve_agent_ref, resolve_openclaw_ref
|
|
19
21
|
from ksadk.cli.cmd_files import (
|
|
@@ -24,20 +26,28 @@ from ksadk.cli.cmd_files import (
|
|
|
24
26
|
_normalize_workspace_dir,
|
|
25
27
|
_push_workspace_files,
|
|
26
28
|
)
|
|
27
|
-
from ksadk.cli.resource_common import
|
|
29
|
+
from ksadk.cli.resource_common import (
|
|
30
|
+
CONTEXT_SETTINGS,
|
|
31
|
+
CompatibilityAliasCommand,
|
|
32
|
+
print_compatibility_hint,
|
|
33
|
+
)
|
|
28
34
|
from ksadk.hermes_terminal import run_hermes_terminal_session
|
|
29
35
|
from ksadk.terminal_client import run_terminal_session
|
|
30
36
|
from ksadk_runtime_common.workspace_files.constants import DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
31
37
|
|
|
38
|
+
console: Any = None
|
|
39
|
+
Markdown: Any = None
|
|
40
|
+
Live: Any = None
|
|
32
41
|
try:
|
|
33
|
-
from rich.console import Console
|
|
34
|
-
from rich.
|
|
35
|
-
from rich.
|
|
36
|
-
|
|
42
|
+
from rich.console import Console as RichConsole
|
|
43
|
+
from rich.live import Live as RichLive
|
|
44
|
+
from rich.markdown import Markdown as RichMarkdown
|
|
45
|
+
|
|
46
|
+
console = RichConsole()
|
|
47
|
+
Markdown = RichMarkdown
|
|
48
|
+
Live = RichLive
|
|
37
49
|
except ImportError:
|
|
38
|
-
|
|
39
|
-
Markdown = None
|
|
40
|
-
Live = None
|
|
50
|
+
pass
|
|
41
51
|
|
|
42
52
|
|
|
43
53
|
@click.command(
|
|
@@ -84,6 +94,20 @@ except ImportError:
|
|
|
84
94
|
)
|
|
85
95
|
@click.option("--model", help="指定模型名称")
|
|
86
96
|
@click.option("--show-thinking", is_flag=True, help="显示模型思考过程")
|
|
97
|
+
@click.option(
|
|
98
|
+
"--api-format",
|
|
99
|
+
"api_format",
|
|
100
|
+
type=click.Choice(["auto", "responses", "chat_completions"], case_sensitive=False),
|
|
101
|
+
default="auto",
|
|
102
|
+
show_default=True,
|
|
103
|
+
help="HTTP 协议: auto(优先 responses,不通回退 chat), responses, chat_completions",
|
|
104
|
+
)
|
|
105
|
+
@click.option(
|
|
106
|
+
"--no-alt-screen",
|
|
107
|
+
"no_alt_screen",
|
|
108
|
+
is_flag=True,
|
|
109
|
+
help="兼容参数;TUI 已默认使用 inline viewport 并保留终端 scrollback",
|
|
110
|
+
)
|
|
87
111
|
def invoke(
|
|
88
112
|
agent_ref: str,
|
|
89
113
|
agent_option: str,
|
|
@@ -101,6 +125,8 @@ def invoke(
|
|
|
101
125
|
verbose_workspace_sync: bool,
|
|
102
126
|
model: str,
|
|
103
127
|
show_thinking: bool,
|
|
128
|
+
api_format: str,
|
|
129
|
+
no_alt_screen: bool,
|
|
104
130
|
):
|
|
105
131
|
"""与 Agent 进行交互 (本地或远程)。"""
|
|
106
132
|
run_invoke_command(
|
|
@@ -121,6 +147,8 @@ def invoke(
|
|
|
121
147
|
model=model,
|
|
122
148
|
show_thinking=show_thinking,
|
|
123
149
|
compatibility_alias=True,
|
|
150
|
+
api_format=None if (api_format or "auto").lower() == "auto" else api_format,
|
|
151
|
+
no_alt_screen=no_alt_screen,
|
|
124
152
|
)
|
|
125
153
|
|
|
126
154
|
|
|
@@ -129,9 +157,9 @@ def _resolve_remote_workspace_seed_path(
|
|
|
129
157
|
remote_workspace_path: str | None,
|
|
130
158
|
) -> str:
|
|
131
159
|
if remote_workspace_path:
|
|
132
|
-
return _normalize_workspace_dir(remote_workspace_path)
|
|
160
|
+
return str(_normalize_workspace_dir(remote_workspace_path))
|
|
133
161
|
default_name = local_workspace.resolve().name or local_workspace.name or "workspace"
|
|
134
|
-
return _normalize_workspace_dir(default_name)
|
|
162
|
+
return str(_normalize_workspace_dir(default_name))
|
|
135
163
|
|
|
136
164
|
|
|
137
165
|
def _summarize_local_workspace_dir(
|
|
@@ -182,10 +210,12 @@ def _describe_workspace_sync_phase(event: dict[str, Any] | None) -> str:
|
|
|
182
210
|
return "同步 workspace"
|
|
183
211
|
|
|
184
212
|
|
|
185
|
-
def _render_workspace_sync_progress_bar(
|
|
213
|
+
def _render_workspace_sync_progress_bar(
|
|
214
|
+
current: object | None, total: object | None, *, width: int = 20
|
|
215
|
+
) -> str:
|
|
186
216
|
try:
|
|
187
|
-
current_value = int(current or 0)
|
|
188
|
-
total_value = max(int(total or 0), 1)
|
|
217
|
+
current_value = int(str(current or 0))
|
|
218
|
+
total_value = max(int(str(total or 0)), 1)
|
|
189
219
|
except (TypeError, ValueError):
|
|
190
220
|
current_value = 0
|
|
191
221
|
total_value = 1
|
|
@@ -196,8 +226,8 @@ def _render_workspace_sync_progress_bar(current: object | None, total: object |
|
|
|
196
226
|
|
|
197
227
|
def _format_workspace_sync_percent(current: object | None, total: object | None) -> str:
|
|
198
228
|
try:
|
|
199
|
-
current_value = float(current or 0)
|
|
200
|
-
total_value = max(float(total or 0), 1.0)
|
|
229
|
+
current_value = float(str(current or 0))
|
|
230
|
+
total_value = max(float(str(total or 0)), 1.0)
|
|
201
231
|
except (TypeError, ValueError):
|
|
202
232
|
current_value = 0.0
|
|
203
233
|
total_value = 1.0
|
|
@@ -226,7 +256,8 @@ def _build_workspace_sync_progress_emitter(*, verbose: bool) -> Callable[[dict[s
|
|
|
226
256
|
click.secho(
|
|
227
257
|
"📂 准备同步 workspace: "
|
|
228
258
|
f"{event.get('total_files', 0)} 个文件,"
|
|
229
|
-
f"{_format_size(event.get('total_bytes', 0))} ->
|
|
259
|
+
f"{_format_size(event.get('total_bytes', 0))} -> "
|
|
260
|
+
f"workspace:/{event.get('remote_path', '.')}",
|
|
230
261
|
fg="blue",
|
|
231
262
|
)
|
|
232
263
|
ignored_artifacts = list(event.get("ignored_artifacts") or [])
|
|
@@ -306,7 +337,7 @@ def _extract_workspace_upload_limit(bootstrap: dict[str, Any] | None) -> int | N
|
|
|
306
337
|
return None
|
|
307
338
|
raw_limit = workspace.get("max_upload_bytes") or workspace.get("MaxUploadBytes")
|
|
308
339
|
try:
|
|
309
|
-
limit = int(raw_limit)
|
|
340
|
+
limit = int(str(raw_limit))
|
|
310
341
|
except (TypeError, ValueError):
|
|
311
342
|
return None
|
|
312
343
|
return limit if limit > 0 else None
|
|
@@ -318,7 +349,7 @@ async def _lookup_workspace_upload_limit(
|
|
|
318
349
|
region: str,
|
|
319
350
|
) -> int:
|
|
320
351
|
if not agent_ref:
|
|
321
|
-
return DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
352
|
+
return int(DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES)
|
|
322
353
|
|
|
323
354
|
try:
|
|
324
355
|
async with AgentEngineClient(region=region) as client:
|
|
@@ -327,9 +358,9 @@ async def _lookup_workspace_upload_limit(
|
|
|
327
358
|
else:
|
|
328
359
|
payload = await client.get_agent_ui_bootstrap(name=agent_ref)
|
|
329
360
|
except Exception:
|
|
330
|
-
return DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
361
|
+
return int(DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES)
|
|
331
362
|
|
|
332
|
-
return _extract_workspace_upload_limit(payload) or DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
363
|
+
return int(_extract_workspace_upload_limit(payload) or DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES)
|
|
333
364
|
|
|
334
365
|
|
|
335
366
|
async def _sync_local_workspace_for_hermes_invoke(
|
|
@@ -376,7 +407,8 @@ async def _sync_local_workspace_for_hermes_invoke(
|
|
|
376
407
|
if item["size_bytes"] > max_upload_bytes:
|
|
377
408
|
raise click.ClickException(
|
|
378
409
|
"本地目录中存在超过远端 workspace 上传上限的文件:"
|
|
379
|
-
f"{item['path']}({_format_size(item['size_bytes'])} >
|
|
410
|
+
f"{item['path']}({_format_size(item['size_bytes'])} > "
|
|
411
|
+
f"{_format_size(max_upload_bytes)})"
|
|
380
412
|
)
|
|
381
413
|
|
|
382
414
|
if summary["total_bytes"] > max_upload_bytes:
|
|
@@ -394,7 +426,7 @@ async def _sync_local_workspace_for_hermes_invoke(
|
|
|
394
426
|
progress_callback(event)
|
|
395
427
|
|
|
396
428
|
try:
|
|
397
|
-
|
|
429
|
+
result = await _push_workspace_files(
|
|
398
430
|
agent_ref=agent_ref,
|
|
399
431
|
local_dir=local_dir,
|
|
400
432
|
remote_path=remote_path,
|
|
@@ -406,11 +438,12 @@ async def _sync_local_workspace_for_hermes_invoke(
|
|
|
406
438
|
ignore_dev_artifacts=True,
|
|
407
439
|
ignore_git_artifacts=".git" in list(summary.get("ignored_artifacts") or []),
|
|
408
440
|
)
|
|
441
|
+
if not isinstance(result, dict):
|
|
442
|
+
raise click.ClickException("同步远端 workspace 返回了无效响应")
|
|
443
|
+
return result
|
|
409
444
|
except AgentEngineAPIError as exc:
|
|
410
445
|
phase = _describe_workspace_sync_phase(last_progress)
|
|
411
|
-
raise click.ClickException(
|
|
412
|
-
f"同步远端 workspace 失败({phase}):{exc.message}"
|
|
413
|
-
) from exc
|
|
446
|
+
raise click.ClickException(f"同步远端 workspace 失败({phase}):{exc.message}") from exc
|
|
414
447
|
|
|
415
448
|
|
|
416
449
|
def run_invoke_command(
|
|
@@ -432,6 +465,8 @@ def run_invoke_command(
|
|
|
432
465
|
show_thinking: bool,
|
|
433
466
|
openclaw_gateway_token: str | None = None,
|
|
434
467
|
compatibility_alias: bool = False,
|
|
468
|
+
api_format: str | None = None,
|
|
469
|
+
no_alt_screen: bool = False,
|
|
435
470
|
):
|
|
436
471
|
"""与 Agent 进行交互 (本地或远程)。"""
|
|
437
472
|
if compatibility_alias:
|
|
@@ -458,9 +493,8 @@ def run_invoke_command(
|
|
|
458
493
|
# 加载本地状态
|
|
459
494
|
state = _load_state()
|
|
460
495
|
latest_access: dict[str, Any] = {}
|
|
461
|
-
reuse_state_session = True
|
|
462
496
|
target_agent: str | None = None
|
|
463
|
-
|
|
497
|
+
|
|
464
498
|
normalized_transport = (transport or "auto").strip().lower() or "auto"
|
|
465
499
|
|
|
466
500
|
# 确定 Endpoint
|
|
@@ -493,25 +527,24 @@ def run_invoke_command(
|
|
|
493
527
|
state=state,
|
|
494
528
|
persist=_state_matches_target(state, target_agent),
|
|
495
529
|
)
|
|
496
|
-
reuse_state_session = not agent_input or _state_matches_target(state, target_agent)
|
|
497
530
|
|
|
498
531
|
# 优先使用 state 里的 endpoint (如果是对应的 agent)
|
|
499
532
|
if latest_access.get("endpoint"):
|
|
500
533
|
endpoint = latest_access["endpoint"]
|
|
501
534
|
elif not agent_input or _state_matches_target(state, target_agent):
|
|
502
535
|
endpoint = state.get("endpoint")
|
|
503
|
-
|
|
536
|
+
|
|
504
537
|
if not endpoint:
|
|
505
538
|
# 自动获取
|
|
506
539
|
endpoint = _get_endpoint(target_agent, region)
|
|
507
540
|
|
|
508
541
|
# API Key
|
|
509
542
|
api_key = api_key or latest_access.get("api_key") or state.get("api_key")
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
)
|
|
543
|
+
# 单次 --message 调用默认开新 session,避免复用 .agentengine.state 里长上下文
|
|
544
|
+
# 默认每次调用开新 session(单次 -m 与交互 TUI 都是),不复用 .agentengine.state
|
|
545
|
+
# 里的上次 session,避免长上下文累积导致首包变慢。TUI 内多轮续聊由
|
|
546
|
+
# InteractionLoop.session_id 维持;跨次想续聊上次会话用显式 --session 指定。
|
|
547
|
+
session_id = session or str(uuid.uuid4())[:8]
|
|
515
548
|
|
|
516
549
|
next_state = _load_state() or dict(state)
|
|
517
550
|
for key in ("agent_id", "name", "endpoint", "api_key", "framework"):
|
|
@@ -530,7 +563,7 @@ def run_invoke_command(
|
|
|
530
563
|
latest_access=latest_access,
|
|
531
564
|
)
|
|
532
565
|
|
|
533
|
-
click.secho(
|
|
566
|
+
click.secho("🤖 连接到 Agent", fg="blue", bold=True)
|
|
534
567
|
click.echo(f" Endpoint: {endpoint}")
|
|
535
568
|
if _is_openclaw_target(next_state, latest_access):
|
|
536
569
|
if runtime_api_key:
|
|
@@ -543,13 +576,13 @@ def run_invoke_command(
|
|
|
543
576
|
click.echo(f" Auth: Bearer {_mask_secret(api_key)}")
|
|
544
577
|
else:
|
|
545
578
|
click.secho(" ⚠️ 未发现 API Key,尝试匿名调用", fg="yellow")
|
|
546
|
-
|
|
579
|
+
|
|
547
580
|
if insecure:
|
|
548
581
|
click.secho(" ⚠️ SSL 证书验证已禁用", fg="yellow")
|
|
549
582
|
|
|
550
583
|
if message:
|
|
551
584
|
# 单次调用模式
|
|
552
|
-
|
|
585
|
+
api_format_resolved = asyncio.run(
|
|
553
586
|
_resolve_remote_api_format(
|
|
554
587
|
endpoint=endpoint,
|
|
555
588
|
api_key=api_key,
|
|
@@ -557,12 +590,23 @@ def run_invoke_command(
|
|
|
557
590
|
insecure=insecure,
|
|
558
591
|
state=next_state,
|
|
559
592
|
latest_access=latest_access,
|
|
593
|
+
api_format=api_format,
|
|
594
|
+
)
|
|
595
|
+
)
|
|
596
|
+
asyncio.run(
|
|
597
|
+
_invoke_once(
|
|
598
|
+
endpoint,
|
|
599
|
+
message,
|
|
600
|
+
runtime_api_key,
|
|
601
|
+
session_id,
|
|
602
|
+
True,
|
|
603
|
+
insecure,
|
|
604
|
+
model,
|
|
605
|
+
api_format_resolved,
|
|
560
606
|
)
|
|
561
607
|
)
|
|
562
|
-
asyncio.run(_invoke_once(endpoint, message, runtime_api_key, session_id, True, insecure, model, api_format))
|
|
563
608
|
else:
|
|
564
609
|
is_hermes_target = _is_hermes_target(next_state, latest_access)
|
|
565
|
-
is_openclaw_target = _is_openclaw_target(next_state, latest_access)
|
|
566
610
|
if normalized_transport == "chat" and is_hermes_target:
|
|
567
611
|
click.secho("❌ Hermes 不再支持 ksadk 通用 chat TUI。", fg="red")
|
|
568
612
|
click.echo(" 浏览器聊天页请改用: agentengine hermes open --chat")
|
|
@@ -634,7 +678,7 @@ def run_invoke_command(
|
|
|
634
678
|
insecure=insecure,
|
|
635
679
|
)
|
|
636
680
|
else:
|
|
637
|
-
|
|
681
|
+
api_format_resolved = asyncio.run(
|
|
638
682
|
_resolve_remote_api_format(
|
|
639
683
|
endpoint=endpoint,
|
|
640
684
|
api_key=api_key,
|
|
@@ -642,8 +686,16 @@ def run_invoke_command(
|
|
|
642
686
|
insecure=insecure,
|
|
643
687
|
state=next_state,
|
|
644
688
|
latest_access=latest_access,
|
|
689
|
+
api_format=api_format,
|
|
645
690
|
)
|
|
646
691
|
)
|
|
692
|
+
tui_agent_id = (
|
|
693
|
+
latest_access.get("agent_id")
|
|
694
|
+
or next_state.get("agent_id")
|
|
695
|
+
or target_agent
|
|
696
|
+
or latest_access.get("name")
|
|
697
|
+
or next_state.get("name")
|
|
698
|
+
)
|
|
647
699
|
_invoke_tui(
|
|
648
700
|
endpoint,
|
|
649
701
|
runtime_api_key,
|
|
@@ -651,28 +703,34 @@ def run_invoke_command(
|
|
|
651
703
|
insecure,
|
|
652
704
|
model,
|
|
653
705
|
show_thinking,
|
|
654
|
-
api_format=
|
|
706
|
+
api_format=api_format_resolved,
|
|
655
707
|
responses_session_header=(
|
|
656
|
-
"x-openclaw-session-key"
|
|
708
|
+
"x-openclaw-session-key"
|
|
709
|
+
if _is_openclaw_target(next_state, latest_access)
|
|
710
|
+
else None
|
|
657
711
|
),
|
|
712
|
+
no_alt_screen=no_alt_screen,
|
|
713
|
+
region=region,
|
|
714
|
+
agent_id=tui_agent_id,
|
|
658
715
|
)
|
|
659
716
|
|
|
660
717
|
|
|
661
|
-
|
|
662
|
-
|
|
663
718
|
def _invoke_tui(
|
|
664
719
|
endpoint: str,
|
|
665
|
-
api_key: str = None,
|
|
666
|
-
session_id: str = None,
|
|
720
|
+
api_key: Optional[str] = None,
|
|
721
|
+
session_id: Optional[str] = None,
|
|
667
722
|
insecure: bool = False,
|
|
668
|
-
model: str = None,
|
|
723
|
+
model: Optional[str] = None,
|
|
669
724
|
show_thinking: bool = False,
|
|
670
725
|
api_format: str = "chat_completions",
|
|
671
726
|
responses_session_header: str | None = None,
|
|
727
|
+
no_alt_screen: bool = False,
|
|
728
|
+
region: str = "cn-beijing-6",
|
|
729
|
+
agent_id: str | None = None,
|
|
672
730
|
):
|
|
673
731
|
"""使用 TUI 模式调用"""
|
|
674
732
|
from ksadk.runners.remote_runner import RemoteRunner
|
|
675
|
-
from ksadk.tui import
|
|
733
|
+
from ksadk.tui.loop import run_tui
|
|
676
734
|
|
|
677
735
|
runner = RemoteRunner(
|
|
678
736
|
endpoint=endpoint,
|
|
@@ -683,22 +741,102 @@ def _invoke_tui(
|
|
|
683
741
|
api_format=api_format,
|
|
684
742
|
responses_session_header=responses_session_header,
|
|
685
743
|
)
|
|
744
|
+
# 优先用 ListAgentModels(控制面 action,server 侧封装 runtime catalog,
|
|
745
|
+
# 避开 CLI 直连 runtime /v1/models 对 openclaw/hermes 的鉴权坑)拿模型列表 +
|
|
746
|
+
# 当前模型 metadata。失败 fallback 到 _fetch_tui_model_metadata(直连 /v1/models)。
|
|
747
|
+
model_list = asyncio.run(_fetch_tui_model_list(region=region, agent_id=agent_id))
|
|
748
|
+
if model_list:
|
|
749
|
+
runner.available_models = model_list.get("models") or []
|
|
750
|
+
current_id = model_list.get("current")
|
|
751
|
+
matched = None
|
|
752
|
+
if current_id:
|
|
753
|
+
matched = next((m for m in runner.available_models if m.get("id") == current_id), None)
|
|
754
|
+
if matched is None and runner.available_models:
|
|
755
|
+
matched = runner.available_models[0]
|
|
756
|
+
if matched:
|
|
757
|
+
setattr(runner, "model_metadata", matched)
|
|
758
|
+
# 用户未指定 --model 时,用 ListAgentModels 的 current 作为 runner.model,
|
|
759
|
+
# 让 TUI 启动即显示真实模型名(而非 unknown)。
|
|
760
|
+
if not runner.model and matched.get("id"):
|
|
761
|
+
runner.model = str(matched["id"])
|
|
762
|
+
else:
|
|
763
|
+
# fallback:直连 runtime /v1/models(旧路径,托管 runtime 常失败但不阻断)
|
|
764
|
+
model_metadata = asyncio.run(
|
|
765
|
+
_fetch_tui_model_metadata(endpoint=endpoint, api_key=api_key, model=model)
|
|
766
|
+
)
|
|
767
|
+
if model_metadata:
|
|
768
|
+
setattr(runner, "model_metadata", model_metadata)
|
|
686
769
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
770
|
+
run_tui(runner, show_thinking=show_thinking, project_dir=".", no_alt_screen=no_alt_screen)
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
async def _fetch_tui_model_list(
|
|
774
|
+
*,
|
|
775
|
+
region: str,
|
|
776
|
+
agent_id: str | None,
|
|
777
|
+
) -> dict[str, Any] | None:
|
|
778
|
+
"""通过控制面 ListAgentModels action 拿 Agent 可选模型列表(对标 hosted UI)。
|
|
779
|
+
|
|
780
|
+
需 KSYUN access/secret key(env),与 _fetch_remote_access 同套鉴权。
|
|
781
|
+
失败返回 None(不阻断 TUI,由 _fetch_tui_model_metadata fallback)。
|
|
782
|
+
"""
|
|
783
|
+
if not agent_id:
|
|
784
|
+
return None
|
|
785
|
+
try:
|
|
786
|
+
from ksadk.api.client import AgentEngineClient
|
|
787
|
+
|
|
788
|
+
async with AgentEngineClient(region=region) as client:
|
|
789
|
+
payload = await client.list_agent_models(agent_id=agent_id)
|
|
790
|
+
return payload if isinstance(payload, dict) else None
|
|
791
|
+
except Exception:
|
|
792
|
+
return None
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
async def _fetch_tui_model_metadata(
|
|
796
|
+
*,
|
|
797
|
+
endpoint: str,
|
|
798
|
+
api_key: str | None,
|
|
799
|
+
model: str | None,
|
|
800
|
+
) -> dict[str, Any] | None:
|
|
801
|
+
try:
|
|
802
|
+
from ksadk.cli.model_catalog import (
|
|
803
|
+
fetch_provider_model_catalog,
|
|
804
|
+
find_model_in_catalog,
|
|
805
|
+
)
|
|
806
|
+
from ksadk.conversations.model_context import normalize_model_metadata
|
|
807
|
+
|
|
808
|
+
catalog = await fetch_provider_model_catalog(
|
|
809
|
+
api_base=endpoint,
|
|
810
|
+
api_key=api_key,
|
|
811
|
+
timeout=3.0,
|
|
812
|
+
)
|
|
813
|
+
except Exception:
|
|
814
|
+
return None
|
|
815
|
+
|
|
816
|
+
if not catalog:
|
|
817
|
+
return None
|
|
818
|
+
raw_models = [item.get("_provider_raw_model") or item for item in catalog]
|
|
819
|
+
matched = find_model_in_catalog(raw_models, model)
|
|
820
|
+
if matched is not None:
|
|
821
|
+
metadata = normalize_model_metadata(matched)
|
|
822
|
+
return metadata if isinstance(metadata, dict) else None
|
|
823
|
+
if model:
|
|
824
|
+
return None
|
|
825
|
+
if len(catalog) == 1:
|
|
826
|
+
item = dict(catalog[0])
|
|
827
|
+
item.pop("_provider_raw_model", None)
|
|
828
|
+
return item
|
|
829
|
+
return None
|
|
693
830
|
|
|
694
831
|
|
|
695
832
|
def _load_state() -> dict:
|
|
696
833
|
"""从 .agentengine.state 加载状态"""
|
|
697
834
|
import yaml
|
|
835
|
+
|
|
698
836
|
state_file = Path(".") / ".agentengine.state"
|
|
699
837
|
if state_file.exists():
|
|
700
838
|
try:
|
|
701
|
-
with open(state_file,
|
|
839
|
+
with open(state_file, "r", encoding="utf-8") as f:
|
|
702
840
|
return yaml.safe_load(f) or {}
|
|
703
841
|
except Exception:
|
|
704
842
|
pass
|
|
@@ -726,15 +864,13 @@ def _extract_remote_access(detail: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
726
864
|
if not isinstance(detail, dict):
|
|
727
865
|
return {}
|
|
728
866
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
or detail.get("framework")
|
|
737
|
-
)
|
|
867
|
+
basic_value = detail.get("basic")
|
|
868
|
+
quick_value = detail.get("quick_access")
|
|
869
|
+
deployment_value = detail.get("deployment")
|
|
870
|
+
basic: Dict[str, Any] = basic_value if isinstance(basic_value, dict) else {}
|
|
871
|
+
quick: Dict[str, Any] = quick_value if isinstance(quick_value, dict) else {}
|
|
872
|
+
deployment: Dict[str, Any] = deployment_value if isinstance(deployment_value, dict) else {}
|
|
873
|
+
framework = deployment.get("framework") or basic.get("framework") or detail.get("framework")
|
|
738
874
|
|
|
739
875
|
return {
|
|
740
876
|
"agent_id": basic.get("agent_id") or detail.get("agent_id"),
|
|
@@ -752,9 +888,13 @@ async def _fetch_remote_access(target_agent: str, region: str) -> Dict[str, Any]
|
|
|
752
888
|
|
|
753
889
|
async with AgentEngineClient(region=region) as client:
|
|
754
890
|
try:
|
|
755
|
-
return _extract_remote_access(
|
|
891
|
+
return _extract_remote_access(
|
|
892
|
+
await client.get_agent(agent_id=target_agent, include_api_key=True)
|
|
893
|
+
)
|
|
756
894
|
except Exception:
|
|
757
|
-
return _extract_remote_access(
|
|
895
|
+
return _extract_remote_access(
|
|
896
|
+
await client.get_agent(name=target_agent, include_api_key=True)
|
|
897
|
+
)
|
|
758
898
|
|
|
759
899
|
|
|
760
900
|
def _refresh_remote_access(
|
|
@@ -783,22 +923,20 @@ def _refresh_remote_access(
|
|
|
783
923
|
|
|
784
924
|
|
|
785
925
|
def _is_hermes_target(state: dict, latest_access: dict) -> bool:
|
|
786
|
-
framework =
|
|
787
|
-
latest_access.get("framework")
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
).strip().lower()
|
|
926
|
+
framework = (
|
|
927
|
+
str(latest_access.get("framework") or state.get("framework") or state.get("type") or "")
|
|
928
|
+
.strip()
|
|
929
|
+
.lower()
|
|
930
|
+
)
|
|
792
931
|
return framework == "hermes"
|
|
793
932
|
|
|
794
933
|
|
|
795
934
|
def _is_openclaw_target(state: dict, latest_access: dict) -> bool:
|
|
796
|
-
framework =
|
|
797
|
-
latest_access.get("framework")
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
).strip().lower()
|
|
935
|
+
framework = (
|
|
936
|
+
str(latest_access.get("framework") or state.get("framework") or state.get("type") or "")
|
|
937
|
+
.strip()
|
|
938
|
+
.lower()
|
|
939
|
+
)
|
|
802
940
|
return framework == "openclaw"
|
|
803
941
|
|
|
804
942
|
|
|
@@ -812,13 +950,17 @@ def _mask_secret(secret: str | None) -> str:
|
|
|
812
950
|
|
|
813
951
|
|
|
814
952
|
def _openclaw_auth_mode(state: dict, latest_access: dict) -> str:
|
|
815
|
-
return
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
953
|
+
return (
|
|
954
|
+
str(
|
|
955
|
+
latest_access.get("openclaw_auth_mode")
|
|
956
|
+
or latest_access.get("gateway_auth_mode")
|
|
957
|
+
or state.get("openclaw_auth_mode")
|
|
958
|
+
or state.get("gateway_auth_mode")
|
|
959
|
+
or ""
|
|
960
|
+
)
|
|
961
|
+
.strip()
|
|
962
|
+
.lower()
|
|
963
|
+
)
|
|
822
964
|
|
|
823
965
|
|
|
824
966
|
def _select_runtime_api_key(
|
|
@@ -846,26 +988,28 @@ def _select_runtime_api_key(
|
|
|
846
988
|
auth_mode = _openclaw_auth_mode(state, latest_access)
|
|
847
989
|
if auth_mode in {"token", "password"}:
|
|
848
990
|
raise click.ClickException(
|
|
849
|
-
"当前 OpenClaw Gateway 为 token/password 模式,
|
|
991
|
+
"当前 OpenClaw Gateway 为 token/password 模式,"
|
|
992
|
+
"agentengine invoke 需要 OpenClaw Gateway token。\n"
|
|
850
993
|
"请使用: agentengine invoke --gateway-token <token>\n"
|
|
851
994
|
"或设置: OPENCLAW_GATEWAY_TOKEN=<token> agentengine invoke\n"
|
|
852
|
-
"如果部署时传过 OPENCLAW_GATEWAY_TOKEN,请重新运行部署让本地
|
|
853
|
-
"
|
|
995
|
+
"如果部署时传过 OPENCLAW_GATEWAY_TOKEN,请重新运行部署让本地 "
|
|
996
|
+
".agentengine.state 记录该 token。\n"
|
|
997
|
+
"注意:这里不是 AgentEngine API Key(ak-*),而是 "
|
|
998
|
+
"OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD。"
|
|
854
999
|
)
|
|
855
1000
|
|
|
856
1001
|
return api_key
|
|
857
1002
|
|
|
858
1003
|
|
|
859
1004
|
def _select_remote_api_format(state: dict, latest_access: dict) -> str:
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
return "chat_completions"
|
|
1005
|
+
"""auto 模式下所有框架首选 responses(probe 不通再回退 chat_completions)。"""
|
|
1006
|
+
return "responses"
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
# probe 结果进程内短缓存,key=endpoint,value=(过期时间戳, 是否暴露 responses)。
|
|
1010
|
+
# 避免每次 invoke 都多打一跳 GET /v1/responses。
|
|
1011
|
+
_RESPONSES_ROUTE_CACHE: dict[tuple, tuple[float, bool]] = {}
|
|
1012
|
+
_RESPONSES_ROUTE_CACHE_TTL = 30.0
|
|
869
1013
|
|
|
870
1014
|
|
|
871
1015
|
async def _resolve_remote_api_format(
|
|
@@ -876,23 +1020,65 @@ async def _resolve_remote_api_format(
|
|
|
876
1020
|
insecure: bool,
|
|
877
1021
|
state: dict,
|
|
878
1022
|
latest_access: dict,
|
|
1023
|
+
api_format: str | None = None,
|
|
879
1024
|
) -> str:
|
|
880
|
-
api_format
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1025
|
+
# 显式指定 api_format(非 auto)时直接用,不 probe。
|
|
1026
|
+
explicit = str(api_format or "").strip().lower()
|
|
1027
|
+
if explicit in {"responses", "chat_completions"}:
|
|
1028
|
+
return explicit
|
|
1029
|
+
|
|
1030
|
+
# auto:按框架决定首选格式,probe /v1/responses 不通回退 chat_completions。
|
|
1031
|
+
preferred = _select_remote_api_format(state, latest_access)
|
|
1032
|
+
if preferred != "responses":
|
|
1033
|
+
return preferred
|
|
884
1034
|
probe_api_key = runtime_api_key if runtime_api_key is not None else api_key
|
|
885
|
-
|
|
886
|
-
|
|
1035
|
+
# openclaw/hermes 的 responses 用独立 gateway 鉴权头,GET 401 不代表 POST 401,
|
|
1036
|
+
# 保留 401=可用的既有行为;普通框架 401=鉴权不通,回退 chat。
|
|
1037
|
+
treat_401 = _is_openclaw_target(state, latest_access) or _is_hermes_target(state, latest_access)
|
|
1038
|
+
if await _probe_responses_route_cached(
|
|
1039
|
+
endpoint=endpoint,
|
|
1040
|
+
api_key=probe_api_key,
|
|
1041
|
+
insecure=insecure,
|
|
1042
|
+
treat_401_as_available=treat_401,
|
|
1043
|
+
):
|
|
1044
|
+
return "responses"
|
|
1045
|
+
return "chat_completions"
|
|
1046
|
+
|
|
887
1047
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1048
|
+
# probe 结果进程内短缓存。cache key 含 treat_401:同一 endpoint 在 openclaw/hermes
|
|
1049
|
+
# 与普通框架下对 401 的解读不同,分开缓存避免串味。
|
|
1050
|
+
async def _probe_responses_route_cached(
|
|
1051
|
+
*,
|
|
1052
|
+
endpoint: str,
|
|
1053
|
+
api_key: str | None,
|
|
1054
|
+
insecure: bool,
|
|
1055
|
+
treat_401_as_available: bool = False,
|
|
1056
|
+
) -> bool:
|
|
1057
|
+
import time as _time
|
|
1058
|
+
|
|
1059
|
+
cache_key = (endpoint.rstrip("/"), treat_401_as_available)
|
|
1060
|
+
now = _time.time()
|
|
1061
|
+
cached = _RESPONSES_ROUTE_CACHE.get(cache_key)
|
|
1062
|
+
if cached and cached[0] > now:
|
|
1063
|
+
return cached[1]
|
|
1064
|
+
|
|
1065
|
+
available = await _probe_responses_route(
|
|
1066
|
+
endpoint=endpoint,
|
|
1067
|
+
api_key=api_key,
|
|
1068
|
+
insecure=insecure,
|
|
1069
|
+
treat_401_as_available=treat_401_as_available,
|
|
892
1070
|
)
|
|
1071
|
+
_RESPONSES_ROUTE_CACHE[cache_key] = (now + _RESPONSES_ROUTE_CACHE_TTL, available)
|
|
1072
|
+
return available
|
|
893
1073
|
|
|
894
1074
|
|
|
895
|
-
async def
|
|
1075
|
+
async def _probe_responses_route(
|
|
1076
|
+
*,
|
|
1077
|
+
endpoint: str,
|
|
1078
|
+
api_key: str | None,
|
|
1079
|
+
insecure: bool,
|
|
1080
|
+
treat_401_as_available: bool = False,
|
|
1081
|
+
) -> bool:
|
|
896
1082
|
try:
|
|
897
1083
|
import httpx
|
|
898
1084
|
except ImportError:
|
|
@@ -912,12 +1098,19 @@ async def _probe_openclaw_responses_route(*, endpoint: str, api_key: str | None,
|
|
|
912
1098
|
except Exception:
|
|
913
1099
|
return False
|
|
914
1100
|
|
|
915
|
-
# POST-only API routes usually answer GET with 405/422/400
|
|
916
|
-
#
|
|
917
|
-
|
|
1101
|
+
# POST-only API routes usually answer GET with 405/422/400 = 路由存在且鉴权通过。
|
|
1102
|
+
# 401 = 未鉴权:带 key 仍 401 说明该 key 不被 responses 路由接受,走 responses 也会 401,
|
|
1103
|
+
# 应回退 chat。openclaw/hermes 的 responses 用独立 gateway 鉴权头,GET 401 不代表 POST 401,
|
|
1104
|
+
# 由 treat_401_as_available 保留其既有行为。404/HTML 200 = 路由不存在。
|
|
1105
|
+
available = response.status_code in {400, 405, 422}
|
|
1106
|
+
if treat_401_as_available and response.status_code == 401:
|
|
1107
|
+
available = True
|
|
1108
|
+
return available
|
|
918
1109
|
|
|
919
1110
|
|
|
920
|
-
def _should_use_hermes_native_tui(
|
|
1111
|
+
def _should_use_hermes_native_tui(
|
|
1112
|
+
*, transport: str, local: bool, state: dict, latest_access: dict
|
|
1113
|
+
) -> bool:
|
|
921
1114
|
if transport == "chat":
|
|
922
1115
|
return False
|
|
923
1116
|
if transport == "native":
|
|
@@ -927,7 +1120,9 @@ def _should_use_hermes_native_tui(*, transport: str, local: bool, state: dict, l
|
|
|
927
1120
|
return _is_hermes_target(state, latest_access)
|
|
928
1121
|
|
|
929
1122
|
|
|
930
|
-
def _should_use_openclaw_native_tui(
|
|
1123
|
+
def _should_use_openclaw_native_tui(
|
|
1124
|
+
*, transport: str, local: bool, state: dict, latest_access: dict
|
|
1125
|
+
) -> bool:
|
|
931
1126
|
if transport == "chat":
|
|
932
1127
|
return False
|
|
933
1128
|
if transport == "native":
|
|
@@ -939,14 +1134,16 @@ def _should_use_openclaw_native_tui(*, transport: str, local: bool, state: dict,
|
|
|
939
1134
|
|
|
940
1135
|
def _invoke_hermes_terminal_tui(
|
|
941
1136
|
endpoint: str,
|
|
942
|
-
api_key: str = None,
|
|
943
|
-
session_id: str = None,
|
|
1137
|
+
api_key: Optional[str] = None,
|
|
1138
|
+
session_id: Optional[str] = None,
|
|
944
1139
|
insecure: bool = False,
|
|
945
1140
|
cwd: str | None = None,
|
|
946
1141
|
):
|
|
947
1142
|
click.secho("🖥️ Hermes Native Remote TUI", fg="blue", bold=True)
|
|
948
1143
|
click.echo(" 退出: Ctrl-D 或 Ctrl-C")
|
|
949
|
-
click.echo(
|
|
1144
|
+
click.echo(
|
|
1145
|
+
" 新 Pod 首次启动会加载 Hermes tools/skills,可能需要几十秒到 1 分钟;后续进入会更快。"
|
|
1146
|
+
)
|
|
950
1147
|
try:
|
|
951
1148
|
_warmup_hermes_terminal(
|
|
952
1149
|
endpoint=endpoint,
|
|
@@ -1005,8 +1202,8 @@ def _warmup_hermes_terminal(
|
|
|
1005
1202
|
|
|
1006
1203
|
def _invoke_openclaw_terminal_tui(
|
|
1007
1204
|
endpoint: str,
|
|
1008
|
-
api_key: str = None,
|
|
1009
|
-
session_id: str = None,
|
|
1205
|
+
api_key: Optional[str] = None,
|
|
1206
|
+
session_id: Optional[str] = None,
|
|
1010
1207
|
insecure: bool = False,
|
|
1011
1208
|
):
|
|
1012
1209
|
click.secho("🖥️ OpenClaw Native Remote TUI", fg="blue", bold=True)
|
|
@@ -1039,15 +1236,16 @@ def _get_api_key() -> Optional[str]:
|
|
|
1039
1236
|
|
|
1040
1237
|
def _get_endpoint(agent_ref: str, region: str) -> str:
|
|
1041
1238
|
"""获取 Agent Endpoint(先按 ID,再按名称)"""
|
|
1042
|
-
from ksadk.api import AgentEngineClient
|
|
1043
1239
|
import asyncio
|
|
1044
1240
|
|
|
1045
|
-
|
|
1241
|
+
from ksadk.api import AgentEngineClient
|
|
1242
|
+
|
|
1243
|
+
async def _get() -> str:
|
|
1046
1244
|
async with AgentEngineClient(region=region) as client:
|
|
1047
1245
|
# 1) 优先按 ID 查询
|
|
1048
1246
|
try:
|
|
1049
1247
|
res = await client.get_agent(agent_id=agent_ref)
|
|
1050
|
-
endpoint = _extract_remote_access(res).get("endpoint"
|
|
1248
|
+
endpoint = str(_extract_remote_access(res).get("endpoint") or "").strip()
|
|
1051
1249
|
if endpoint:
|
|
1052
1250
|
return endpoint
|
|
1053
1251
|
except Exception:
|
|
@@ -1055,13 +1253,14 @@ def _get_endpoint(agent_ref: str, region: str) -> str:
|
|
|
1055
1253
|
|
|
1056
1254
|
# 2) 回退按名称查询
|
|
1057
1255
|
res = await client.get_agent(name=agent_ref)
|
|
1058
|
-
endpoint = _extract_remote_access(res).get("endpoint"
|
|
1256
|
+
endpoint = str(_extract_remote_access(res).get("endpoint") or "").strip()
|
|
1059
1257
|
if endpoint:
|
|
1060
1258
|
return endpoint
|
|
1061
1259
|
|
|
1062
1260
|
# endpoint 为空时,尽量提取真实 ID 供默认域名拼接
|
|
1063
|
-
|
|
1064
|
-
|
|
1261
|
+
basic_value = res.get("basic")
|
|
1262
|
+
basic = basic_value if isinstance(basic_value, dict) else {}
|
|
1263
|
+
return str(basic.get("agent_id") or res.get("agent_id") or "")
|
|
1065
1264
|
|
|
1066
1265
|
try:
|
|
1067
1266
|
resolved = asyncio.run(_get())
|
|
@@ -1081,16 +1280,16 @@ def _get_endpoint(agent_ref: str, region: str) -> str:
|
|
|
1081
1280
|
async def _invoke_once(
|
|
1082
1281
|
endpoint: str,
|
|
1083
1282
|
message: str,
|
|
1084
|
-
api_key: str = None,
|
|
1085
|
-
session_id: str = None,
|
|
1283
|
+
api_key: Optional[str] = None,
|
|
1284
|
+
session_id: Optional[str] = None,
|
|
1086
1285
|
stream: bool = True,
|
|
1087
1286
|
insecure: bool = False,
|
|
1088
|
-
model: str = None,
|
|
1287
|
+
model: Optional[str] = None,
|
|
1089
1288
|
api_format: str = "chat_completions",
|
|
1090
1289
|
):
|
|
1091
1290
|
"""单次调用"""
|
|
1092
1291
|
click.echo(f"\n👤 你: {message}")
|
|
1093
|
-
click.echo(
|
|
1292
|
+
click.echo("🤖 Agent: ", nl=False)
|
|
1094
1293
|
|
|
1095
1294
|
try:
|
|
1096
1295
|
if stream:
|
|
@@ -1098,12 +1297,19 @@ async def _invoke_once(
|
|
|
1098
1297
|
if Live and Markdown:
|
|
1099
1298
|
# 降低刷新率减少闪烁,vertical_overflow="visible"防止回滚丢失
|
|
1100
1299
|
# 手动控制刷新以减少闪烁
|
|
1101
|
-
with Live(
|
|
1102
|
-
|
|
1300
|
+
with Live(
|
|
1301
|
+
Markdown("", justify="left"),
|
|
1302
|
+
console=console,
|
|
1303
|
+
auto_refresh=False,
|
|
1304
|
+
vertical_overflow="visible",
|
|
1305
|
+
) as live:
|
|
1306
|
+
last_refresh_time = 0.0
|
|
1103
1307
|
full_reasoning = ""
|
|
1104
|
-
async for chunk in _stream_chat(
|
|
1308
|
+
async for chunk in _stream_chat(
|
|
1309
|
+
endpoint, message, api_key, session_id, True, insecure, model, api_format
|
|
1310
|
+
):
|
|
1105
1311
|
content, reasoning = _extract_content(chunk)
|
|
1106
|
-
|
|
1312
|
+
|
|
1107
1313
|
updated = False
|
|
1108
1314
|
if reasoning:
|
|
1109
1315
|
full_reasoning += reasoning
|
|
@@ -1111,25 +1317,27 @@ async def _invoke_once(
|
|
|
1111
1317
|
if content:
|
|
1112
1318
|
full_response += content
|
|
1113
1319
|
updated = True
|
|
1114
|
-
|
|
1320
|
+
|
|
1115
1321
|
if updated:
|
|
1116
1322
|
# 构造显示文本
|
|
1117
1323
|
display_text = ""
|
|
1118
1324
|
if full_reasoning:
|
|
1119
|
-
formatted_reasoning = full_reasoning.replace(
|
|
1325
|
+
formatted_reasoning = full_reasoning.replace("\n", "\n> ")
|
|
1120
1326
|
display_text += f"> 🧠 **Thinking:**\n> {formatted_reasoning}\n\n"
|
|
1121
1327
|
display_text += full_response
|
|
1122
|
-
|
|
1328
|
+
|
|
1123
1329
|
live.update(Markdown(display_text, justify="left"))
|
|
1124
|
-
|
|
1330
|
+
|
|
1125
1331
|
# 基于时间限流刷新 (每0.2秒一次 = 5 FPS)
|
|
1126
1332
|
now = time.time()
|
|
1127
1333
|
if now - last_refresh_time > 0.2:
|
|
1128
1334
|
live.refresh()
|
|
1129
1335
|
last_refresh_time = now
|
|
1130
|
-
live.refresh()
|
|
1336
|
+
live.refresh() # 确保最后一次刷新
|
|
1131
1337
|
else:
|
|
1132
|
-
|
|
1338
|
+
async for chunk in _stream_chat(
|
|
1339
|
+
endpoint, message, api_key, session_id, True, insecure, model, api_format
|
|
1340
|
+
):
|
|
1133
1341
|
content, reasoning = _extract_content(chunk)
|
|
1134
1342
|
if reasoning:
|
|
1135
1343
|
click.secho(reasoning, fg="bright_black", nl=False)
|
|
@@ -1137,7 +1345,9 @@ async def _invoke_once(
|
|
|
1137
1345
|
print(content, end="", flush=True)
|
|
1138
1346
|
click.echo() # 换行
|
|
1139
1347
|
else:
|
|
1140
|
-
response = await _chat(
|
|
1348
|
+
response = await _chat(
|
|
1349
|
+
endpoint, message, api_key, session_id, insecure, model, api_format
|
|
1350
|
+
)
|
|
1141
1351
|
content = _extract_response_content(response)
|
|
1142
1352
|
if console and Markdown:
|
|
1143
1353
|
console.print(Markdown(content))
|
|
@@ -1147,16 +1357,15 @@ async def _invoke_once(
|
|
|
1147
1357
|
click.secho(f"\n❌ 调用失败: {e}", fg="red")
|
|
1148
1358
|
|
|
1149
1359
|
|
|
1150
|
-
|
|
1151
1360
|
async def _chat(
|
|
1152
1361
|
endpoint: str,
|
|
1153
1362
|
message: str,
|
|
1154
|
-
api_key: str = None,
|
|
1155
|
-
session_id: str = None,
|
|
1363
|
+
api_key: Optional[str] = None,
|
|
1364
|
+
session_id: Optional[str] = None,
|
|
1156
1365
|
insecure: bool = False,
|
|
1157
|
-
model: str = None,
|
|
1366
|
+
model: Optional[str] = None,
|
|
1158
1367
|
api_format: str = "chat_completions",
|
|
1159
|
-
) -> dict:
|
|
1368
|
+
) -> dict[str, Any]:
|
|
1160
1369
|
"""非流式调用 (OpenAI 兼容格式)"""
|
|
1161
1370
|
try:
|
|
1162
1371
|
import httpx
|
|
@@ -1167,10 +1376,16 @@ async def _chat(
|
|
|
1167
1376
|
normalized_api_format = str(api_format or "chat_completions").strip().lower()
|
|
1168
1377
|
if normalized_api_format == "responses":
|
|
1169
1378
|
url = f"{endpoint.rstrip('/')}/v1/responses"
|
|
1170
|
-
payload
|
|
1379
|
+
payload: dict[str, Any] = {
|
|
1380
|
+
"input": [{"role": "user", "content": message}],
|
|
1381
|
+
"stream": False,
|
|
1382
|
+
}
|
|
1171
1383
|
else:
|
|
1172
1384
|
url = f"{endpoint.rstrip('/')}/v1/chat/completions"
|
|
1173
|
-
payload = {
|
|
1385
|
+
payload = {
|
|
1386
|
+
"messages": [{"role": "user", "content": message}],
|
|
1387
|
+
"stream": False,
|
|
1388
|
+
}
|
|
1174
1389
|
|
|
1175
1390
|
if session_id:
|
|
1176
1391
|
payload["session_id"] = session_id
|
|
@@ -1184,31 +1399,37 @@ async def _chat(
|
|
|
1184
1399
|
is_local = "localhost" in url or "127.0.0.1" in url or "0.0.0.0" in url
|
|
1185
1400
|
|
|
1186
1401
|
# 构造 httpx client 配置
|
|
1187
|
-
client_kwargs
|
|
1402
|
+
client_kwargs: dict[str, Any] = {
|
|
1403
|
+
"timeout": 60,
|
|
1404
|
+
"trust_env": not is_local,
|
|
1405
|
+
}
|
|
1188
1406
|
|
|
1189
1407
|
# 如果指定了 --insecure 参数,跳过 SSL 证书验证(类似 curl -k)
|
|
1190
1408
|
if insecure:
|
|
1191
1409
|
client_kwargs["verify"] = False
|
|
1192
1410
|
|
|
1193
1411
|
# 构造 Headers
|
|
1194
|
-
headers = {}
|
|
1412
|
+
headers: dict[str, str] = {}
|
|
1195
1413
|
if api_key:
|
|
1196
1414
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
1197
1415
|
|
|
1198
1416
|
async with httpx.AsyncClient(**client_kwargs) as client:
|
|
1199
1417
|
response = await client.post(url, json=payload, headers=headers)
|
|
1200
1418
|
response.raise_for_status()
|
|
1201
|
-
|
|
1419
|
+
response_payload = response.json()
|
|
1420
|
+
if not isinstance(response_payload, dict):
|
|
1421
|
+
raise ValueError("Agent response must be a JSON object")
|
|
1422
|
+
return response_payload
|
|
1202
1423
|
|
|
1203
1424
|
|
|
1204
1425
|
async def _stream_chat(
|
|
1205
1426
|
endpoint: str,
|
|
1206
1427
|
message: str,
|
|
1207
|
-
api_key: str = None,
|
|
1208
|
-
session_id: str = None,
|
|
1428
|
+
api_key: Optional[str] = None,
|
|
1429
|
+
session_id: Optional[str] = None,
|
|
1209
1430
|
is_once: bool = False,
|
|
1210
1431
|
insecure: bool = False,
|
|
1211
|
-
model: str = None,
|
|
1432
|
+
model: Optional[str] = None,
|
|
1212
1433
|
api_format: str = "chat_completions",
|
|
1213
1434
|
):
|
|
1214
1435
|
"""流式调用 (SSE)"""
|
|
@@ -1221,7 +1442,10 @@ async def _stream_chat(
|
|
|
1221
1442
|
normalized_api_format = str(api_format or "chat_completions").strip().lower()
|
|
1222
1443
|
if normalized_api_format == "responses":
|
|
1223
1444
|
url = f"{endpoint.rstrip('/')}/v1/responses"
|
|
1224
|
-
payload
|
|
1445
|
+
payload: dict[str, Any] = {
|
|
1446
|
+
"input": [{"role": "user", "content": message}],
|
|
1447
|
+
"stream": True,
|
|
1448
|
+
}
|
|
1225
1449
|
else:
|
|
1226
1450
|
url = f"{endpoint.rstrip('/')}/v1/chat/completions"
|
|
1227
1451
|
payload = {"messages": [{"role": "user", "content": message}], "stream": True}
|
|
@@ -1238,14 +1462,17 @@ async def _stream_chat(
|
|
|
1238
1462
|
is_local = "localhost" in url or "127.0.0.1" in url or "0.0.0.0" in url
|
|
1239
1463
|
|
|
1240
1464
|
# 构造 httpx client 配置
|
|
1241
|
-
client_kwargs
|
|
1465
|
+
client_kwargs: dict[str, Any] = {
|
|
1466
|
+
"timeout": 60,
|
|
1467
|
+
"trust_env": not is_local,
|
|
1468
|
+
}
|
|
1242
1469
|
|
|
1243
1470
|
# 如果指定了 --insecure 参数,跳过 SSL 证书验证(类似 curl -k)
|
|
1244
1471
|
if insecure:
|
|
1245
1472
|
client_kwargs["verify"] = False
|
|
1246
1473
|
|
|
1247
1474
|
# 构造 Headers
|
|
1248
|
-
headers = {}
|
|
1475
|
+
headers: dict[str, str] = {}
|
|
1249
1476
|
if api_key:
|
|
1250
1477
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
1251
1478
|
|
|
@@ -1270,6 +1497,8 @@ async def _stream_chat(
|
|
|
1270
1497
|
|
|
1271
1498
|
try:
|
|
1272
1499
|
data = json.loads(data_str)
|
|
1500
|
+
if not isinstance(data, dict):
|
|
1501
|
+
continue
|
|
1273
1502
|
if current_event and isinstance(data, dict):
|
|
1274
1503
|
data = {**data, "_event": current_event}
|
|
1275
1504
|
# 直接 yield 解析后的 JSON 数据,让 _extract_content 处理
|
|
@@ -1330,7 +1559,7 @@ def _extract_response_content(response: dict) -> str:
|
|
|
1330
1559
|
choices = response.get("choices", [])
|
|
1331
1560
|
if choices:
|
|
1332
1561
|
message = choices[0].get("message", {})
|
|
1333
|
-
return message.get("content"
|
|
1562
|
+
return str(message.get("content") or "")
|
|
1334
1563
|
except (KeyError, IndexError):
|
|
1335
1564
|
pass
|
|
1336
1565
|
return str(response)
|