agentengine-sdk-python 0.6.2__py3-none-any.whl → 0.6.9__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/METADATA +179 -0
- agentengine_sdk_python-0.6.9.dist-info/RECORD +487 -0
- {agentengine_sdk_python-0.6.2.dist-info → agentengine_sdk_python-0.6.9.dist-info}/WHEEL +1 -1
- agentengine_sdk_python-0.6.9.dist-info/entry_points.txt +3 -0
- agentengine_sdk_python-0.6.9.dist-info/licenses/LICENSE +175 -0
- agentengine_sdk_python-0.6.9.dist-info/top_level.txt +2 -0
- ksadk/__init__.py +13 -0
- ksadk/__main__.py +4 -0
- ksadk/a2a/__init__.py +15 -0
- ksadk/a2a/card_builder.py +64 -0
- ksadk/a2a/client.py +309 -0
- ksadk/a2a/executor.py +201 -0
- ksadk/a2a/server.py +79 -0
- ksadk/agents/__init__.py +20 -0
- ksadk/agents/base.py +370 -0
- ksadk/agents/context.py +38 -0
- ksadk/agents/event.py +35 -0
- ksadk/agents/loop_agent.py +71 -0
- ksadk/agents/parallel_agent.py +109 -0
- ksadk/agents/runner_adapter.py +24 -0
- ksadk/agents/sequential_agent.py +34 -0
- ksadk/api/__init__.py +3 -0
- ksadk/api/client.py +2227 -0
- ksadk/builders/__init__.py +20 -0
- ksadk/builders/base.py +66 -0
- ksadk/builders/code_builder.py +1990 -0
- ksadk/builders/container_builder.py +786 -0
- ksadk/builders/framework_requirements.py +59 -0
- ksadk/builders/ks3_uploader.py +389 -0
- ksadk/builders/mcp_builder.py +476 -0
- ksadk/builders/requirements_utils.py +75 -0
- ksadk/cli/__init__.py +531 -0
- ksadk/cli/__main__.py +4 -0
- ksadk/cli/agent_ref.py +204 -0
- ksadk/cli/cmd_a2a.py +167 -0
- ksadk/cli/cmd_agent.py +201 -0
- ksadk/cli/cmd_build.py +270 -0
- ksadk/cli/cmd_completion.py +304 -0
- ksadk/cli/cmd_config.py +758 -0
- ksadk/cli/cmd_create.py +2277 -0
- ksadk/cli/cmd_dashboard.py +900 -0
- ksadk/cli/cmd_deploy.py +704 -0
- ksadk/cli/cmd_destroy.py +345 -0
- ksadk/cli/cmd_files.py +1227 -0
- ksadk/cli/cmd_hermes.py +1259 -0
- ksadk/cli/cmd_invoke.py +1336 -0
- ksadk/cli/cmd_launch.py +564 -0
- ksadk/cli/cmd_mcp.py +1174 -0
- ksadk/cli/cmd_model.py +302 -0
- ksadk/cli/cmd_openclaw.py +3918 -0
- ksadk/cli/cmd_run.py +232 -0
- ksadk/cli/cmd_status.py +588 -0
- ksadk/cli/cmd_version.py +522 -0
- ksadk/cli/cmd_web.py +288 -0
- ksadk/cli/deploy_utils.py +87 -0
- ksadk/cli/dry_run.py +214 -0
- ksadk/cli/env_options.py +82 -0
- ksadk/cli/error_utils.py +525 -0
- ksadk/cli/global_options.py +31 -0
- ksadk/cli/local_runtime.py +97 -0
- ksadk/cli/model_catalog.py +136 -0
- ksadk/cli/network_options.py +410 -0
- ksadk/cli/resource_common.py +621 -0
- ksadk/cli/storage.py +84 -0
- ksadk/cli/ui.py +431 -0
- ksadk/cli/workflow_common.py +552 -0
- ksadk/common/__init__.py +14 -0
- ksadk/common/aicp_env.py +128 -0
- ksadk/common/auth.py +190 -0
- ksadk/common/constants.py +84 -0
- ksadk/common/llm_utils.py +61 -0
- ksadk/configs/__init__.py +61 -0
- ksadk/configs/env_registry.py +342 -0
- ksadk/configs/global_config.py +200 -0
- ksadk/configs/settings.py +706 -0
- ksadk/conversations/__init__.py +95 -0
- ksadk/conversations/attachment_storage.py +379 -0
- ksadk/conversations/attachments.py +774 -0
- ksadk/conversations/compaction_pipeline.py +387 -0
- ksadk/conversations/compaction_prompt.py +128 -0
- ksadk/conversations/context.py +303 -0
- ksadk/conversations/model_context.py +360 -0
- ksadk/conversations/model_options.py +136 -0
- ksadk/conversations/normalize.py +446 -0
- ksadk/conversations/reasoning_markup.py +91 -0
- ksadk/conversations/run_kinds.py +61 -0
- ksadk/conversations/run_status.py +53 -0
- ksadk/conversations/runtime.py +5208 -0
- ksadk/conversations/semantic_summary.py +359 -0
- ksadk/conversations/session_title.py +254 -0
- ksadk/deployment/__init__.py +83 -0
- ksadk/deployment/agent_access.py +226 -0
- ksadk/deployment/base.py +229 -0
- ksadk/deployment/manager.py +332 -0
- ksadk/deployment/providers/__init__.py +10 -0
- ksadk/deployment/providers/serverless.py +1105 -0
- ksadk/deployment/registry.py +120 -0
- ksadk/deployment/state.py +78 -0
- ksadk/deployment/ui_config.py +21 -0
- ksadk/detection/__init__.py +7 -0
- ksadk/detection/detector.py +431 -0
- ksadk/detection/mcp_detector.py +250 -0
- ksadk/hermes_terminal.py +467 -0
- ksadk/identity/__init__.py +17 -0
- ksadk/identity/resolver.py +385 -0
- ksadk/knowledge_base/__init__.py +62 -0
- ksadk/knowledge_base/adk_tool.py +47 -0
- ksadk/knowledge_base/client.py +297 -0
- ksadk/knowledge_base/langchain_tool.py +55 -0
- ksadk/knowledge_base/service.py +66 -0
- ksadk/knowledge_base/tool.py +50 -0
- ksadk/markdown.py +169 -0
- ksadk/mcp_runtime/__init__.py +231 -0
- ksadk/memory/__init__.py +57 -0
- ksadk/memory/adk/__init__.py +51 -0
- ksadk/memory/adk/backends/__init__.py +5 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
- ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
- ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
- ksadk/memory/adk/long_term_memory.py +273 -0
- ksadk/memory/adk/short_term_memory.py +289 -0
- ksadk/memory/adk_tool.py +31 -0
- ksadk/memory/backends/__init__.py +6 -0
- ksadk/memory/backends/base.py +103 -0
- ksadk/memory/backends/memory.py +112 -0
- ksadk/memory/backends/redis.py +146 -0
- ksadk/memory/langchain_tool.py +39 -0
- ksadk/memory/ltm_backend_factory.py +24 -0
- ksadk/memory/manager.py +215 -0
- ksadk/memory/service.py +172 -0
- ksadk/memory/tool.py +96 -0
- ksadk/model_policy.py +223 -0
- ksadk/openclaw_gateway.py +378 -0
- ksadk/runners/__init__.py +8 -0
- ksadk/runners/adk_runner.py +1135 -0
- ksadk/runners/base_runner.py +305 -0
- ksadk/runners/deepagents_runner.py +13 -0
- ksadk/runners/factory.py +73 -0
- ksadk/runners/langchain_runner.py +539 -0
- ksadk/runners/langgraph_runner.py +852 -0
- ksadk/runners/patch_langchain.py +187 -0
- ksadk/runners/remote_runner.py +667 -0
- ksadk/runners/usage_accumulator.py +38 -0
- ksadk/runners/utils/__init__.py +19 -0
- ksadk/runners/utils/langfuse.py +230 -0
- ksadk/runners/utils/loader.py +62 -0
- ksadk/runtime_context.py +166 -0
- ksadk/runtime_state.py +25 -0
- ksadk/sandbox/__init__.py +28 -0
- ksadk/sandbox/backends/__init__.py +9 -0
- ksadk/sandbox/backends/e2b.py +154 -0
- ksadk/sandbox/backends/local_process.py +131 -0
- ksadk/sandbox/base.py +101 -0
- ksadk/sandbox/factory.py +53 -0
- ksadk/sandbox/registry.py +187 -0
- ksadk/server/__init__.py +7 -0
- ksadk/server/api_models.py +73 -0
- ksadk/server/app.py +3789 -0
- ksadk/server/static/assets/ArtifactsPanel-C0meuOdi.js +1 -0
- ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +9 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +8 -0
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
- ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
- ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +1 -0
- ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
- ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
- ksadk/server/static/assets/arc-bj2rZnP5.js +1 -0
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +1 -0
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-2u1ltDIj.js +36 -0
- ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
- ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
- ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-D6fvdK20.js +132 -0
- ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-BK82L5n5.js +10 -0
- ksadk/server/static/assets/channel-BSSOwFIb.js +1 -0
- ksadk/server/static/assets/chunk-2J33WTMH-DLVdkeyQ.js +1 -0
- ksadk/server/static/assets/chunk-4BX2VUAB-BbwsYcbW.js +1 -0
- ksadk/server/static/assets/chunk-55IACEB6-DOairxzw.js +1 -0
- ksadk/server/static/assets/chunk-727SXJPM-hCO487gs.js +206 -0
- ksadk/server/static/assets/chunk-AQP2D5EJ-XU8vDOuG.js +231 -0
- ksadk/server/static/assets/chunk-FMBD7UC4-PrapyfMu.js +15 -0
- ksadk/server/static/assets/chunk-ND2GUHAM-CboKhTZ3.js +1 -0
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +1 -0
- ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
- ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
- ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
- ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
- ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
- ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
- ksadk/server/static/assets/cose-bilkent-S5V4N54A-_N8bJ-EU.js +1 -0
- ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
- ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
- ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
- ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
- ksadk/server/static/assets/dagre-BM42HDAG-FoDlo5J2.js +4 -0
- ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
- ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
- ksadk/server/static/assets/diagram-2AECGRRQ-BBp6ebBw.js +43 -0
- ksadk/server/static/assets/diagram-5GNKFQAL-CqOvG1PR.js +10 -0
- ksadk/server/static/assets/diagram-KO2AKTUF-O9T3t6ba.js +3 -0
- ksadk/server/static/assets/diagram-LMA3HP47-CGG8ekBO.js +24 -0
- ksadk/server/static/assets/diagram-OG6HWLK6-4BmH1EEg.js +24 -0
- ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
- ksadk/server/static/assets/dist-6kSZvCZh.js +1 -0
- ksadk/server/static/assets/dist-B67uF2uK.js +1 -0
- ksadk/server/static/assets/dist-BMKM66B4.js +1 -0
- ksadk/server/static/assets/dist-BVzcWa6D.js +1 -0
- ksadk/server/static/assets/dist-Bk1l05Ni.js +1 -0
- ksadk/server/static/assets/dist-BkJjNih3.js +1 -0
- ksadk/server/static/assets/dist-BmuTWcm7.js +9 -0
- ksadk/server/static/assets/dist-C2DKRFVK.js +1 -0
- ksadk/server/static/assets/dist-CWiTDN_V.js +6 -0
- ksadk/server/static/assets/dist-CZ6C3x8B.js +23 -0
- ksadk/server/static/assets/dist-CiZHB6ZW.js +1 -0
- ksadk/server/static/assets/dist-Cp1_jKwf.js +1 -0
- ksadk/server/static/assets/dist-Cwhk4lwI.js +2 -0
- ksadk/server/static/assets/dist-D5JGcxBx.js +11 -0
- ksadk/server/static/assets/dist-D99wf6_X.js +1 -0
- ksadk/server/static/assets/dist-DCpZrjaK.js +1 -0
- ksadk/server/static/assets/dist-DI-oVAXw.js +1 -0
- ksadk/server/static/assets/dist-DLMjL01k.js +1 -0
- ksadk/server/static/assets/dist-DLeM1OCK.js +1 -0
- ksadk/server/static/assets/dist-DM0rEQPk.js +1 -0
- ksadk/server/static/assets/dist-DbaH3p5h.js +1 -0
- ksadk/server/static/assets/dist-S4klkK5W.js +1 -0
- ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
- ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
- ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
- ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
- ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
- ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
- ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
- ksadk/server/static/assets/erDiagram-TEJ5UH35-oJhaci7S.js +85 -0
- ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +1 -0
- ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
- ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DtTmUan-.js +162 -0
- ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
- ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BSsb_eij.js +292 -0
- ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
- ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-BR5s6Pws.js +106 -0
- ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
- ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
- ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
- ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
- ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
- ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
- ksadk/server/static/assets/index-BH7-pC0B.js +165 -0
- ksadk/server/static/assets/index-DAURbCsP.css +1 -0
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +1 -0
- ksadk/server/static/assets/infoDiagram-5YYISTIA-DYPc5cvh.js +2 -0
- ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
- ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-MoBdALDg.js +70 -0
- ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
- ksadk/server/static/assets/journeyDiagram-JHISSGLW-DgLiQT8l.js +139 -0
- ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
- ksadk/server/static/assets/kanban-definition-UN3LZRKU-CsFx112F.js +89 -0
- ksadk/server/static/assets/katex-DRsiwpM2.js +265 -0
- ksadk/server/static/assets/linear-TNRQ5Orv.js +1 -0
- ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
- ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
- ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
- ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
- ksadk/server/static/assets/mermaid-parser.core-D2eMVOgo.js +161 -0
- ksadk/server/static/assets/mindmap-definition-RKZ34NQL-BtByYFhL.js +96 -0
- ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
- ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
- ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
- ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
- ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
- ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
- ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
- ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
- ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
- ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
- ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +1 -0
- ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
- ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +1 -0
- ksadk/server/static/assets/pieDiagram-4H26LBE5-DPnuCB8c.js +30 -0
- ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
- ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
- ksadk/server/static/assets/properties-C357ku5U.js +1 -0
- ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
- ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
- ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
- ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
- ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
- ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-miFpCPv4.js +7 -0
- ksadk/server/static/assets/r-DADKenSm.js +1 -0
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +1 -0
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-mjB_yYkW.js +84 -0
- ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
- ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
- ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-D7hSlZZZ.js +40 -0
- ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
- ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
- ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-D9Cx6hC3.js +162 -0
- ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
- ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
- ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
- ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
- ksadk/server/static/assets/solr-BBopId9w.js +1 -0
- ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
- ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
- ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
- ksadk/server/static/assets/stateDiagram-AJRCARHV-D_zrg47o.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +1 -0
- ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
- ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
- ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
- ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
- ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
- ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
- ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
- ksadk/server/static/assets/timeline-definition-PNZ67QCA-CwSec0Wa.js +120 -0
- ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +1 -0
- ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
- ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
- ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
- ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
- ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
- ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
- ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-agCVobiF.js +34 -0
- ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
- ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-TmrlhWNt.js +78 -0
- ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
- ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
- ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-DV71V-ah.js +7 -0
- ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
- ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
- ksadk/server/static/favicon.svg +1 -0
- ksadk/server/static/icons.svg +24 -0
- ksadk/server/static/index.html +14 -0
- ksadk/server/terminal_sessions.py +551 -0
- ksadk/sessions/__init__.py +283 -0
- ksadk/sessions/base.py +354 -0
- ksadk/sessions/continuity.py +313 -0
- ksadk/sessions/errors.py +7 -0
- ksadk/sessions/in_memory.py +273 -0
- ksadk/sessions/local_service.py +949 -0
- ksadk/sessions/postgres_service.py +838 -0
- ksadk/sessions/sqlite_service.py +13 -0
- ksadk/skills/__init__.py +9 -0
- ksadk/skills/loader.py +46 -0
- ksadk/skills/models.py +120 -0
- ksadk/skills/package_store.py +104 -0
- ksadk/skills/runtime/__init__.py +17 -0
- ksadk/skills/runtime/agent.py +183 -0
- ksadk/skills/runtime/artifacts.py +39 -0
- ksadk/skills/runtime/backends/__init__.py +1 -0
- ksadk/skills/runtime/backends/disabled.py +8 -0
- ksadk/skills/runtime/backends/e2b.py +168 -0
- ksadk/skills/runtime/backends/local.py +95 -0
- ksadk/skills/runtime/base.py +103 -0
- ksadk/skills/runtime/executor.py +219 -0
- ksadk/skills/runtime/factory.py +37 -0
- ksadk/skills/runtime/loader.py +131 -0
- ksadk/skills/runtime/registry.py +150 -0
- ksadk/skills/runtime/request.py +67 -0
- ksadk/skills/service_client.py +239 -0
- ksadk/skills/service_env.py +58 -0
- ksadk/skills/tool_defs.py +221 -0
- ksadk/terminal_client.py +69 -0
- ksadk/terminal_exec_policy.py +229 -0
- ksadk/tools/__init__.py +17 -0
- ksadk/tools/gateway.py +230 -0
- ksadk/tools/result_budget.py +154 -0
- ksadk/toolsets/__init__.py +852 -0
- ksadk/toolsets/_langchain.py +11 -0
- ksadk/toolsets/platform.py +93 -0
- ksadk/toolsets/sandbox.py +310 -0
- ksadk/toolsets/skills.py +217 -0
- ksadk/toolsets/web.py +266 -0
- ksadk/toolsets/workspace.py +878 -0
- ksadk/toolsets/workspace_state.py +44 -0
- ksadk/tracing/__init__.py +37 -0
- ksadk/tracing/exporters/__init__.py +1 -0
- ksadk/tracing/exporters/inmemory_exporter.py +116 -0
- ksadk/tracing/exporters/langfuse_exporter.py +445 -0
- ksadk/tracing/setup.py +829 -0
- ksadk/tracing/span_utils.py +114 -0
- ksadk/tui/__init__.py +12 -0
- ksadk/tui/app.py +400 -0
- ksadk/tui/clipboard.py +100 -0
- ksadk/tui/widgets/__init__.py +22 -0
- ksadk/tui/widgets/assistant.py +89 -0
- ksadk/tui/widgets/base.py +54 -0
- ksadk/tui/widgets/chat_input.py +246 -0
- ksadk/tui/widgets/history.py +156 -0
- ksadk/tui/widgets/system.py +87 -0
- ksadk/tui/widgets/thinking.py +110 -0
- ksadk/tui/widgets/tool_call.py +161 -0
- ksadk/tui/widgets/user.py +37 -0
- ksadk/ui_config.py +168 -0
- ksadk/version.py +4 -0
- ksadk_runtime_common/__init__.py +20 -0
- ksadk_runtime_common/memory_backend/__init__.py +29 -0
- ksadk_runtime_common/memory_backend/manifest.py +79 -0
- ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
- ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
- ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
- ksadk_runtime_common/memory_backend/registry.py +56 -0
- ksadk_runtime_common/memory_backend/render.py +71 -0
- ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
- ksadk_runtime_common/workspace_files/__init__.py +29 -0
- ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
- ksadk_runtime_common/workspace_files/constants.py +9 -0
- ksadk_runtime_common/workspace_files/path_utils.py +71 -0
- ksadk_runtime_common/workspace_files/preview.py +96 -0
- ksadk_runtime_common/workspace_files/router.py +254 -0
- agentengine_sdk_python-0.6.2.dist-info/METADATA +0 -46
- agentengine_sdk_python-0.6.2.dist-info/RECORD +0 -4
- agentengine_sdk_python-0.6.2.dist-info/top_level.txt +0 -1
ksadk/cli/cmd_invoke.py
ADDED
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agentengine invoke - 与已部署的 Agent 进行交互
|
|
3
|
+
|
|
4
|
+
支持 OpenAI 兼容格式调用,支持流式输出
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import asyncio
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Callable, Dict, Optional
|
|
15
|
+
import time
|
|
16
|
+
import uuid
|
|
17
|
+
from ksadk.api import AgentEngineAPIError, AgentEngineClient
|
|
18
|
+
from ksadk.cli.agent_ref import merge_agent_inputs, resolve_agent_ref, resolve_openclaw_ref
|
|
19
|
+
from ksadk.cli.cmd_files import (
|
|
20
|
+
_build_sync_payload,
|
|
21
|
+
_collect_local_files_report,
|
|
22
|
+
_emit_sync_payload,
|
|
23
|
+
_format_size,
|
|
24
|
+
_normalize_workspace_dir,
|
|
25
|
+
_push_workspace_files,
|
|
26
|
+
)
|
|
27
|
+
from ksadk.cli.resource_common import CONTEXT_SETTINGS, CompatibilityAliasCommand, print_compatibility_hint
|
|
28
|
+
from ksadk.hermes_terminal import run_hermes_terminal_session
|
|
29
|
+
from ksadk.terminal_client import run_terminal_session
|
|
30
|
+
from ksadk_runtime_common.workspace_files.constants import DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from rich.console import Console
|
|
34
|
+
from rich.markdown import Markdown
|
|
35
|
+
from rich.live import Live
|
|
36
|
+
console = Console()
|
|
37
|
+
except ImportError:
|
|
38
|
+
console = None
|
|
39
|
+
Markdown = None
|
|
40
|
+
Live = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@click.command(
|
|
44
|
+
context_settings=CONTEXT_SETTINGS,
|
|
45
|
+
hidden=True,
|
|
46
|
+
cls=CompatibilityAliasCommand,
|
|
47
|
+
canonical_command="agentengine agent invoke",
|
|
48
|
+
)
|
|
49
|
+
@click.argument("agent_ref", required=False)
|
|
50
|
+
@click.option("--agent", "--agent-id", "agent_option", "-a", help="Agent 名称或 ID")
|
|
51
|
+
@click.option("--endpoint", "-e", help="Agent Endpoint URL (覆盖自动获取)")
|
|
52
|
+
@click.option("--api-key", help="AgentEngine API Key (覆盖本地配置)")
|
|
53
|
+
@click.option(
|
|
54
|
+
"--gateway-token",
|
|
55
|
+
"openclaw_gateway_token",
|
|
56
|
+
envvar="OPENCLAW_GATEWAY_TOKEN",
|
|
57
|
+
help="OpenClaw Gateway token/password(用于 OpenClaw token/password 模式的 /v1/responses)",
|
|
58
|
+
)
|
|
59
|
+
@click.option("--message", "-m", help="发送的消息 (单次调用模式)")
|
|
60
|
+
@click.option("--session", "-s", help="Session ID (可选)")
|
|
61
|
+
@click.option("--region", "-r", default="cn-beijing-6", envvar="KSYUN_REGION", help="区域")
|
|
62
|
+
@click.option("--local", "-l", is_flag=True, help="连接本地服务 (http://localhost:8080)")
|
|
63
|
+
@click.option("--insecure", "-k", is_flag=True, help="跳过 SSL 证书验证 (类似 curl -k)")
|
|
64
|
+
@click.option(
|
|
65
|
+
"--transport",
|
|
66
|
+
type=click.Choice(["auto", "chat", "native"], case_sensitive=False),
|
|
67
|
+
default="auto",
|
|
68
|
+
show_default=True,
|
|
69
|
+
help="交互传输层: auto(自动), chat(HTTP OpenAI API), native(框架原生远端终端)",
|
|
70
|
+
)
|
|
71
|
+
@click.option(
|
|
72
|
+
"--local-workspace",
|
|
73
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
|
74
|
+
help="仅 Hermes 远程 native 模式: 先将本地目录同步到远端 workspace",
|
|
75
|
+
)
|
|
76
|
+
@click.option(
|
|
77
|
+
"--remote-workspace-path",
|
|
78
|
+
help="远端 workspace 子目录 (默认使用本地目录名)",
|
|
79
|
+
)
|
|
80
|
+
@click.option(
|
|
81
|
+
"--verbose-workspace-sync",
|
|
82
|
+
is_flag=True,
|
|
83
|
+
help="显示逐文件 workspace 同步日志(默认使用单行进度刷新)",
|
|
84
|
+
)
|
|
85
|
+
@click.option("--model", help="指定模型名称")
|
|
86
|
+
@click.option("--show-thinking", is_flag=True, help="显示模型思考过程")
|
|
87
|
+
def invoke(
|
|
88
|
+
agent_ref: str,
|
|
89
|
+
agent_option: str,
|
|
90
|
+
endpoint: str,
|
|
91
|
+
api_key: str,
|
|
92
|
+
openclaw_gateway_token: str,
|
|
93
|
+
message: str,
|
|
94
|
+
session: str,
|
|
95
|
+
region: str,
|
|
96
|
+
local: bool,
|
|
97
|
+
insecure: bool,
|
|
98
|
+
transport: str,
|
|
99
|
+
local_workspace: Path | None,
|
|
100
|
+
remote_workspace_path: str | None,
|
|
101
|
+
verbose_workspace_sync: bool,
|
|
102
|
+
model: str,
|
|
103
|
+
show_thinking: bool,
|
|
104
|
+
):
|
|
105
|
+
"""与 Agent 进行交互 (本地或远程)。"""
|
|
106
|
+
run_invoke_command(
|
|
107
|
+
agent_ref=agent_ref,
|
|
108
|
+
agent_option=agent_option,
|
|
109
|
+
endpoint=endpoint,
|
|
110
|
+
api_key=api_key,
|
|
111
|
+
openclaw_gateway_token=openclaw_gateway_token,
|
|
112
|
+
message=message,
|
|
113
|
+
session=session,
|
|
114
|
+
region=region,
|
|
115
|
+
local=local,
|
|
116
|
+
insecure=insecure,
|
|
117
|
+
transport=transport,
|
|
118
|
+
local_workspace=local_workspace,
|
|
119
|
+
remote_workspace_path=remote_workspace_path,
|
|
120
|
+
verbose_workspace_sync=verbose_workspace_sync,
|
|
121
|
+
model=model,
|
|
122
|
+
show_thinking=show_thinking,
|
|
123
|
+
compatibility_alias=True,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _resolve_remote_workspace_seed_path(
|
|
128
|
+
local_workspace: Path,
|
|
129
|
+
remote_workspace_path: str | None,
|
|
130
|
+
) -> str:
|
|
131
|
+
if remote_workspace_path:
|
|
132
|
+
return _normalize_workspace_dir(remote_workspace_path)
|
|
133
|
+
default_name = local_workspace.resolve().name or local_workspace.name or "workspace"
|
|
134
|
+
return _normalize_workspace_dir(default_name)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _summarize_local_workspace_dir(
|
|
138
|
+
local_dir: Path,
|
|
139
|
+
*,
|
|
140
|
+
ignore_git_artifacts: bool,
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
if not local_dir.exists():
|
|
143
|
+
raise click.ClickException(f"本地目录不存在: {local_dir}")
|
|
144
|
+
if not local_dir.is_dir():
|
|
145
|
+
raise click.ClickException(f"本地路径不是目录: {local_dir}")
|
|
146
|
+
|
|
147
|
+
report = _collect_local_files_report(
|
|
148
|
+
local_dir,
|
|
149
|
+
ignore_dev_artifacts=True,
|
|
150
|
+
ignore_git_artifacts=ignore_git_artifacts,
|
|
151
|
+
)
|
|
152
|
+
files = [
|
|
153
|
+
{
|
|
154
|
+
"path": file_path,
|
|
155
|
+
"relative_path": relative_path,
|
|
156
|
+
"size_bytes": int(file_path.stat().st_size),
|
|
157
|
+
}
|
|
158
|
+
for relative_path, file_path in report["files"]
|
|
159
|
+
]
|
|
160
|
+
return {
|
|
161
|
+
"local_dir": local_dir,
|
|
162
|
+
"files": files,
|
|
163
|
+
"total_files": len(files),
|
|
164
|
+
"total_bytes": int(report["total_bytes"]),
|
|
165
|
+
"ignored_artifacts": list(report["ignored_artifacts"]),
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _describe_workspace_sync_phase(event: dict[str, Any] | None) -> str:
|
|
170
|
+
phase = str((event or {}).get("phase") or "").strip()
|
|
171
|
+
remote_path = str((event or {}).get("remote_path") or "").strip()
|
|
172
|
+
if phase == "list_start":
|
|
173
|
+
return f"列出远端目录 {remote_path or '.'}"
|
|
174
|
+
if phase == "upload_start":
|
|
175
|
+
return f"上传 {remote_path or '文件'}"
|
|
176
|
+
if phase == "upload_done":
|
|
177
|
+
return f"确认上传 {remote_path or '文件'}"
|
|
178
|
+
if phase == "limit_done":
|
|
179
|
+
return "读取 workspace 上传限制"
|
|
180
|
+
if phase == "scan_done":
|
|
181
|
+
return "扫描本地目录"
|
|
182
|
+
return "同步 workspace"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _render_workspace_sync_progress_bar(current: object | None, total: object | None, *, width: int = 20) -> str:
|
|
186
|
+
try:
|
|
187
|
+
current_value = int(current or 0)
|
|
188
|
+
total_value = max(int(total or 0), 1)
|
|
189
|
+
except (TypeError, ValueError):
|
|
190
|
+
current_value = 0
|
|
191
|
+
total_value = 1
|
|
192
|
+
current_value = max(0, min(current_value, total_value))
|
|
193
|
+
filled = int(width * current_value / total_value)
|
|
194
|
+
return f"[{'#' * filled}{'-' * (width - filled)}]"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _format_workspace_sync_percent(current: object | None, total: object | None) -> str:
|
|
198
|
+
try:
|
|
199
|
+
current_value = float(current or 0)
|
|
200
|
+
total_value = max(float(total or 0), 1.0)
|
|
201
|
+
except (TypeError, ValueError):
|
|
202
|
+
current_value = 0.0
|
|
203
|
+
total_value = 1.0
|
|
204
|
+
percent = max(0.0, min(current_value * 100.0 / total_value, 100.0))
|
|
205
|
+
return f"{percent:.1f}".rstrip("0").rstrip(".") + "%"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _build_workspace_sync_progress_emitter(*, verbose: bool) -> Callable[[dict[str, Any]], None]:
|
|
209
|
+
active_inline = False
|
|
210
|
+
last_inline_width = 0
|
|
211
|
+
|
|
212
|
+
def _finish_inline() -> None:
|
|
213
|
+
nonlocal active_inline, last_inline_width
|
|
214
|
+
if active_inline:
|
|
215
|
+
click.echo(f"\r{' ' * last_inline_width}\r", nl=False)
|
|
216
|
+
click.echo("")
|
|
217
|
+
active_inline = False
|
|
218
|
+
last_inline_width = 0
|
|
219
|
+
|
|
220
|
+
def _emit(event: dict[str, Any]) -> None:
|
|
221
|
+
nonlocal active_inline, last_inline_width
|
|
222
|
+
|
|
223
|
+
phase = str(event.get("phase") or "").strip()
|
|
224
|
+
if phase == "scan_done":
|
|
225
|
+
_finish_inline()
|
|
226
|
+
click.secho(
|
|
227
|
+
"📂 准备同步 workspace: "
|
|
228
|
+
f"{event.get('total_files', 0)} 个文件,"
|
|
229
|
+
f"{_format_size(event.get('total_bytes', 0))} -> workspace:/{event.get('remote_path', '.')}",
|
|
230
|
+
fg="blue",
|
|
231
|
+
)
|
|
232
|
+
ignored_artifacts = list(event.get("ignored_artifacts") or [])
|
|
233
|
+
if ignored_artifacts:
|
|
234
|
+
click.echo(f" 默认忽略: {', '.join(ignored_artifacts)}")
|
|
235
|
+
return
|
|
236
|
+
if phase == "limit_done":
|
|
237
|
+
_finish_inline()
|
|
238
|
+
click.echo(f" 上传上限: {_format_size(event.get('max_upload_bytes'))}")
|
|
239
|
+
return
|
|
240
|
+
if phase == "list_start":
|
|
241
|
+
_finish_inline()
|
|
242
|
+
click.echo(f" 检查远端目录: workspace:/{event.get('remote_path', '.')}")
|
|
243
|
+
return
|
|
244
|
+
if phase == "list_done":
|
|
245
|
+
_finish_inline()
|
|
246
|
+
click.echo(f" 远端已有条目: {event.get('remote_entry_count', 0)}")
|
|
247
|
+
return
|
|
248
|
+
if phase == "upload_start":
|
|
249
|
+
total = max(int(event.get("total") or 0), 1)
|
|
250
|
+
current = max(0, min(int(event.get("current") or 0), total))
|
|
251
|
+
message = (
|
|
252
|
+
" "
|
|
253
|
+
f"{_render_workspace_sync_progress_bar(current, total)} "
|
|
254
|
+
f"{_format_workspace_sync_percent(current, total)} ({current}/{total}) "
|
|
255
|
+
f"上传 {event.get('remote_path', '')} "
|
|
256
|
+
f"({_format_size(event.get('size_bytes'))})"
|
|
257
|
+
)
|
|
258
|
+
if verbose:
|
|
259
|
+
click.echo(message)
|
|
260
|
+
else:
|
|
261
|
+
padded_message = message.ljust(last_inline_width)
|
|
262
|
+
click.echo(f"\r{padded_message}", nl=False)
|
|
263
|
+
active_inline = True
|
|
264
|
+
last_inline_width = max(last_inline_width, len(message))
|
|
265
|
+
return
|
|
266
|
+
if phase == "upload_skipped":
|
|
267
|
+
total = max(int(event.get("total") or 0), 1)
|
|
268
|
+
current = max(0, min(int(event.get("current") or 0), total))
|
|
269
|
+
message = (
|
|
270
|
+
" "
|
|
271
|
+
f"{_render_workspace_sync_progress_bar(current, total)} "
|
|
272
|
+
f"{_format_workspace_sync_percent(current, total)} ({current}/{total}) "
|
|
273
|
+
f"跳过已存在 {event.get('remote_path', '')}"
|
|
274
|
+
)
|
|
275
|
+
if verbose:
|
|
276
|
+
click.echo(message)
|
|
277
|
+
else:
|
|
278
|
+
padded_message = message.ljust(last_inline_width)
|
|
279
|
+
click.echo(f"\r{padded_message}", nl=False)
|
|
280
|
+
active_inline = True
|
|
281
|
+
last_inline_width = max(last_inline_width, len(message))
|
|
282
|
+
return
|
|
283
|
+
if phase == "upload_done":
|
|
284
|
+
total = max(int(event.get("total") or 0), 1)
|
|
285
|
+
current = max(0, min(int(event.get("current") or 0), total))
|
|
286
|
+
if not verbose and current >= total:
|
|
287
|
+
_finish_inline()
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
return _emit
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _emit_workspace_sync_progress(event: dict[str, Any]) -> None:
|
|
294
|
+
if not hasattr(_emit_workspace_sync_progress, "_emitter"):
|
|
295
|
+
_emit_workspace_sync_progress._emitter = _build_workspace_sync_progress_emitter( # type: ignore[attr-defined]
|
|
296
|
+
verbose=True
|
|
297
|
+
)
|
|
298
|
+
_emit_workspace_sync_progress._emitter(event) # type: ignore[attr-defined]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _extract_workspace_upload_limit(bootstrap: dict[str, Any] | None) -> int | None:
|
|
302
|
+
if not isinstance(bootstrap, dict):
|
|
303
|
+
return None
|
|
304
|
+
workspace = bootstrap.get("workspace_files") or bootstrap.get("WorkspaceFiles") or {}
|
|
305
|
+
if not isinstance(workspace, dict):
|
|
306
|
+
return None
|
|
307
|
+
raw_limit = workspace.get("max_upload_bytes") or workspace.get("MaxUploadBytes")
|
|
308
|
+
try:
|
|
309
|
+
limit = int(raw_limit)
|
|
310
|
+
except (TypeError, ValueError):
|
|
311
|
+
return None
|
|
312
|
+
return limit if limit > 0 else None
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
async def _lookup_workspace_upload_limit(
|
|
316
|
+
*,
|
|
317
|
+
agent_ref: str | None,
|
|
318
|
+
region: str,
|
|
319
|
+
) -> int:
|
|
320
|
+
if not agent_ref:
|
|
321
|
+
return DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
async with AgentEngineClient(region=region) as client:
|
|
325
|
+
if str(agent_ref).startswith("ar-"):
|
|
326
|
+
payload = await client.get_agent_ui_bootstrap(agent_id=agent_ref)
|
|
327
|
+
else:
|
|
328
|
+
payload = await client.get_agent_ui_bootstrap(name=agent_ref)
|
|
329
|
+
except Exception:
|
|
330
|
+
return DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
331
|
+
|
|
332
|
+
return _extract_workspace_upload_limit(payload) or DEFAULT_WORKSPACE_MAX_UPLOAD_BYTES
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
async def _sync_local_workspace_for_hermes_invoke(
|
|
336
|
+
*,
|
|
337
|
+
agent_ref: str | None,
|
|
338
|
+
local_workspace: str | Path,
|
|
339
|
+
remote_path: str,
|
|
340
|
+
region: str,
|
|
341
|
+
endpoint: str | None,
|
|
342
|
+
api_key: str | None,
|
|
343
|
+
progress_callback: Callable[[dict], None] | None = None,
|
|
344
|
+
) -> dict[str, Any]:
|
|
345
|
+
local_dir = Path(local_workspace).expanduser().resolve()
|
|
346
|
+
max_upload_bytes = await _lookup_workspace_upload_limit(agent_ref=agent_ref, region=region)
|
|
347
|
+
if progress_callback:
|
|
348
|
+
progress_callback(
|
|
349
|
+
{
|
|
350
|
+
"phase": "limit_done",
|
|
351
|
+
"remote_path": remote_path,
|
|
352
|
+
"max_upload_bytes": max_upload_bytes,
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
summary = _summarize_local_workspace_dir(local_dir, ignore_git_artifacts=False)
|
|
356
|
+
if summary["total_bytes"] > max_upload_bytes:
|
|
357
|
+
summary_without_git = _summarize_local_workspace_dir(local_dir, ignore_git_artifacts=True)
|
|
358
|
+
if summary_without_git["total_bytes"] <= max_upload_bytes:
|
|
359
|
+
summary = summary_without_git
|
|
360
|
+
if progress_callback:
|
|
361
|
+
progress_callback(
|
|
362
|
+
{
|
|
363
|
+
"phase": "scan_done",
|
|
364
|
+
"local_dir": str(local_dir),
|
|
365
|
+
"remote_path": remote_path,
|
|
366
|
+
"total_files": summary["total_files"],
|
|
367
|
+
"total_bytes": summary["total_bytes"],
|
|
368
|
+
"ignored_artifacts": summary.get("ignored_artifacts", []),
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
if summary["total_files"] == 0:
|
|
373
|
+
raise click.ClickException(f"本地目录为空,当前版本暂不支持同步纯空目录: {local_dir}")
|
|
374
|
+
|
|
375
|
+
for item in summary["files"]:
|
|
376
|
+
if item["size_bytes"] > max_upload_bytes:
|
|
377
|
+
raise click.ClickException(
|
|
378
|
+
"本地目录中存在超过远端 workspace 上传上限的文件:"
|
|
379
|
+
f"{item['path']}({_format_size(item['size_bytes'])} > {_format_size(max_upload_bytes)})"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if summary["total_bytes"] > max_upload_bytes:
|
|
383
|
+
raise click.ClickException(
|
|
384
|
+
"本地目录总大小超过远端 workspace 单次同步上限:"
|
|
385
|
+
f"{_format_size(summary['total_bytes'])} > {_format_size(max_upload_bytes)}"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
last_progress: dict[str, Any] | None = None
|
|
389
|
+
|
|
390
|
+
def _record_progress(event: dict[str, Any]) -> None:
|
|
391
|
+
nonlocal last_progress
|
|
392
|
+
last_progress = dict(event)
|
|
393
|
+
if progress_callback:
|
|
394
|
+
progress_callback(event)
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
return await _push_workspace_files(
|
|
398
|
+
agent_ref=agent_ref,
|
|
399
|
+
local_dir=local_dir,
|
|
400
|
+
remote_path=remote_path,
|
|
401
|
+
force=True,
|
|
402
|
+
region=region,
|
|
403
|
+
endpoint=endpoint,
|
|
404
|
+
api_key=api_key,
|
|
405
|
+
progress_callback=_record_progress,
|
|
406
|
+
ignore_dev_artifacts=True,
|
|
407
|
+
ignore_git_artifacts=".git" in list(summary.get("ignored_artifacts") or []),
|
|
408
|
+
)
|
|
409
|
+
except AgentEngineAPIError as exc:
|
|
410
|
+
phase = _describe_workspace_sync_phase(last_progress)
|
|
411
|
+
raise click.ClickException(
|
|
412
|
+
f"同步远端 workspace 失败({phase}):{exc.message}"
|
|
413
|
+
) from exc
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def run_invoke_command(
|
|
417
|
+
*,
|
|
418
|
+
agent_ref: str | None,
|
|
419
|
+
agent_option: str | None,
|
|
420
|
+
endpoint: str | None,
|
|
421
|
+
api_key: str | None,
|
|
422
|
+
message: str | None,
|
|
423
|
+
session: str | None,
|
|
424
|
+
region: str,
|
|
425
|
+
local: bool,
|
|
426
|
+
insecure: bool,
|
|
427
|
+
transport: str,
|
|
428
|
+
local_workspace: str | Path | None = None,
|
|
429
|
+
remote_workspace_path: str | None = None,
|
|
430
|
+
verbose_workspace_sync: bool = False,
|
|
431
|
+
model: str | None,
|
|
432
|
+
show_thinking: bool,
|
|
433
|
+
openclaw_gateway_token: str | None = None,
|
|
434
|
+
compatibility_alias: bool = False,
|
|
435
|
+
):
|
|
436
|
+
"""与 Agent 进行交互 (本地或远程)。"""
|
|
437
|
+
if compatibility_alias:
|
|
438
|
+
print_compatibility_hint(
|
|
439
|
+
legacy="agentengine invoke",
|
|
440
|
+
canonical="agentengine agent invoke",
|
|
441
|
+
)
|
|
442
|
+
try:
|
|
443
|
+
agent_input = merge_agent_inputs(
|
|
444
|
+
agent_option=agent_option,
|
|
445
|
+
positional_agent=agent_ref,
|
|
446
|
+
)
|
|
447
|
+
except ValueError as e:
|
|
448
|
+
click.secho(f"❌ {e}", fg="red")
|
|
449
|
+
raise SystemExit(1)
|
|
450
|
+
|
|
451
|
+
if remote_workspace_path and not local_workspace:
|
|
452
|
+
click.secho("❌ --remote-workspace-path 需要与 --local-workspace 一起使用。", fg="red")
|
|
453
|
+
raise SystemExit(1)
|
|
454
|
+
if local_workspace and message:
|
|
455
|
+
click.secho("❌ --local-workspace 仅支持远程 Hermes native 交互模式。", fg="red")
|
|
456
|
+
raise SystemExit(1)
|
|
457
|
+
|
|
458
|
+
# 加载本地状态
|
|
459
|
+
state = _load_state()
|
|
460
|
+
latest_access: dict[str, Any] = {}
|
|
461
|
+
reuse_state_session = True
|
|
462
|
+
target_agent: str | None = None
|
|
463
|
+
|
|
464
|
+
normalized_transport = (transport or "auto").strip().lower() or "auto"
|
|
465
|
+
|
|
466
|
+
# 确定 Endpoint
|
|
467
|
+
if local:
|
|
468
|
+
endpoint = "http://localhost:8080"
|
|
469
|
+
elif not endpoint:
|
|
470
|
+
resolved = resolve_agent_ref(
|
|
471
|
+
agent_input,
|
|
472
|
+
cwd=Path("."),
|
|
473
|
+
include_state=True,
|
|
474
|
+
include_project_config=True,
|
|
475
|
+
)
|
|
476
|
+
if not resolved and not agent_input:
|
|
477
|
+
resolved = resolve_openclaw_ref(
|
|
478
|
+
None,
|
|
479
|
+
cwd=Path("."),
|
|
480
|
+
include_state=True,
|
|
481
|
+
)
|
|
482
|
+
if not resolved:
|
|
483
|
+
click.secho("❌ 请指定 Agent(--agent 或位置参数)、--local 或 --endpoint", fg="red")
|
|
484
|
+
click.echo(" 自动解析顺序: .agentengine.state -> agentengine.yaml/ksadk.yaml")
|
|
485
|
+
raise SystemExit(1)
|
|
486
|
+
target_agent = resolved.value
|
|
487
|
+
if resolved.source != "cli":
|
|
488
|
+
click.echo(f"ℹ 未显式指定 Agent,使用 {resolved.source_text}: {target_agent}")
|
|
489
|
+
|
|
490
|
+
latest_access = _refresh_remote_access(
|
|
491
|
+
target_agent=target_agent,
|
|
492
|
+
region=region,
|
|
493
|
+
state=state,
|
|
494
|
+
persist=_state_matches_target(state, target_agent),
|
|
495
|
+
)
|
|
496
|
+
reuse_state_session = not agent_input or _state_matches_target(state, target_agent)
|
|
497
|
+
|
|
498
|
+
# 优先使用 state 里的 endpoint (如果是对应的 agent)
|
|
499
|
+
if latest_access.get("endpoint"):
|
|
500
|
+
endpoint = latest_access["endpoint"]
|
|
501
|
+
elif not agent_input or _state_matches_target(state, target_agent):
|
|
502
|
+
endpoint = state.get("endpoint")
|
|
503
|
+
|
|
504
|
+
if not endpoint:
|
|
505
|
+
# 自动获取
|
|
506
|
+
endpoint = _get_endpoint(target_agent, region)
|
|
507
|
+
|
|
508
|
+
# API Key
|
|
509
|
+
api_key = api_key or latest_access.get("api_key") or state.get("api_key")
|
|
510
|
+
session_id = (
|
|
511
|
+
session
|
|
512
|
+
or (state.get("session_id") if reuse_state_session else None)
|
|
513
|
+
or str(uuid.uuid4())[:8]
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
next_state = _load_state() or dict(state)
|
|
517
|
+
for key in ("agent_id", "name", "endpoint", "api_key", "framework"):
|
|
518
|
+
if latest_access.get(key):
|
|
519
|
+
next_state[key] = latest_access[key]
|
|
520
|
+
if latest_access.get("framework") == "hermes":
|
|
521
|
+
next_state["type"] = "hermes"
|
|
522
|
+
if latest_access.get("framework") == "openclaw":
|
|
523
|
+
next_state["type"] = "openclaw"
|
|
524
|
+
next_state["session_id"] = session_id
|
|
525
|
+
_save_state(next_state)
|
|
526
|
+
runtime_api_key = _select_runtime_api_key(
|
|
527
|
+
api_key=api_key,
|
|
528
|
+
openclaw_gateway_token=openclaw_gateway_token,
|
|
529
|
+
state=next_state,
|
|
530
|
+
latest_access=latest_access,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
click.secho(f"🤖 连接到 Agent", fg="blue", bold=True)
|
|
534
|
+
click.echo(f" Endpoint: {endpoint}")
|
|
535
|
+
if _is_openclaw_target(next_state, latest_access):
|
|
536
|
+
if runtime_api_key:
|
|
537
|
+
click.echo(f" Runtime Auth: OpenClaw Gateway token {_mask_secret(runtime_api_key)}")
|
|
538
|
+
else:
|
|
539
|
+
click.echo(" Runtime Auth: OpenClaw Gateway anonymous/trusted-proxy")
|
|
540
|
+
if api_key:
|
|
541
|
+
click.echo(f" AgentEngine API: Bearer {_mask_secret(api_key)}")
|
|
542
|
+
elif api_key:
|
|
543
|
+
click.echo(f" Auth: Bearer {_mask_secret(api_key)}")
|
|
544
|
+
else:
|
|
545
|
+
click.secho(" ⚠️ 未发现 API Key,尝试匿名调用", fg="yellow")
|
|
546
|
+
|
|
547
|
+
if insecure:
|
|
548
|
+
click.secho(" ⚠️ SSL 证书验证已禁用", fg="yellow")
|
|
549
|
+
|
|
550
|
+
if message:
|
|
551
|
+
# 单次调用模式
|
|
552
|
+
api_format = asyncio.run(
|
|
553
|
+
_resolve_remote_api_format(
|
|
554
|
+
endpoint=endpoint,
|
|
555
|
+
api_key=api_key,
|
|
556
|
+
runtime_api_key=runtime_api_key,
|
|
557
|
+
insecure=insecure,
|
|
558
|
+
state=next_state,
|
|
559
|
+
latest_access=latest_access,
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
asyncio.run(_invoke_once(endpoint, message, runtime_api_key, session_id, True, insecure, model, api_format))
|
|
563
|
+
else:
|
|
564
|
+
is_hermes_target = _is_hermes_target(next_state, latest_access)
|
|
565
|
+
is_openclaw_target = _is_openclaw_target(next_state, latest_access)
|
|
566
|
+
if normalized_transport == "chat" and is_hermes_target:
|
|
567
|
+
click.secho("❌ Hermes 不再支持 ksadk 通用 chat TUI。", fg="red")
|
|
568
|
+
click.echo(" 浏览器聊天页请改用: agentengine hermes open --chat")
|
|
569
|
+
raise SystemExit(1)
|
|
570
|
+
if local_workspace:
|
|
571
|
+
if local or normalized_transport == "chat":
|
|
572
|
+
click.secho("❌ --local-workspace 仅支持远程 Hermes native 交互模式。", fg="red")
|
|
573
|
+
raise SystemExit(1)
|
|
574
|
+
if not is_hermes_target:
|
|
575
|
+
click.secho("❌ --local-workspace 仅支持 Hermes 远程 native 模式。", fg="red")
|
|
576
|
+
raise SystemExit(1)
|
|
577
|
+
|
|
578
|
+
local_workspace_dir = Path(local_workspace).expanduser().resolve()
|
|
579
|
+
normalized_remote_workspace_path = _resolve_remote_workspace_seed_path(
|
|
580
|
+
local_workspace_dir,
|
|
581
|
+
remote_workspace_path,
|
|
582
|
+
)
|
|
583
|
+
sync_agent_ref = (
|
|
584
|
+
latest_access.get("agent_id")
|
|
585
|
+
or next_state.get("agent_id")
|
|
586
|
+
or target_agent
|
|
587
|
+
or latest_access.get("name")
|
|
588
|
+
or next_state.get("name")
|
|
589
|
+
)
|
|
590
|
+
sync_payload = asyncio.run(
|
|
591
|
+
_sync_local_workspace_for_hermes_invoke(
|
|
592
|
+
agent_ref=sync_agent_ref,
|
|
593
|
+
local_workspace=local_workspace_dir,
|
|
594
|
+
remote_path=normalized_remote_workspace_path,
|
|
595
|
+
region=region,
|
|
596
|
+
endpoint=endpoint,
|
|
597
|
+
api_key=api_key,
|
|
598
|
+
progress_callback=_build_workspace_sync_progress_emitter(
|
|
599
|
+
verbose=verbose_workspace_sync
|
|
600
|
+
),
|
|
601
|
+
)
|
|
602
|
+
)
|
|
603
|
+
_emit_sync_payload(_build_sync_payload(sync_payload), None)
|
|
604
|
+
else:
|
|
605
|
+
normalized_remote_workspace_path = None
|
|
606
|
+
# 默认 TUI 模式
|
|
607
|
+
if _should_use_hermes_native_tui(
|
|
608
|
+
transport=normalized_transport,
|
|
609
|
+
local=local,
|
|
610
|
+
state=next_state,
|
|
611
|
+
latest_access=latest_access,
|
|
612
|
+
):
|
|
613
|
+
native_kwargs: dict[str, Any] = {
|
|
614
|
+
"endpoint": endpoint,
|
|
615
|
+
"api_key": api_key,
|
|
616
|
+
"session_id": session_id,
|
|
617
|
+
"insecure": insecure,
|
|
618
|
+
}
|
|
619
|
+
if normalized_remote_workspace_path is not None:
|
|
620
|
+
native_kwargs["cwd"] = normalized_remote_workspace_path
|
|
621
|
+
_invoke_hermes_terminal_tui(
|
|
622
|
+
**native_kwargs,
|
|
623
|
+
)
|
|
624
|
+
elif _should_use_openclaw_native_tui(
|
|
625
|
+
transport=normalized_transport,
|
|
626
|
+
local=local,
|
|
627
|
+
state=next_state,
|
|
628
|
+
latest_access=latest_access,
|
|
629
|
+
):
|
|
630
|
+
_invoke_openclaw_terminal_tui(
|
|
631
|
+
endpoint=endpoint,
|
|
632
|
+
api_key=runtime_api_key,
|
|
633
|
+
session_id=session_id,
|
|
634
|
+
insecure=insecure,
|
|
635
|
+
)
|
|
636
|
+
else:
|
|
637
|
+
api_format = asyncio.run(
|
|
638
|
+
_resolve_remote_api_format(
|
|
639
|
+
endpoint=endpoint,
|
|
640
|
+
api_key=api_key,
|
|
641
|
+
runtime_api_key=runtime_api_key,
|
|
642
|
+
insecure=insecure,
|
|
643
|
+
state=next_state,
|
|
644
|
+
latest_access=latest_access,
|
|
645
|
+
)
|
|
646
|
+
)
|
|
647
|
+
_invoke_tui(
|
|
648
|
+
endpoint,
|
|
649
|
+
runtime_api_key,
|
|
650
|
+
session_id,
|
|
651
|
+
insecure,
|
|
652
|
+
model,
|
|
653
|
+
show_thinking,
|
|
654
|
+
api_format=api_format,
|
|
655
|
+
responses_session_header=(
|
|
656
|
+
"x-openclaw-session-key" if _is_openclaw_target(next_state, latest_access) else None
|
|
657
|
+
),
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _invoke_tui(
|
|
664
|
+
endpoint: str,
|
|
665
|
+
api_key: str = None,
|
|
666
|
+
session_id: str = None,
|
|
667
|
+
insecure: bool = False,
|
|
668
|
+
model: str = None,
|
|
669
|
+
show_thinking: bool = False,
|
|
670
|
+
api_format: str = "chat_completions",
|
|
671
|
+
responses_session_header: str | None = None,
|
|
672
|
+
):
|
|
673
|
+
"""使用 TUI 模式调用"""
|
|
674
|
+
from ksadk.runners.remote_runner import RemoteRunner
|
|
675
|
+
from ksadk.tui import AgentTUI
|
|
676
|
+
|
|
677
|
+
runner = RemoteRunner(
|
|
678
|
+
endpoint=endpoint,
|
|
679
|
+
api_key=api_key,
|
|
680
|
+
session_id=session_id,
|
|
681
|
+
insecure=insecure,
|
|
682
|
+
model=model,
|
|
683
|
+
api_format=api_format,
|
|
684
|
+
responses_session_header=responses_session_header,
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
app = AgentTUI(
|
|
688
|
+
runner=runner,
|
|
689
|
+
show_thinking=show_thinking,
|
|
690
|
+
project_dir=".",
|
|
691
|
+
)
|
|
692
|
+
app.run()
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def _load_state() -> dict:
|
|
696
|
+
"""从 .agentengine.state 加载状态"""
|
|
697
|
+
import yaml
|
|
698
|
+
state_file = Path(".") / ".agentengine.state"
|
|
699
|
+
if state_file.exists():
|
|
700
|
+
try:
|
|
701
|
+
with open(state_file, 'r', encoding='utf-8') as f:
|
|
702
|
+
return yaml.safe_load(f) or {}
|
|
703
|
+
except Exception:
|
|
704
|
+
pass
|
|
705
|
+
return {}
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _save_state(state: dict) -> None:
|
|
709
|
+
"""保存 .agentengine.state。"""
|
|
710
|
+
import yaml
|
|
711
|
+
|
|
712
|
+
state_file = Path(".") / ".agentengine.state"
|
|
713
|
+
payload = dict(state)
|
|
714
|
+
payload["updated_at"] = datetime.now().isoformat()
|
|
715
|
+
with open(state_file, "w", encoding="utf-8") as f:
|
|
716
|
+
yaml.safe_dump(payload, f, allow_unicode=True)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _state_matches_target(state: dict, target_agent: str | None) -> bool:
|
|
720
|
+
if not state or not target_agent:
|
|
721
|
+
return False
|
|
722
|
+
return target_agent == state.get("agent_id") or target_agent == state.get("name")
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def _extract_remote_access(detail: Dict[str, Any]) -> Dict[str, Any]:
|
|
726
|
+
if not isinstance(detail, dict):
|
|
727
|
+
return {}
|
|
728
|
+
|
|
729
|
+
basic = detail.get("basic") if isinstance(detail.get("basic"), dict) else {}
|
|
730
|
+
quick = detail.get("quick_access") if isinstance(detail.get("quick_access"), dict) else {}
|
|
731
|
+
|
|
732
|
+
deployment = detail.get("deployment") if isinstance(detail.get("deployment"), dict) else {}
|
|
733
|
+
framework = (
|
|
734
|
+
deployment.get("framework")
|
|
735
|
+
or basic.get("framework")
|
|
736
|
+
or detail.get("framework")
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
return {
|
|
740
|
+
"agent_id": basic.get("agent_id") or detail.get("agent_id"),
|
|
741
|
+
"name": basic.get("name") or detail.get("name"),
|
|
742
|
+
"endpoint": quick.get("public_endpoint")
|
|
743
|
+
or quick.get("private_endpoint")
|
|
744
|
+
or detail.get("endpoint"),
|
|
745
|
+
"api_key": quick.get("api_key") or detail.get("api_key"),
|
|
746
|
+
"framework": str(framework or "").strip().lower() or None,
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
async def _fetch_remote_access(target_agent: str, region: str) -> Dict[str, Any]:
|
|
751
|
+
from ksadk.api import AgentEngineClient
|
|
752
|
+
|
|
753
|
+
async with AgentEngineClient(region=region) as client:
|
|
754
|
+
try:
|
|
755
|
+
return _extract_remote_access(await client.get_agent(agent_id=target_agent, include_api_key=True))
|
|
756
|
+
except Exception:
|
|
757
|
+
return _extract_remote_access(await client.get_agent(name=target_agent, include_api_key=True))
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _refresh_remote_access(
|
|
761
|
+
*,
|
|
762
|
+
target_agent: str,
|
|
763
|
+
region: str,
|
|
764
|
+
state: dict,
|
|
765
|
+
persist: bool,
|
|
766
|
+
) -> Dict[str, Any]:
|
|
767
|
+
try:
|
|
768
|
+
latest = asyncio.run(_fetch_remote_access(target_agent, region))
|
|
769
|
+
except Exception:
|
|
770
|
+
return {}
|
|
771
|
+
|
|
772
|
+
if persist and latest:
|
|
773
|
+
merged = dict(state)
|
|
774
|
+
for key in ("agent_id", "name", "endpoint", "api_key", "framework"):
|
|
775
|
+
if latest.get(key):
|
|
776
|
+
merged[key] = latest[key]
|
|
777
|
+
if latest.get("framework") == "hermes":
|
|
778
|
+
merged["type"] = "hermes"
|
|
779
|
+
if latest.get("framework") == "openclaw":
|
|
780
|
+
merged["type"] = "openclaw"
|
|
781
|
+
_save_state(merged)
|
|
782
|
+
return latest
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _is_hermes_target(state: dict, latest_access: dict) -> bool:
|
|
786
|
+
framework = str(
|
|
787
|
+
latest_access.get("framework")
|
|
788
|
+
or state.get("framework")
|
|
789
|
+
or state.get("type")
|
|
790
|
+
or ""
|
|
791
|
+
).strip().lower()
|
|
792
|
+
return framework == "hermes"
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def _is_openclaw_target(state: dict, latest_access: dict) -> bool:
|
|
796
|
+
framework = str(
|
|
797
|
+
latest_access.get("framework")
|
|
798
|
+
or state.get("framework")
|
|
799
|
+
or state.get("type")
|
|
800
|
+
or ""
|
|
801
|
+
).strip().lower()
|
|
802
|
+
return framework == "openclaw"
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _mask_secret(secret: str | None) -> str:
|
|
806
|
+
if not secret:
|
|
807
|
+
return ""
|
|
808
|
+
text = str(secret)
|
|
809
|
+
if len(text) <= 4:
|
|
810
|
+
return "****"
|
|
811
|
+
return f"{text[:4]}****"
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def _openclaw_auth_mode(state: dict, latest_access: dict) -> str:
|
|
815
|
+
return str(
|
|
816
|
+
latest_access.get("openclaw_auth_mode")
|
|
817
|
+
or latest_access.get("gateway_auth_mode")
|
|
818
|
+
or state.get("openclaw_auth_mode")
|
|
819
|
+
or state.get("gateway_auth_mode")
|
|
820
|
+
or ""
|
|
821
|
+
).strip().lower()
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _select_runtime_api_key(
|
|
825
|
+
*,
|
|
826
|
+
api_key: str | None,
|
|
827
|
+
openclaw_gateway_token: str | None,
|
|
828
|
+
state: dict,
|
|
829
|
+
latest_access: dict,
|
|
830
|
+
) -> str | None:
|
|
831
|
+
if not _is_openclaw_target(state, latest_access):
|
|
832
|
+
return api_key
|
|
833
|
+
|
|
834
|
+
gateway_token = (
|
|
835
|
+
openclaw_gateway_token
|
|
836
|
+
or os.environ.get("OPENCLAW_GATEWAY_TOKEN")
|
|
837
|
+
or os.environ.get("OPENCLAW_GATEWAY_PASSWORD")
|
|
838
|
+
or latest_access.get("openclaw_gateway_token")
|
|
839
|
+
or latest_access.get("openclaw_gateway_password")
|
|
840
|
+
or state.get("openclaw_gateway_token")
|
|
841
|
+
or state.get("openclaw_gateway_password")
|
|
842
|
+
)
|
|
843
|
+
if gateway_token:
|
|
844
|
+
return str(gateway_token).strip() or None
|
|
845
|
+
|
|
846
|
+
auth_mode = _openclaw_auth_mode(state, latest_access)
|
|
847
|
+
if auth_mode in {"token", "password"}:
|
|
848
|
+
raise click.ClickException(
|
|
849
|
+
"当前 OpenClaw Gateway 为 token/password 模式,agentengine invoke 需要 OpenClaw Gateway token。\n"
|
|
850
|
+
"请使用: agentengine invoke --gateway-token <token>\n"
|
|
851
|
+
"或设置: OPENCLAW_GATEWAY_TOKEN=<token> agentengine invoke\n"
|
|
852
|
+
"如果部署时传过 OPENCLAW_GATEWAY_TOKEN,请重新运行部署让本地 .agentengine.state 记录该 token。\n"
|
|
853
|
+
"注意:这里不是 AgentEngine API Key(ak-*),而是 OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD。"
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
return api_key
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def _select_remote_api_format(state: dict, latest_access: dict) -> str:
|
|
860
|
+
framework = str(
|
|
861
|
+
latest_access.get("framework")
|
|
862
|
+
or state.get("framework")
|
|
863
|
+
or state.get("type")
|
|
864
|
+
or ""
|
|
865
|
+
).strip().lower()
|
|
866
|
+
if framework in {"openclaw", "hermes"}:
|
|
867
|
+
return "responses"
|
|
868
|
+
return "chat_completions"
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
async def _resolve_remote_api_format(
|
|
872
|
+
*,
|
|
873
|
+
endpoint: str,
|
|
874
|
+
api_key: str | None,
|
|
875
|
+
runtime_api_key: str | None = None,
|
|
876
|
+
insecure: bool,
|
|
877
|
+
state: dict,
|
|
878
|
+
latest_access: dict,
|
|
879
|
+
) -> str:
|
|
880
|
+
api_format = _select_remote_api_format(state, latest_access)
|
|
881
|
+
if api_format != "responses" or not _is_openclaw_target(state, latest_access):
|
|
882
|
+
return api_format
|
|
883
|
+
|
|
884
|
+
probe_api_key = runtime_api_key if runtime_api_key is not None else api_key
|
|
885
|
+
if await _probe_openclaw_responses_route(endpoint=endpoint, api_key=probe_api_key, insecure=insecure):
|
|
886
|
+
return api_format
|
|
887
|
+
|
|
888
|
+
raise click.ClickException(
|
|
889
|
+
"当前 OpenClaw endpoint 未暴露 /v1/responses,不能使用 agentengine invoke 的 HTTP TUI。\n"
|
|
890
|
+
"可先使用: agentengine dashboard open\n"
|
|
891
|
+
"如果要命令行交互,请升级/修复 OpenClaw 镜像或 bootstrap,使 Gateway 暴露 OpenResponses API。"
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
async def _probe_openclaw_responses_route(*, endpoint: str, api_key: str | None, insecure: bool) -> bool:
|
|
896
|
+
try:
|
|
897
|
+
import httpx
|
|
898
|
+
except ImportError:
|
|
899
|
+
return False
|
|
900
|
+
|
|
901
|
+
url = f"{endpoint.rstrip('/')}/v1/responses"
|
|
902
|
+
headers = {}
|
|
903
|
+
if api_key:
|
|
904
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
905
|
+
client_kwargs: dict[str, Any] = {"timeout": 5, "trust_env": True}
|
|
906
|
+
if insecure:
|
|
907
|
+
client_kwargs["verify"] = False
|
|
908
|
+
|
|
909
|
+
try:
|
|
910
|
+
async with httpx.AsyncClient(**client_kwargs) as client:
|
|
911
|
+
response = await client.get(url, headers=headers)
|
|
912
|
+
except Exception:
|
|
913
|
+
return False
|
|
914
|
+
|
|
915
|
+
# POST-only API routes usually answer GET with 405/422/400. A 404 or HTML 200
|
|
916
|
+
# means the request fell through to the OpenClaw UI/router instead of the API.
|
|
917
|
+
return response.status_code in {400, 401, 405, 422}
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def _should_use_hermes_native_tui(*, transport: str, local: bool, state: dict, latest_access: dict) -> bool:
|
|
921
|
+
if transport == "chat":
|
|
922
|
+
return False
|
|
923
|
+
if transport == "native":
|
|
924
|
+
return True
|
|
925
|
+
if local:
|
|
926
|
+
return False
|
|
927
|
+
return _is_hermes_target(state, latest_access)
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def _should_use_openclaw_native_tui(*, transport: str, local: bool, state: dict, latest_access: dict) -> bool:
|
|
931
|
+
if transport == "chat":
|
|
932
|
+
return False
|
|
933
|
+
if transport == "native":
|
|
934
|
+
return True
|
|
935
|
+
if local:
|
|
936
|
+
return False
|
|
937
|
+
return _is_openclaw_target(state, latest_access)
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def _invoke_hermes_terminal_tui(
|
|
941
|
+
endpoint: str,
|
|
942
|
+
api_key: str = None,
|
|
943
|
+
session_id: str = None,
|
|
944
|
+
insecure: bool = False,
|
|
945
|
+
cwd: str | None = None,
|
|
946
|
+
):
|
|
947
|
+
click.secho("🖥️ Hermes Native Remote TUI", fg="blue", bold=True)
|
|
948
|
+
click.echo(" 退出: Ctrl-D 或 Ctrl-C")
|
|
949
|
+
click.echo(" 新 Pod 首次启动会加载 Hermes tools/skills,可能需要几十秒到 1 分钟;后续进入会更快。")
|
|
950
|
+
try:
|
|
951
|
+
_warmup_hermes_terminal(
|
|
952
|
+
endpoint=endpoint,
|
|
953
|
+
api_key=api_key,
|
|
954
|
+
session_id=session_id,
|
|
955
|
+
insecure=insecure,
|
|
956
|
+
)
|
|
957
|
+
exit_code = asyncio.run(
|
|
958
|
+
run_hermes_terminal_session(
|
|
959
|
+
endpoint=endpoint,
|
|
960
|
+
api_key=api_key,
|
|
961
|
+
session_id=session_id,
|
|
962
|
+
insecure=insecure,
|
|
963
|
+
mode="tui",
|
|
964
|
+
argv=[],
|
|
965
|
+
cwd=cwd,
|
|
966
|
+
)
|
|
967
|
+
)
|
|
968
|
+
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
969
|
+
raise SystemExit(130)
|
|
970
|
+
except Exception as e:
|
|
971
|
+
click.secho(f"\n❌ Hermes 终端连接失败: {e}", fg="red")
|
|
972
|
+
click.echo(" 浏览器聊天页请改用: agentengine hermes open --chat")
|
|
973
|
+
raise SystemExit(1)
|
|
974
|
+
if exit_code:
|
|
975
|
+
raise SystemExit(exit_code)
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def _warmup_hermes_terminal(
|
|
979
|
+
*,
|
|
980
|
+
endpoint: str,
|
|
981
|
+
api_key: str | None,
|
|
982
|
+
session_id: str | None,
|
|
983
|
+
insecure: bool,
|
|
984
|
+
) -> None:
|
|
985
|
+
try:
|
|
986
|
+
asyncio.run(
|
|
987
|
+
asyncio.wait_for(
|
|
988
|
+
run_hermes_terminal_session(
|
|
989
|
+
endpoint=endpoint,
|
|
990
|
+
api_key=api_key,
|
|
991
|
+
session_id=session_id,
|
|
992
|
+
insecure=insecure,
|
|
993
|
+
mode="exec",
|
|
994
|
+
argv=["status"],
|
|
995
|
+
stdin=io.BytesIO(b""),
|
|
996
|
+
stdout=io.BytesIO(),
|
|
997
|
+
),
|
|
998
|
+
timeout=30,
|
|
999
|
+
)
|
|
1000
|
+
)
|
|
1001
|
+
except Exception as exc:
|
|
1002
|
+
if os.getenv("AGENTENGINE_HERMES_TUI_DEBUG"):
|
|
1003
|
+
click.echo(f" Hermes terminal warmup skipped: {exc}", err=True)
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def _invoke_openclaw_terminal_tui(
|
|
1007
|
+
endpoint: str,
|
|
1008
|
+
api_key: str = None,
|
|
1009
|
+
session_id: str = None,
|
|
1010
|
+
insecure: bool = False,
|
|
1011
|
+
):
|
|
1012
|
+
click.secho("🖥️ OpenClaw Native Remote TUI", fg="blue", bold=True)
|
|
1013
|
+
click.echo(" 退出: Ctrl-D 或 Ctrl-C")
|
|
1014
|
+
try:
|
|
1015
|
+
exit_code = asyncio.run(
|
|
1016
|
+
run_terminal_session(
|
|
1017
|
+
endpoint=endpoint,
|
|
1018
|
+
api_key=api_key,
|
|
1019
|
+
session_id=session_id,
|
|
1020
|
+
insecure=insecure,
|
|
1021
|
+
mode="tui",
|
|
1022
|
+
argv=[],
|
|
1023
|
+
)
|
|
1024
|
+
)
|
|
1025
|
+
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
1026
|
+
raise SystemExit(130)
|
|
1027
|
+
except Exception as e:
|
|
1028
|
+
click.secho(f"\n❌ OpenClaw 终端连接失败: {e}", fg="red")
|
|
1029
|
+
click.echo(" 浏览器聊天页请改用: agentengine dashboard open --path /chat")
|
|
1030
|
+
raise SystemExit(1)
|
|
1031
|
+
if exit_code:
|
|
1032
|
+
raise SystemExit(exit_code)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def _get_api_key() -> Optional[str]:
|
|
1036
|
+
"""兼容旧代码"""
|
|
1037
|
+
return _load_state().get("api_key")
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def _get_endpoint(agent_ref: str, region: str) -> str:
|
|
1041
|
+
"""获取 Agent Endpoint(先按 ID,再按名称)"""
|
|
1042
|
+
from ksadk.api import AgentEngineClient
|
|
1043
|
+
import asyncio
|
|
1044
|
+
|
|
1045
|
+
async def _get():
|
|
1046
|
+
async with AgentEngineClient(region=region) as client:
|
|
1047
|
+
# 1) 优先按 ID 查询
|
|
1048
|
+
try:
|
|
1049
|
+
res = await client.get_agent(agent_id=agent_ref)
|
|
1050
|
+
endpoint = _extract_remote_access(res).get("endpoint", "")
|
|
1051
|
+
if endpoint:
|
|
1052
|
+
return endpoint
|
|
1053
|
+
except Exception:
|
|
1054
|
+
pass
|
|
1055
|
+
|
|
1056
|
+
# 2) 回退按名称查询
|
|
1057
|
+
res = await client.get_agent(name=agent_ref)
|
|
1058
|
+
endpoint = _extract_remote_access(res).get("endpoint", "")
|
|
1059
|
+
if endpoint:
|
|
1060
|
+
return endpoint
|
|
1061
|
+
|
|
1062
|
+
# endpoint 为空时,尽量提取真实 ID 供默认域名拼接
|
|
1063
|
+
basic = res.get("basic", {})
|
|
1064
|
+
return basic.get("agent_id") or res.get("agent_id") or ""
|
|
1065
|
+
|
|
1066
|
+
try:
|
|
1067
|
+
resolved = asyncio.run(_get())
|
|
1068
|
+
if resolved and resolved.startswith("http"):
|
|
1069
|
+
return resolved
|
|
1070
|
+
if resolved:
|
|
1071
|
+
click.secho(f"⚠️ Agent '{agent_ref}' 未返回 Endpoint,尝试默认域名", fg="yellow")
|
|
1072
|
+
return f"https://{resolved}.agent.kspmas.ksyun.com"
|
|
1073
|
+
click.secho(f"⚠️ Agent '{agent_ref}' 未返回 Endpoint,尝试使用默认格式", fg="yellow")
|
|
1074
|
+
return f"https://{agent_ref}.agent.kspmas.ksyun.com"
|
|
1075
|
+
except Exception as e:
|
|
1076
|
+
# 如果是本地开发环境或者连接失败,降级处理
|
|
1077
|
+
click.secho(f"⚠️ 获取 Endpoint 失败: {e},尝试使用默认格式", fg="yellow")
|
|
1078
|
+
return f"https://{agent_ref}.agent.kspmas.ksyun.com"
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
async def _invoke_once(
|
|
1082
|
+
endpoint: str,
|
|
1083
|
+
message: str,
|
|
1084
|
+
api_key: str = None,
|
|
1085
|
+
session_id: str = None,
|
|
1086
|
+
stream: bool = True,
|
|
1087
|
+
insecure: bool = False,
|
|
1088
|
+
model: str = None,
|
|
1089
|
+
api_format: str = "chat_completions",
|
|
1090
|
+
):
|
|
1091
|
+
"""单次调用"""
|
|
1092
|
+
click.echo(f"\n👤 你: {message}")
|
|
1093
|
+
click.echo(f"🤖 Agent: ", nl=False)
|
|
1094
|
+
|
|
1095
|
+
try:
|
|
1096
|
+
if stream:
|
|
1097
|
+
full_response = ""
|
|
1098
|
+
if Live and Markdown:
|
|
1099
|
+
# 降低刷新率减少闪烁,vertical_overflow="visible"防止回滚丢失
|
|
1100
|
+
# 手动控制刷新以减少闪烁
|
|
1101
|
+
with Live(Markdown("", justify="left"), console=console, auto_refresh=False, vertical_overflow="visible") as live:
|
|
1102
|
+
last_refresh_time = 0
|
|
1103
|
+
full_reasoning = ""
|
|
1104
|
+
async for chunk in _stream_chat(endpoint, message, api_key, session_id, True, insecure, model, api_format):
|
|
1105
|
+
content, reasoning = _extract_content(chunk)
|
|
1106
|
+
|
|
1107
|
+
updated = False
|
|
1108
|
+
if reasoning:
|
|
1109
|
+
full_reasoning += reasoning
|
|
1110
|
+
updated = True
|
|
1111
|
+
if content:
|
|
1112
|
+
full_response += content
|
|
1113
|
+
updated = True
|
|
1114
|
+
|
|
1115
|
+
if updated:
|
|
1116
|
+
# 构造显示文本
|
|
1117
|
+
display_text = ""
|
|
1118
|
+
if full_reasoning:
|
|
1119
|
+
formatted_reasoning = full_reasoning.replace('\n', '\n> ')
|
|
1120
|
+
display_text += f"> 🧠 **Thinking:**\n> {formatted_reasoning}\n\n"
|
|
1121
|
+
display_text += full_response
|
|
1122
|
+
|
|
1123
|
+
live.update(Markdown(display_text, justify="left"))
|
|
1124
|
+
|
|
1125
|
+
# 基于时间限流刷新 (每0.2秒一次 = 5 FPS)
|
|
1126
|
+
now = time.time()
|
|
1127
|
+
if now - last_refresh_time > 0.2:
|
|
1128
|
+
live.refresh()
|
|
1129
|
+
last_refresh_time = now
|
|
1130
|
+
live.refresh() # 确保最后一次刷新
|
|
1131
|
+
else:
|
|
1132
|
+
async for chunk in _stream_chat(endpoint, message, api_key, session_id, True, insecure, model, api_format):
|
|
1133
|
+
content, reasoning = _extract_content(chunk)
|
|
1134
|
+
if reasoning:
|
|
1135
|
+
click.secho(reasoning, fg="bright_black", nl=False)
|
|
1136
|
+
if content:
|
|
1137
|
+
print(content, end="", flush=True)
|
|
1138
|
+
click.echo() # 换行
|
|
1139
|
+
else:
|
|
1140
|
+
response = await _chat(endpoint, message, api_key, session_id, insecure, model, api_format)
|
|
1141
|
+
content = _extract_response_content(response)
|
|
1142
|
+
if console and Markdown:
|
|
1143
|
+
console.print(Markdown(content))
|
|
1144
|
+
else:
|
|
1145
|
+
click.echo(content)
|
|
1146
|
+
except Exception as e:
|
|
1147
|
+
click.secho(f"\n❌ 调用失败: {e}", fg="red")
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
async def _chat(
|
|
1152
|
+
endpoint: str,
|
|
1153
|
+
message: str,
|
|
1154
|
+
api_key: str = None,
|
|
1155
|
+
session_id: str = None,
|
|
1156
|
+
insecure: bool = False,
|
|
1157
|
+
model: str = None,
|
|
1158
|
+
api_format: str = "chat_completions",
|
|
1159
|
+
) -> dict:
|
|
1160
|
+
"""非流式调用 (OpenAI 兼容格式)"""
|
|
1161
|
+
try:
|
|
1162
|
+
import httpx
|
|
1163
|
+
except ImportError:
|
|
1164
|
+
click.secho("❌ 请安装 httpx: pip install httpx", fg="red")
|
|
1165
|
+
raise SystemExit(1)
|
|
1166
|
+
|
|
1167
|
+
normalized_api_format = str(api_format or "chat_completions").strip().lower()
|
|
1168
|
+
if normalized_api_format == "responses":
|
|
1169
|
+
url = f"{endpoint.rstrip('/')}/v1/responses"
|
|
1170
|
+
payload = {"input": [{"role": "user", "content": message}], "stream": False}
|
|
1171
|
+
else:
|
|
1172
|
+
url = f"{endpoint.rstrip('/')}/v1/chat/completions"
|
|
1173
|
+
payload = {"messages": [{"role": "user", "content": message}], "stream": False}
|
|
1174
|
+
|
|
1175
|
+
if session_id:
|
|
1176
|
+
payload["session_id"] = session_id
|
|
1177
|
+
|
|
1178
|
+
if model:
|
|
1179
|
+
payload["model"] = model
|
|
1180
|
+
|
|
1181
|
+
# 本地请求禁用系统代理 (ClashX 等会导致本地请求 502 错误)
|
|
1182
|
+
# trust_env=False 会禁用: 代理设置、SSL 证书环境变量、.netrc 文件
|
|
1183
|
+
# 对本地请求通常无影响,因为不需要这些配置
|
|
1184
|
+
is_local = "localhost" in url or "127.0.0.1" in url or "0.0.0.0" in url
|
|
1185
|
+
|
|
1186
|
+
# 构造 httpx client 配置
|
|
1187
|
+
client_kwargs = {"timeout": 60, "trust_env": not is_local}
|
|
1188
|
+
|
|
1189
|
+
# 如果指定了 --insecure 参数,跳过 SSL 证书验证(类似 curl -k)
|
|
1190
|
+
if insecure:
|
|
1191
|
+
client_kwargs["verify"] = False
|
|
1192
|
+
|
|
1193
|
+
# 构造 Headers
|
|
1194
|
+
headers = {}
|
|
1195
|
+
if api_key:
|
|
1196
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
1197
|
+
|
|
1198
|
+
async with httpx.AsyncClient(**client_kwargs) as client:
|
|
1199
|
+
response = await client.post(url, json=payload, headers=headers)
|
|
1200
|
+
response.raise_for_status()
|
|
1201
|
+
return response.json()
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
async def _stream_chat(
|
|
1205
|
+
endpoint: str,
|
|
1206
|
+
message: str,
|
|
1207
|
+
api_key: str = None,
|
|
1208
|
+
session_id: str = None,
|
|
1209
|
+
is_once: bool = False,
|
|
1210
|
+
insecure: bool = False,
|
|
1211
|
+
model: str = None,
|
|
1212
|
+
api_format: str = "chat_completions",
|
|
1213
|
+
):
|
|
1214
|
+
"""流式调用 (SSE)"""
|
|
1215
|
+
try:
|
|
1216
|
+
import httpx
|
|
1217
|
+
except ImportError:
|
|
1218
|
+
click.secho("❌ 请安装 httpx: pip install httpx", fg="red")
|
|
1219
|
+
raise SystemExit(1)
|
|
1220
|
+
|
|
1221
|
+
normalized_api_format = str(api_format or "chat_completions").strip().lower()
|
|
1222
|
+
if normalized_api_format == "responses":
|
|
1223
|
+
url = f"{endpoint.rstrip('/')}/v1/responses"
|
|
1224
|
+
payload = {"input": [{"role": "user", "content": message}], "stream": True}
|
|
1225
|
+
else:
|
|
1226
|
+
url = f"{endpoint.rstrip('/')}/v1/chat/completions"
|
|
1227
|
+
payload = {"messages": [{"role": "user", "content": message}], "stream": True}
|
|
1228
|
+
|
|
1229
|
+
if session_id:
|
|
1230
|
+
payload["session_id"] = session_id
|
|
1231
|
+
|
|
1232
|
+
if model:
|
|
1233
|
+
payload["model"] = model
|
|
1234
|
+
|
|
1235
|
+
# 本地请求禁用系统代理 (ClashX 等会导致本地请求 502 错误)
|
|
1236
|
+
# trust_env=False 会禁用: 代理设置、SSL 证书环境变量、.netrc 文件
|
|
1237
|
+
# 对本地请求通常无影响,因为不需要这些配置
|
|
1238
|
+
is_local = "localhost" in url or "127.0.0.1" in url or "0.0.0.0" in url
|
|
1239
|
+
|
|
1240
|
+
# 构造 httpx client 配置
|
|
1241
|
+
client_kwargs = {"timeout": 60, "trust_env": not is_local}
|
|
1242
|
+
|
|
1243
|
+
# 如果指定了 --insecure 参数,跳过 SSL 证书验证(类似 curl -k)
|
|
1244
|
+
if insecure:
|
|
1245
|
+
client_kwargs["verify"] = False
|
|
1246
|
+
|
|
1247
|
+
# 构造 Headers
|
|
1248
|
+
headers = {}
|
|
1249
|
+
if api_key:
|
|
1250
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
1251
|
+
|
|
1252
|
+
async with httpx.AsyncClient(**client_kwargs) as client:
|
|
1253
|
+
async with client.stream("POST", url, json=payload, headers=headers) as response:
|
|
1254
|
+
response.raise_for_status()
|
|
1255
|
+
try:
|
|
1256
|
+
current_event = ""
|
|
1257
|
+
# Use aiter_lines() for robust UTF-8 decoding and line splitting
|
|
1258
|
+
async for line in response.aiter_lines():
|
|
1259
|
+
if not line:
|
|
1260
|
+
continue
|
|
1261
|
+
|
|
1262
|
+
if line.startswith("event: "):
|
|
1263
|
+
current_event = line[7:].strip()
|
|
1264
|
+
continue
|
|
1265
|
+
|
|
1266
|
+
if line.startswith("data: "):
|
|
1267
|
+
data_str = line[6:].strip()
|
|
1268
|
+
if data_str == "[DONE]":
|
|
1269
|
+
break
|
|
1270
|
+
|
|
1271
|
+
try:
|
|
1272
|
+
data = json.loads(data_str)
|
|
1273
|
+
if current_event and isinstance(data, dict):
|
|
1274
|
+
data = {**data, "_event": current_event}
|
|
1275
|
+
# 直接 yield 解析后的 JSON 数据,让 _extract_content 处理
|
|
1276
|
+
yield data
|
|
1277
|
+
|
|
1278
|
+
# Handle events/errors
|
|
1279
|
+
error = data.get("error")
|
|
1280
|
+
if error:
|
|
1281
|
+
click.secho(f"\nError: {error}", fg="red")
|
|
1282
|
+
|
|
1283
|
+
except json.JSONDecodeError:
|
|
1284
|
+
pass
|
|
1285
|
+
except Exception as e:
|
|
1286
|
+
click.secho(f"\nStream error: {e}", fg="red")
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
def _extract_content(chunk: dict) -> tuple[str, str]:
|
|
1290
|
+
"""从 OpenAI 流式响应中提取内容 (包含 reasoning_content)"""
|
|
1291
|
+
event_name = str(chunk.get("_event") or "")
|
|
1292
|
+
if event_name == "response.output_text.delta":
|
|
1293
|
+
return str(chunk.get("delta") or ""), ""
|
|
1294
|
+
if event_name == "response.reasoning.delta":
|
|
1295
|
+
return "", str(chunk.get("delta") or "")
|
|
1296
|
+
if event_name == "response.completed":
|
|
1297
|
+
return "", ""
|
|
1298
|
+
|
|
1299
|
+
# OpenAI 格式: {"choices": [{"delta": {"content": "xxx", "reasoning_content": "thought"}}]}
|
|
1300
|
+
try:
|
|
1301
|
+
choices = chunk.get("choices", [])
|
|
1302
|
+
if choices:
|
|
1303
|
+
delta = choices[0].get("delta", {})
|
|
1304
|
+
return delta.get("content", "") or "", delta.get("reasoning_content", "") or ""
|
|
1305
|
+
except (KeyError, IndexError):
|
|
1306
|
+
pass
|
|
1307
|
+
|
|
1308
|
+
if isinstance(chunk.get("output_text"), str):
|
|
1309
|
+
return chunk["output_text"], ""
|
|
1310
|
+
if isinstance(chunk.get("delta"), str):
|
|
1311
|
+
return chunk["delta"], ""
|
|
1312
|
+
return "", ""
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def _extract_response_content(response: dict) -> str:
|
|
1316
|
+
"""从 OpenAI 非流式响应中提取内容"""
|
|
1317
|
+
output_text = response.get("output_text")
|
|
1318
|
+
if output_text:
|
|
1319
|
+
return str(output_text)
|
|
1320
|
+
output = response.get("output") or []
|
|
1321
|
+
for item in output:
|
|
1322
|
+
if not isinstance(item, dict):
|
|
1323
|
+
continue
|
|
1324
|
+
for content in item.get("content") or []:
|
|
1325
|
+
if isinstance(content, dict) and content.get("text"):
|
|
1326
|
+
return str(content["text"])
|
|
1327
|
+
|
|
1328
|
+
# OpenAI 格式: {"choices": [{"message": {"content": "xxx"}}]}
|
|
1329
|
+
try:
|
|
1330
|
+
choices = response.get("choices", [])
|
|
1331
|
+
if choices:
|
|
1332
|
+
message = choices[0].get("message", {})
|
|
1333
|
+
return message.get("content", "")
|
|
1334
|
+
except (KeyError, IndexError):
|
|
1335
|
+
pass
|
|
1336
|
+
return str(response)
|