agentengine-sdk-python 0.6.4__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentengine_sdk_python-0.7.0.dist-info/METADATA +181 -0
- agentengine_sdk_python-0.7.0.dist-info/RECORD +482 -0
- {agentengine_sdk_python-0.6.4.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +1 -1
- agentengine_sdk_python-0.7.0.dist-info/entry_points.txt +3 -0
- agentengine_sdk_python-0.7.0.dist-info/licenses/LICENSE +175 -0
- agentengine_sdk_python-0.7.0.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 +2246 -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 +2304 -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 +1263 -0
- ksadk/cli/cmd_invoke.py +1503 -0
- ksadk/cli/cmd_launch.py +564 -0
- ksadk/cli/cmd_mcp.py +1179 -0
- ksadk/cli/cmd_model.py +302 -0
- ksadk/cli/cmd_openclaw.py +3922 -0
- ksadk/cli/cmd_run.py +239 -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 +412 -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 +343 -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/message_projection.py +269 -0
- ksadk/conversations/model_context.py +370 -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 +5276 -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 +1115 -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/resilient_session_service.py +268 -0
- ksadk/memory/adk/short_term_memory.py +304 -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 +1994 -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 +751 -0
- ksadk/runners/langgraph_runner.py +985 -0
- ksadk/runners/patch_langchain.py +187 -0
- ksadk/runners/remote_runner.py +792 -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 +3931 -0
- ksadk/server/static/assets/ArtifactsPanel-D-SSn2ee.js +1 -0
- ksadk/server/static/assets/CodeBlock-P16VOLYi.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-CdGiQWHR.js +8 -0
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
- ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
- ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.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-BA13sHc9.js +1 -0
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-DzJzgVoL.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-UTdVcZhn.js +132 -0
- ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dix0GD4m.js +10 -0
- ksadk/server/static/assets/channel-CTuydARX.js +1 -0
- ksadk/server/static/assets/chunk-2J33WTMH-C2JxuZ42.js +1 -0
- ksadk/server/static/assets/chunk-4BX2VUAB-Be57QPgD.js +1 -0
- ksadk/server/static/assets/chunk-55IACEB6-WjSuy4nv.js +1 -0
- ksadk/server/static/assets/chunk-727SXJPM-BOovP-b5.js +206 -0
- ksadk/server/static/assets/chunk-AQP2D5EJ-DOmZNmWj.js +231 -0
- ksadk/server/static/assets/chunk-FMBD7UC4-DzwlodkU.js +15 -0
- ksadk/server/static/assets/chunk-ND2GUHAM-DMqAikEU.js +1 -0
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.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-Zhy2GBnP.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-CbKrtIUz.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-BRApgQRK.js +43 -0
- ksadk/server/static/assets/diagram-5GNKFQAL-D0xbnDiK.js +10 -0
- ksadk/server/static/assets/diagram-KO2AKTUF-CVugVI9L.js +3 -0
- ksadk/server/static/assets/diagram-LMA3HP47-DozUGgee.js +24 -0
- ksadk/server/static/assets/diagram-OG6HWLK6-ZOYsKlru.js +24 -0
- ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
- ksadk/server/static/assets/dist-2hInLS_J.js +9 -0
- ksadk/server/static/assets/dist-8EEyGgDx.js +1 -0
- ksadk/server/static/assets/dist-8_SfK1IP.js +1 -0
- ksadk/server/static/assets/dist-B0NP_Z0z.js +23 -0
- ksadk/server/static/assets/dist-BIJVDMuB.js +1 -0
- ksadk/server/static/assets/dist-BmlMPsCc.js +2 -0
- ksadk/server/static/assets/dist-BrCcQ47T.js +1 -0
- ksadk/server/static/assets/dist-BssVmjIo.js +6 -0
- ksadk/server/static/assets/dist-C3HIIaN5.js +1 -0
- ksadk/server/static/assets/dist-CPBtqRTW.js +1 -0
- ksadk/server/static/assets/dist-CkEKJFtk.js +1 -0
- ksadk/server/static/assets/dist-D-JMdWPQ.js +1 -0
- ksadk/server/static/assets/dist-D-xOD048.js +1 -0
- ksadk/server/static/assets/dist-D57wnGWd.js +1 -0
- ksadk/server/static/assets/dist-DL_NKc_z.js +1 -0
- ksadk/server/static/assets/dist-DMezvBGJ.js +1 -0
- ksadk/server/static/assets/dist-DmFaq_Iy.js +1 -0
- ksadk/server/static/assets/dist-Dp0k7bWI.js +1 -0
- ksadk/server/static/assets/dist-DpKtt4Bo.js +1 -0
- ksadk/server/static/assets/dist-DyOkZmze.js +11 -0
- ksadk/server/static/assets/dist-DyWsb78Q.js +1 -0
- ksadk/server/static/assets/dist-gX6GOLKq.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-Bk-9bgE5.js +85 -0
- ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.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-Dn9kZVuZ.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-BGxXlJqm.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-BIao9Yjg.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-C7Q0X-wi.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-C00Lwg3L.js +165 -0
- ksadk/server/static/assets/index-DAURbCsP.css +1 -0
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
- ksadk/server/static/assets/infoDiagram-5YYISTIA-BO_CSqq5.js +2 -0
- ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
- ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-eYLPLd46.js +70 -0
- ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
- ksadk/server/static/assets/journeyDiagram-JHISSGLW-De9geSb0.js +139 -0
- ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
- ksadk/server/static/assets/kanban-definition-UN3LZRKU-Cs80kQto.js +89 -0
- ksadk/server/static/assets/katex-DSrCFD8n.js +265 -0
- ksadk/server/static/assets/linear-CN9sbKTF.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-DlgSBU8l.js +161 -0
- ksadk/server/static/assets/mindmap-definition-RKZ34NQL-9afz7Oug.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-CGkyRxlg.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-M0uWAf4n.js +1 -0
- ksadk/server/static/assets/pieDiagram-4H26LBE5-DPv_VstN.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-o77wZ1XK.js +7 -0
- ksadk/server/static/assets/r-DADKenSm.js +1 -0
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-BYVw81VC.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-B7nZFMKT.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-BGRRrFw5.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-BUfHCoPs.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.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-FbCTGrf9.js +120 -0
- ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.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-DWgwtPsJ.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-BEQz7wyy.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-2x8G-tTM.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-Drma3AYl.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 +279 -0
- ksadk/sessions/base.py +354 -0
- ksadk/sessions/continuity.py +337 -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 +901 -0
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +360 -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 +854 -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 +917 -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 +892 -0
- ksadk/tracing/span_utils.py +114 -0
- ksadk/tui/__init__.py +27 -0
- ksadk/tui/loop.py +1877 -0
- ksadk/tui/stream_render.py +68 -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/__init__.py +0 -3
- agentengine_sdk_python-0.6.4.dist-info/METADATA +0 -48
- agentengine_sdk_python-0.6.4.dist-info/RECORD +0 -5
- agentengine_sdk_python-0.6.4.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,1115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serverless Provider - 金山云 Serverless 计算引擎 (AgentEngine 托管)
|
|
3
|
+
|
|
4
|
+
架构:
|
|
5
|
+
- Build 阶段: 客户端使用本地 AK/SK 直接上传代码包到 KS3
|
|
6
|
+
- Deploy 阶段: 客户端调用 AgentEngine Server API 发起部署
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import shutil
|
|
13
|
+
import time
|
|
14
|
+
import yaml
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Dict, Any, List, Optional
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
from ksadk.builders.ks3_uploader import KS3Uploader
|
|
20
|
+
from ksadk.builders.code_builder import CodeBuilder
|
|
21
|
+
from ksadk.deployment.agent_access import get_latest_agent_access
|
|
22
|
+
from ksadk.deployment.base import (
|
|
23
|
+
BaseDeployProvider,
|
|
24
|
+
DeployTarget,
|
|
25
|
+
DeployResult,
|
|
26
|
+
DeployStatus,
|
|
27
|
+
PackageInfo,
|
|
28
|
+
)
|
|
29
|
+
from ksadk.deployment.registry import DeployProviderRegistry
|
|
30
|
+
from ksadk.deployment.ui_config import resolve_ui_config, ui_config_to_state_fields
|
|
31
|
+
from ksadk.builders.container_builder import (
|
|
32
|
+
ContainerBuilder,
|
|
33
|
+
registry_kind_label,
|
|
34
|
+
resolve_registry_credentials,
|
|
35
|
+
)
|
|
36
|
+
from ksadk.configs.settings import DEFAULT_RUNTIME_TIMEZONE
|
|
37
|
+
from ksadk.configs.env_registry import ENV_VAR_REGISTRY
|
|
38
|
+
from ksadk.configs.global_config import get_env_from_global_config
|
|
39
|
+
from ksadk.api import AgentEngineClient, DryRunExit
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_DEPLOY_PROCESS_ENV_ALLOWLIST = frozenset(
|
|
46
|
+
{
|
|
47
|
+
spec.name
|
|
48
|
+
for spec in ENV_VAR_REGISTRY
|
|
49
|
+
if spec.module
|
|
50
|
+
not in {
|
|
51
|
+
"builders",
|
|
52
|
+
"cli",
|
|
53
|
+
"configs",
|
|
54
|
+
"web",
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
) | frozenset(
|
|
58
|
+
{
|
|
59
|
+
"E2B_API_KEY",
|
|
60
|
+
"E2B_API_URL",
|
|
61
|
+
"OPENAI_API_BASE",
|
|
62
|
+
"OPENAI_API_KEY",
|
|
63
|
+
"OPENAI_BASE_URL",
|
|
64
|
+
"OPENAI_MODEL_NAME",
|
|
65
|
+
"SKILL_SPACE_ID",
|
|
66
|
+
"KSYUN_ACCESS_KEY",
|
|
67
|
+
"KSYUN_ACCOUNT_ID",
|
|
68
|
+
"KSYUN_REGION",
|
|
69
|
+
"KSYUN_SECRET_KEY",
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
_DEPLOY_PROCESS_ENV_PREFIXES = ("KSADK_", "OPENAI_", "KSYUN_", "E2B_")
|
|
73
|
+
_DEPLOY_PROCESS_ENV_DENYLIST = frozenset(
|
|
74
|
+
{
|
|
75
|
+
spec.name
|
|
76
|
+
for spec in ENV_VAR_REGISTRY
|
|
77
|
+
if spec.module in {"builders", "cli", "configs", "web"}
|
|
78
|
+
}
|
|
79
|
+
) | frozenset(
|
|
80
|
+
{
|
|
81
|
+
"KSADK_GLOBAL_CONFIG_ENV_KEYS",
|
|
82
|
+
"KSADK_UPDATED_AT",
|
|
83
|
+
"KSADK_VERSION",
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _should_forward_process_env(name: str) -> bool:
|
|
89
|
+
if name in _DEPLOY_PROCESS_ENV_DENYLIST:
|
|
90
|
+
return False
|
|
91
|
+
return name in _DEPLOY_PROCESS_ENV_ALLOWLIST or name.startswith(_DEPLOY_PROCESS_ENV_PREFIXES)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@DeployProviderRegistry.register("serverless")
|
|
95
|
+
@DeployProviderRegistry.register("kcf")
|
|
96
|
+
@DeployProviderRegistry.register("kce")
|
|
97
|
+
class ServerlessProvider(BaseDeployProvider):
|
|
98
|
+
"""金山云 Serverless 计算引擎 (AgentEngine Server 托管)
|
|
99
|
+
|
|
100
|
+
重构后直接继承 BaseDeployProvider,不再依赖 DockerProvider。
|
|
101
|
+
Container 模式使用 ContainerBuilder 进行打包和构建。
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
name = "serverless"
|
|
105
|
+
display_name = "AgentEngine Serverless (Managed)"
|
|
106
|
+
description = "部署到金山云 Serverless (via AgentEngine Server)"
|
|
107
|
+
|
|
108
|
+
supports_streaming = True
|
|
109
|
+
supports_scaling = True
|
|
110
|
+
requires_image_registry = False
|
|
111
|
+
|
|
112
|
+
def __init__(self, config: Dict[str, Any] = None):
|
|
113
|
+
self.config = config or {}
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _image_credential_from_env(image_ref: str) -> Optional[Dict[str, str]]:
|
|
117
|
+
username, password, registry_kind = resolve_registry_credentials(image_ref)
|
|
118
|
+
if username and password:
|
|
119
|
+
return {
|
|
120
|
+
"endpoint": image_ref.split("/", 1)[0],
|
|
121
|
+
"username": username,
|
|
122
|
+
"password": password,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if password and not username and registry_kind != "personal_kcr":
|
|
126
|
+
click.secho(
|
|
127
|
+
f" ⚠️ 检测到 KCR_PASSWORD 但缺少 KCR_USERNAME,已忽略{registry_kind_label(registry_kind)}镜像凭证;"
|
|
128
|
+
"企业版 KCR 和第三方镜像仓库必须配置 KCR_USERNAME + KCR_PASSWORD",
|
|
129
|
+
fg="yellow",
|
|
130
|
+
)
|
|
131
|
+
elif registry_kind == "personal_kcr":
|
|
132
|
+
click.secho(
|
|
133
|
+
" ⚠️ 未配置个人版 KCR 镜像凭证 (KSYUN_ACCOUNT_ID/KCR_PASSWORD),私有镜像可能无法拉取",
|
|
134
|
+
fg="yellow",
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
click.secho(
|
|
138
|
+
f" ⚠️ 未配置{registry_kind_label(registry_kind)}镜像凭证 "
|
|
139
|
+
"(KCR_USERNAME/KCR_PASSWORD),私有镜像可能无法拉取",
|
|
140
|
+
fg="yellow",
|
|
141
|
+
)
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _parse_code_artifact_path(artifact_path: str) -> tuple[Optional[str], Optional[str]]:
|
|
146
|
+
"""解析代码包路径,返回 (bucket, object_key)。"""
|
|
147
|
+
if not artifact_path:
|
|
148
|
+
return None, None
|
|
149
|
+
|
|
150
|
+
path = artifact_path.strip()
|
|
151
|
+
if not path:
|
|
152
|
+
return None, None
|
|
153
|
+
|
|
154
|
+
if path.startswith("ks3://"):
|
|
155
|
+
raw = path[6:]
|
|
156
|
+
parts = raw.split("/", 1)
|
|
157
|
+
bucket = parts[0].strip() if parts else ""
|
|
158
|
+
object_key = parts[1].strip() if len(parts) > 1 else ""
|
|
159
|
+
return bucket or None, object_key or None
|
|
160
|
+
|
|
161
|
+
if path.startswith("http://") or path.startswith("https://"):
|
|
162
|
+
from urllib.parse import urlparse
|
|
163
|
+
|
|
164
|
+
parsed = urlparse(path)
|
|
165
|
+
bucket = (parsed.netloc.split(".", 1)[0] if parsed.netloc else "").strip()
|
|
166
|
+
object_key = parsed.path.lstrip("/").strip()
|
|
167
|
+
return bucket or None, object_key or None
|
|
168
|
+
|
|
169
|
+
if "/" in path:
|
|
170
|
+
bucket, object_key = path.split("/", 1)
|
|
171
|
+
return bucket.strip() or None, object_key.strip() or None
|
|
172
|
+
|
|
173
|
+
return None, None
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def _load_project_env_vars(env_file: Path) -> Dict[str, str]:
|
|
177
|
+
"""读取项目 .env,并修正 UTF-8 BOM 导致的脏 key。"""
|
|
178
|
+
from dotenv import dotenv_values
|
|
179
|
+
|
|
180
|
+
raw_env = dotenv_values(env_file, encoding="utf-8-sig")
|
|
181
|
+
env_vars: Dict[str, str] = {}
|
|
182
|
+
for key, value in raw_env.items():
|
|
183
|
+
if not key or value is None:
|
|
184
|
+
continue
|
|
185
|
+
clean_key = str(key).lstrip("\ufeff").strip()
|
|
186
|
+
if not clean_key:
|
|
187
|
+
continue
|
|
188
|
+
env_vars[clean_key] = str(value)
|
|
189
|
+
return env_vars
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def _load_deploy_env_vars(
|
|
193
|
+
cls,
|
|
194
|
+
project_dir: str | Path,
|
|
195
|
+
explicit_env_vars: Optional[Dict[str, str]] = None,
|
|
196
|
+
) -> tuple[Dict[str, str], bool, int]:
|
|
197
|
+
"""读取部署时注入到托管运行时的环境变量。
|
|
198
|
+
|
|
199
|
+
全局配置作为兜底,项目 .env 作为项目级覆盖;真实 .env 文件不会随
|
|
200
|
+
Code/Container 制品打包,只通过 deploy payload 注入到 Pod 环境变量。
|
|
201
|
+
"""
|
|
202
|
+
env_vars: Dict[str, str] = dict(get_env_from_global_config())
|
|
203
|
+
env_file = Path(project_dir) / ".env"
|
|
204
|
+
project_env_count = 0
|
|
205
|
+
for key, value in sorted(os.environ.items()):
|
|
206
|
+
if value and _should_forward_process_env(key):
|
|
207
|
+
env_vars.setdefault(key, value)
|
|
208
|
+
if env_file.exists():
|
|
209
|
+
project_env = cls._load_project_env_vars(env_file)
|
|
210
|
+
project_env_count = len(project_env)
|
|
211
|
+
env_vars.update(project_env)
|
|
212
|
+
env_vars.update(explicit_env_vars or {})
|
|
213
|
+
env_vars.setdefault("TZ", DEFAULT_RUNTIME_TIMEZONE)
|
|
214
|
+
return env_vars, env_file.exists(), project_env_count
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
def _inject_ui_runtime_env(
|
|
218
|
+
env_vars: Dict[str, str],
|
|
219
|
+
ui_state: Dict[str, Any],
|
|
220
|
+
local_state: Dict[str, Any],
|
|
221
|
+
) -> Dict[str, str]:
|
|
222
|
+
"""Expose resolved UI config to the runtime pod without packaging local state."""
|
|
223
|
+
|
|
224
|
+
profile = ui_state.get("ui_profile")
|
|
225
|
+
path = ui_state.get("ui_path")
|
|
226
|
+
url = ui_state.get("ui_url")
|
|
227
|
+
bundle_path = (
|
|
228
|
+
ui_state.get("ui_bundle_path")
|
|
229
|
+
or ui_state.get("ui_bundle_dir")
|
|
230
|
+
or local_state.get("ui_bundle_path")
|
|
231
|
+
or local_state.get("ui_bundle_dir")
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if profile:
|
|
235
|
+
env_vars["KSADK_UI_PROFILE"] = str(profile)
|
|
236
|
+
if path:
|
|
237
|
+
env_vars["KSADK_UI_PATH"] = str(path)
|
|
238
|
+
if url:
|
|
239
|
+
env_vars["KSADK_UI_URL"] = str(url)
|
|
240
|
+
if bundle_path:
|
|
241
|
+
env_vars["KSADK_UI_BUNDLE_PATH"] = str(bundle_path)
|
|
242
|
+
return env_vars
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def _persist_build_metadata(package_info: PackageInfo) -> None:
|
|
246
|
+
"""持久化最近一次成功构建的制品信息,供后续命中缓存。"""
|
|
247
|
+
metadata_file = Path(package_info.project_dir) / ".agentengine" / "build-metadata.json"
|
|
248
|
+
metadata_file.parent.mkdir(parents=True, exist_ok=True)
|
|
249
|
+
|
|
250
|
+
payload = {
|
|
251
|
+
"name": package_info.name,
|
|
252
|
+
"framework": package_info.framework,
|
|
253
|
+
"build_dir": package_info.build_dir,
|
|
254
|
+
"project_dir": package_info.project_dir,
|
|
255
|
+
"entry_point": package_info.entry_point,
|
|
256
|
+
"image": package_info.image,
|
|
257
|
+
"metadata": package_info.metadata,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
with open(metadata_file, "w", encoding="utf-8") as f:
|
|
261
|
+
json.dump(payload, f, indent=2, ensure_ascii=False)
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def _serialize_network_config(
|
|
265
|
+
target: DeployTarget, *, is_update: bool = False
|
|
266
|
+
) -> Optional[Dict[str, Any]]:
|
|
267
|
+
network = getattr(target, "network", None)
|
|
268
|
+
if network is None:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
public_access = getattr(network, "enable_public_access", None)
|
|
272
|
+
# 三态:None=未指定。create 默认开公网;update 未指定则不发 network 字段(保留服务端现有配置)。
|
|
273
|
+
if public_access is None:
|
|
274
|
+
public_value: Optional[bool] = None if is_update else True
|
|
275
|
+
else:
|
|
276
|
+
public_value = bool(public_access)
|
|
277
|
+
|
|
278
|
+
payload: Dict[str, Any] = {}
|
|
279
|
+
if public_value is not None:
|
|
280
|
+
payload["enable_public_access"] = public_value
|
|
281
|
+
if bool(getattr(network, "enable_vpc_access", False)):
|
|
282
|
+
payload["enable_vpc_access"] = True
|
|
283
|
+
|
|
284
|
+
for field in ("vpc_id", "subnet_id", "security_group_id", "availability_zone"):
|
|
285
|
+
value = str(getattr(network, field, "") or "").strip()
|
|
286
|
+
if value:
|
|
287
|
+
payload[field] = value
|
|
288
|
+
|
|
289
|
+
if not payload:
|
|
290
|
+
return None
|
|
291
|
+
return payload
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def _serialize_storage_config(target: DeployTarget) -> Optional[Dict[str, Any]]:
|
|
295
|
+
storage = getattr(target, "storage", None)
|
|
296
|
+
if storage is None:
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
mount_path = str(getattr(storage, "mount_path", "") or "").strip()
|
|
300
|
+
size_gi = getattr(storage, "size_gi", None)
|
|
301
|
+
if not mount_path and size_gi is None:
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
payload: Dict[str, Any] = {}
|
|
305
|
+
if mount_path:
|
|
306
|
+
payload["mount_path"] = mount_path
|
|
307
|
+
if size_gi is not None:
|
|
308
|
+
payload["size_gi"] = int(size_gi)
|
|
309
|
+
return payload
|
|
310
|
+
|
|
311
|
+
@staticmethod
|
|
312
|
+
def _format_elapsed(started_at: float) -> str:
|
|
313
|
+
elapsed = max(0.0, time.monotonic() - started_at)
|
|
314
|
+
if elapsed < 60:
|
|
315
|
+
return f"{elapsed:.1f}s"
|
|
316
|
+
minutes = int(elapsed // 60)
|
|
317
|
+
seconds = int(elapsed % 60)
|
|
318
|
+
return f"{minutes}m{seconds:02d}s"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
async def validate_config(self, target: DeployTarget) -> tuple[bool, str]:
|
|
322
|
+
"""验证配置: 确保已配置 AgentEngine Server"""
|
|
323
|
+
|
|
324
|
+
server_url = os.getenv("AGENTENGINE_SERVER_URL")
|
|
325
|
+
|
|
326
|
+
if not server_url:
|
|
327
|
+
click.echo("⚠️ 未配置 AGENTENGINE_SERVER_URL,将尝试使用默认 Region 配置")
|
|
328
|
+
|
|
329
|
+
# Container 模式: 检查 Docker 是否可用
|
|
330
|
+
artifact_type = target.extra.get("artifact_type", "Code")
|
|
331
|
+
if artifact_type == "Container":
|
|
332
|
+
import subprocess
|
|
333
|
+
try:
|
|
334
|
+
result = subprocess.run(["docker", "version"], capture_output=True, timeout=10)
|
|
335
|
+
if result.returncode != 0:
|
|
336
|
+
return False, "Docker 未正常运行"
|
|
337
|
+
except FileNotFoundError:
|
|
338
|
+
return False, "未找到 docker 命令,请确保已安装 Docker"
|
|
339
|
+
except Exception as e:
|
|
340
|
+
return False, f"Docker 检查失败: {e}"
|
|
341
|
+
|
|
342
|
+
return True, ""
|
|
343
|
+
|
|
344
|
+
async def package(self, project_dir: str, detection_result: Any, config: Dict[str, Any] = None) -> PackageInfo:
|
|
345
|
+
"""打包项目
|
|
346
|
+
|
|
347
|
+
Code 模式: 返回基本 PackageInfo,实际构建在 build() 中进行
|
|
348
|
+
Container 模式: 使用 ContainerBuilder 打包
|
|
349
|
+
"""
|
|
350
|
+
project_path = Path(project_dir)
|
|
351
|
+
|
|
352
|
+
# 创建基本 PackageInfo
|
|
353
|
+
package_info = PackageInfo(
|
|
354
|
+
name=detection_result.name or project_path.name,
|
|
355
|
+
framework=detection_result.type.value,
|
|
356
|
+
build_dir=str(project_path / ".agentengine" / "build"),
|
|
357
|
+
project_dir=str(project_path),
|
|
358
|
+
entry_point=detection_result.entry_point,
|
|
359
|
+
metadata={}
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# 尝试加载之前保存的构建元数据 (ks3_path 等)
|
|
363
|
+
try:
|
|
364
|
+
metadata_file = project_path / ".agentengine" / "build-metadata.json"
|
|
365
|
+
if metadata_file.exists():
|
|
366
|
+
import json
|
|
367
|
+
with open(metadata_file, "r") as f:
|
|
368
|
+
saved_data = json.load(f)
|
|
369
|
+
if "metadata" in saved_data:
|
|
370
|
+
count = 0
|
|
371
|
+
for k, v in saved_data["metadata"].items():
|
|
372
|
+
if v:
|
|
373
|
+
package_info.metadata[k] = v
|
|
374
|
+
count += 1
|
|
375
|
+
if count > 0:
|
|
376
|
+
click.echo(f" 📦 已加载上次构建元数据: {count} 项")
|
|
377
|
+
except Exception as e:
|
|
378
|
+
click.secho(f"⚠️ 加载构建元数据失败: {e}", fg="yellow")
|
|
379
|
+
|
|
380
|
+
return package_info
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
async def build(self, package_info: PackageInfo, target: DeployTarget) -> PackageInfo:
|
|
385
|
+
"""构建 & 上传 (客户端直传 KS3)"""
|
|
386
|
+
artifact_type = target.extra.get("artifact_type", "Code")
|
|
387
|
+
|
|
388
|
+
if artifact_type == "Code":
|
|
389
|
+
# 1. 检查是否已有 KS3 路径
|
|
390
|
+
# 优先级: CLI传入 > Metadata缓存 (仅当 !no_cache)
|
|
391
|
+
cli_ks3_path = target.extra.get("ks3_path")
|
|
392
|
+
cached_ks3_path = package_info.metadata.get("ks3_path")
|
|
393
|
+
no_cache = target.extra.get("no_cache", False)
|
|
394
|
+
repackage = target.extra.get("repackage", False)
|
|
395
|
+
|
|
396
|
+
# 如果显式传入了 ks3-path,直接使用
|
|
397
|
+
if cli_ks3_path:
|
|
398
|
+
package_info.metadata["ks3_path"] = cli_ks3_path
|
|
399
|
+
return package_info
|
|
400
|
+
|
|
401
|
+
# 如果没有 no_cache 且有缓存,才使用缓存
|
|
402
|
+
if not no_cache and not repackage and cached_ks3_path:
|
|
403
|
+
logger.info(f"Using cached bundle: {cached_ks3_path}")
|
|
404
|
+
return package_info
|
|
405
|
+
|
|
406
|
+
# 2. 构建 ZIP 包 (委托给 CodeBuilder)
|
|
407
|
+
|
|
408
|
+
# 传递配置,包括 no_cache
|
|
409
|
+
builder_config = target.extra.copy()
|
|
410
|
+
builder_config["no_cache"] = no_cache
|
|
411
|
+
builder_config["repackage"] = repackage
|
|
412
|
+
|
|
413
|
+
# 实例化 Builder
|
|
414
|
+
# 注意: CodeBuilder 目前设计为直接操作 project_dir,
|
|
415
|
+
# 这里传入原始 project_dir (package_info.project_dir)
|
|
416
|
+
builder = CodeBuilder(Path(package_info.project_dir), config=builder_config)
|
|
417
|
+
|
|
418
|
+
# 执行构建
|
|
419
|
+
build_result = builder.build()
|
|
420
|
+
|
|
421
|
+
if not build_result.success:
|
|
422
|
+
raise Exception(f"构建失败: {build_result.error_message}")
|
|
423
|
+
|
|
424
|
+
zip_path = build_result.artifact_path
|
|
425
|
+
# click.echo(f" ✅ ZIP 已生成: {zip_path}")
|
|
426
|
+
|
|
427
|
+
# 3. 直接上传 KS3 (使用本地 AK/SK)
|
|
428
|
+
# 3. 直接上传 KS3 (使用本地 AK/SK)
|
|
429
|
+
click.echo("\n正在上传代码包到 KS3...")
|
|
430
|
+
upload_started_at = time.monotonic()
|
|
431
|
+
|
|
432
|
+
ks3_bucket = target.extra.get("ks3_bucket")
|
|
433
|
+
upload_region = "cn-beijing-6" if target.region == "pre-online" else target.region
|
|
434
|
+
|
|
435
|
+
uploader = KS3Uploader(region=upload_region, bucket=ks3_bucket)
|
|
436
|
+
|
|
437
|
+
# 使用时间戳确保每次上传的代码包路径唯一,支持真正的版本回滚
|
|
438
|
+
from datetime import datetime
|
|
439
|
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
440
|
+
object_key = f"agents/{package_info.name}/code_{timestamp}.zip"
|
|
441
|
+
|
|
442
|
+
ks3_path = await uploader.upload(zip_path, object_key)
|
|
443
|
+
|
|
444
|
+
if not ks3_path:
|
|
445
|
+
raise Exception("KS3 上传失败")
|
|
446
|
+
|
|
447
|
+
logger.info(f"Upload success: {ks3_path}")
|
|
448
|
+
click.echo(f" ✓ 上传耗时: {self._format_elapsed(upload_started_at)}")
|
|
449
|
+
package_info.metadata["ks3_path"] = ks3_path
|
|
450
|
+
|
|
451
|
+
elif artifact_type == "Container":
|
|
452
|
+
# Container 模式: 使用 ContainerBuilder 构建并推送镜像
|
|
453
|
+
cli_image = target.extra.get("image") or package_info.image
|
|
454
|
+
if cli_image:
|
|
455
|
+
package_info.image = cli_image
|
|
456
|
+
package_info.metadata["image"] = cli_image
|
|
457
|
+
self._persist_build_metadata(package_info)
|
|
458
|
+
return package_info
|
|
459
|
+
|
|
460
|
+
registry = target.extra.get("registry", "")
|
|
461
|
+
tag = target.extra.get("tag", "latest")
|
|
462
|
+
no_cache = target.extra.get("no_cache", False)
|
|
463
|
+
cached_image = package_info.metadata.get("image")
|
|
464
|
+
|
|
465
|
+
if not no_cache and cached_image:
|
|
466
|
+
logger.info(f"Using cached image: {cached_image}")
|
|
467
|
+
package_info.image = cached_image
|
|
468
|
+
return package_info
|
|
469
|
+
|
|
470
|
+
builder = ContainerBuilder(
|
|
471
|
+
project_dir=Path(package_info.project_dir),
|
|
472
|
+
tag=tag,
|
|
473
|
+
registry=registry,
|
|
474
|
+
no_cache=no_cache
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# 构建镜像
|
|
478
|
+
build_result = builder.build()
|
|
479
|
+
if not build_result.success:
|
|
480
|
+
raise Exception(f"镜像构建失败: {build_result.error_message}")
|
|
481
|
+
|
|
482
|
+
image_name = build_result.metadata.get("image")
|
|
483
|
+
package_info.image = image_name
|
|
484
|
+
package_info.metadata["image"] = image_name
|
|
485
|
+
|
|
486
|
+
# 推送镜像
|
|
487
|
+
if not builder.push(image_name):
|
|
488
|
+
raise Exception("镜像推送失败")
|
|
489
|
+
|
|
490
|
+
click.secho(f"✅ 镜像已推送: {image_name}", fg="green")
|
|
491
|
+
|
|
492
|
+
self._persist_build_metadata(package_info)
|
|
493
|
+
|
|
494
|
+
return package_info
|
|
495
|
+
|
|
496
|
+
async def deploy(self, package_info: PackageInfo, target: DeployTarget) -> DeployResult:
|
|
497
|
+
"""通过 AgentEngine Server 部署
|
|
498
|
+
|
|
499
|
+
逻辑:
|
|
500
|
+
- 读取本地 .agentengine.state 文件获取 agent_id
|
|
501
|
+
- 如果有 agent_id → 执行更新 (endpoint 不变)
|
|
502
|
+
- 如果没有 → 创建新 Agent,保存 agent_id 到状态文件
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
server_url = os.getenv("AGENTENGINE_SERVER_URL")
|
|
506
|
+
click.echo(f"\n🚀 开始部署到 Serverless (Managed by {server_url})...")
|
|
507
|
+
|
|
508
|
+
# 读取本地状态文件
|
|
509
|
+
project_dir = package_info.project_dir
|
|
510
|
+
state_file = Path(project_dir) / ".agentengine.state"
|
|
511
|
+
local_state = self._load_state(state_file)
|
|
512
|
+
project_config = self._load_project_config(Path(project_dir))
|
|
513
|
+
runtime_state = self._merge_ui_runtime_state(
|
|
514
|
+
local_state=local_state,
|
|
515
|
+
project_config=project_config,
|
|
516
|
+
)
|
|
517
|
+
existing_agent_id = local_state.get("agent_id")
|
|
518
|
+
resolved_ui = resolve_ui_config(
|
|
519
|
+
framework=package_info.framework,
|
|
520
|
+
state=runtime_state,
|
|
521
|
+
cli_profile=target.extra.get("ui_profile"),
|
|
522
|
+
cli_path=target.extra.get("ui_path"),
|
|
523
|
+
cli_url=target.extra.get("ui_url"),
|
|
524
|
+
)
|
|
525
|
+
ui_state = ui_config_to_state_fields(resolved_ui)
|
|
526
|
+
if project_config.get("ui_bundle_path") and not ui_state.get("ui_bundle_path"):
|
|
527
|
+
ui_state["ui_bundle_path"] = project_config.get("ui_bundle_path")
|
|
528
|
+
if project_config.get("ui_bundle_dir") and not ui_state.get("ui_bundle_dir"):
|
|
529
|
+
ui_state["ui_bundle_dir"] = project_config.get("ui_bundle_dir")
|
|
530
|
+
|
|
531
|
+
# 构造请求 payload
|
|
532
|
+
artifact_type = target.extra.get("artifact_type", "Code")
|
|
533
|
+
artifact_path = ""
|
|
534
|
+
if artifact_type == "Code":
|
|
535
|
+
original_artifact_path = package_info.metadata.get("ks3_path", "")
|
|
536
|
+
artifact_path = original_artifact_path
|
|
537
|
+
|
|
538
|
+
# 转换为内网地址 (如果 serverless 无法解析 ks3://)
|
|
539
|
+
if artifact_path.startswith("ks3://"):
|
|
540
|
+
try:
|
|
541
|
+
from ksadk.common.constants import get_ks3_endpoints
|
|
542
|
+
|
|
543
|
+
# 确定 KS3 Region code (pre-online -> cn-beijing)
|
|
544
|
+
ks3_region = target.extra.get("ks3_region")
|
|
545
|
+
if not ks3_region:
|
|
546
|
+
ks3_region = "cn-beijing-6" if target.region == "pre-online" else target.region
|
|
547
|
+
|
|
548
|
+
_, internal_endpoint = get_ks3_endpoints(ks3_region)
|
|
549
|
+
|
|
550
|
+
if internal_endpoint:
|
|
551
|
+
# ks3://bucket/key -> http://bucket.internal_endpoint/key
|
|
552
|
+
bucket, key = self._parse_code_artifact_path(artifact_path)
|
|
553
|
+
if bucket and key:
|
|
554
|
+
artifact_path = f"http://{bucket}.{internal_endpoint}/{key}"
|
|
555
|
+
click.echo(f" Converted artifact path: {artifact_path}")
|
|
556
|
+
except Exception as e:
|
|
557
|
+
logger.warning(f"Failed to convert ks3 path to internal URL: {e}")
|
|
558
|
+
|
|
559
|
+
if not artifact_path:
|
|
560
|
+
raise ValueError(
|
|
561
|
+
"❌ 未找到代码包路径 (ks3_path)。\n"
|
|
562
|
+
" 在 Serverless 模式下,必须先上传代码包。\n"
|
|
563
|
+
" 👉 请先执行: agentengine build --mode code --push\n"
|
|
564
|
+
" 或者在 deploy 命令中使用 --ks3-path 指定路径。"
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
parsed_bucket, parsed_object_key = self._parse_code_artifact_path(original_artifact_path)
|
|
568
|
+
if not parsed_bucket or not parsed_object_key:
|
|
569
|
+
raise ValueError(
|
|
570
|
+
"❌ ks3_path 格式无效,缺少代码包对象路径。\n"
|
|
571
|
+
f" 当前值: {original_artifact_path or '(empty)'}\n"
|
|
572
|
+
" 期望格式: ks3://<bucket>/<object-key>\n"
|
|
573
|
+
" 👉 请重新执行: agentengine build --mode code --push\n"
|
|
574
|
+
" 或者传入完整的 --ks3-path。"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
else:
|
|
578
|
+
artifact_path = package_info.image
|
|
579
|
+
|
|
580
|
+
# 构建 KS3 凭证
|
|
581
|
+
ks3_config = None
|
|
582
|
+
if artifact_type == "Code":
|
|
583
|
+
from ksadk.common.auth import AWSV4Auth
|
|
584
|
+
auth = AWSV4Auth() # 读取本地 AK/SK
|
|
585
|
+
if auth.access_key_id and auth.secret_access_key:
|
|
586
|
+
# 智能推断 Bucket 和 Region
|
|
587
|
+
bucket_name = target.extra.get("ks3_bucket")
|
|
588
|
+
if not bucket_name:
|
|
589
|
+
bucket_name, _ = self._parse_code_artifact_path(
|
|
590
|
+
package_info.metadata.get("ks3_path", "")
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# 如果没有提供,尝试从 artifact_path 解析
|
|
594
|
+
if not bucket_name:
|
|
595
|
+
if artifact_path.startswith("ks3://"):
|
|
596
|
+
try:
|
|
597
|
+
# ks3://bucket/key -> bucket
|
|
598
|
+
bucket_name = artifact_path.split("/")[2]
|
|
599
|
+
logger.info(f"Extracted bucket from ks3:// path: {bucket_name}")
|
|
600
|
+
except IndexError:
|
|
601
|
+
pass
|
|
602
|
+
elif artifact_path.startswith("http") and "." in artifact_path:
|
|
603
|
+
try:
|
|
604
|
+
# http://bucket.endpoint/key -> bucket
|
|
605
|
+
from urllib.parse import urlparse
|
|
606
|
+
parsed = urlparse(artifact_path)
|
|
607
|
+
bucket_name = parsed.netloc.split(".")[0]
|
|
608
|
+
logger.info(f"Extracted bucket from HTTP URL: {bucket_name}")
|
|
609
|
+
except Exception as e:
|
|
610
|
+
logger.warning(f"Failed to extract bucket from URL: {e}")
|
|
611
|
+
|
|
612
|
+
# 如果仍然没有,使用智能默认值(与 KS3Uploader 逻辑一致)
|
|
613
|
+
if not bucket_name:
|
|
614
|
+
account_id = os.getenv("KSYUN_ACCOUNT_ID")
|
|
615
|
+
upload_region = "cn-beijing-6" if target.region == "pre-online" else target.region
|
|
616
|
+
|
|
617
|
+
if not account_id:
|
|
618
|
+
raise ValueError(
|
|
619
|
+
"❌ 缺少 KSYUN_ACCOUNT_ID 环境变量\n"
|
|
620
|
+
" Bucket 名称格式必须为: agentengine-{account_id}-{region}\n"
|
|
621
|
+
" 请在 .env 文件中设置: KSYUN_ACCOUNT_ID=你的账号ID"
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
bucket_name = f"agentengine-{account_id}-{upload_region}"
|
|
625
|
+
logger.info(f"Using default bucket: {bucket_name}")
|
|
626
|
+
|
|
627
|
+
# Region 逻辑需与 Build 阶段保持一致 (pre-online -> cn-beijing-6)
|
|
628
|
+
ks3_region = target.extra.get("ks3_region")
|
|
629
|
+
if not ks3_region:
|
|
630
|
+
ks3_region = "cn-beijing-6" if target.region == "pre-online" else target.region
|
|
631
|
+
|
|
632
|
+
ks3_config = {
|
|
633
|
+
"access_key": auth.access_key_id,
|
|
634
|
+
"secret_key": auth.secret_access_key,
|
|
635
|
+
"region": ks3_region,
|
|
636
|
+
"bucket": bucket_name,
|
|
637
|
+
}
|
|
638
|
+
logger.info(f"📦 KS3 Config: bucket={bucket_name}, region={ks3_region}")
|
|
639
|
+
|
|
640
|
+
try:
|
|
641
|
+
# 获取 dry_run 标识
|
|
642
|
+
is_dry_run = target.extra.get("dry_run", False)
|
|
643
|
+
|
|
644
|
+
async with AgentEngineClient(region=target.region, dry_run=is_dry_run) as client:
|
|
645
|
+
agent_exists = False
|
|
646
|
+
|
|
647
|
+
if existing_agent_id:
|
|
648
|
+
# 有本地状态 → 先检查服务器上是否存在
|
|
649
|
+
click.echo(f" 检测到本地状态: {existing_agent_id}")
|
|
650
|
+
|
|
651
|
+
try:
|
|
652
|
+
# 尝试获取 agent,确认是否存在
|
|
653
|
+
existing_agent = await client.get_agent(existing_agent_id)
|
|
654
|
+
if existing_agent:
|
|
655
|
+
agent_exists = True
|
|
656
|
+
except Exception as e:
|
|
657
|
+
# Agent 不存在或查询失败
|
|
658
|
+
err_msg = str(e).lower()
|
|
659
|
+
if "not found" in err_msg or "404" in err_msg or "不存在" in err_msg:
|
|
660
|
+
click.secho(f" ⚠️ 服务器上未找到 Agent {existing_agent_id},将创建新 Agent", fg="yellow")
|
|
661
|
+
agent_exists = False
|
|
662
|
+
# 如果是 DryRun 抛出的异常(表明请求本来会发出去但被拦截了),我们认为 Agent 可能存在也可能不存在
|
|
663
|
+
# 但为了安全起见,在 DryRun 模式下我们假设它存在并走更新路径,或者简单地打印日志
|
|
664
|
+
elif "Dry Run" in str(e):
|
|
665
|
+
click.secho(f" [Dry Run] 假设 Agent {existing_agent_id} 存在", fg="cyan")
|
|
666
|
+
agent_exists = True
|
|
667
|
+
else:
|
|
668
|
+
# 其他错误,重新抛出
|
|
669
|
+
raise
|
|
670
|
+
|
|
671
|
+
if agent_exists:
|
|
672
|
+
# Agent 存在 → 执行更新
|
|
673
|
+
click.echo(f" 执行热更新 (endpoint 保持不变)...")
|
|
674
|
+
|
|
675
|
+
update_data = {
|
|
676
|
+
"artifact_type": artifact_type,
|
|
677
|
+
"artifact_path": artifact_path,
|
|
678
|
+
"resources": {
|
|
679
|
+
"cpu": target.resources.cpu,
|
|
680
|
+
"memory": target.resources.memory
|
|
681
|
+
},
|
|
682
|
+
"scaling": {
|
|
683
|
+
"min_replicas": target.scaling.min_replicas,
|
|
684
|
+
"max_replicas": target.scaling.max_replicas,
|
|
685
|
+
"concurrency": target.scaling.concurrency
|
|
686
|
+
},
|
|
687
|
+
"observability": {
|
|
688
|
+
"langfuse_enabled": target.extra.get("enable_observability", True)
|
|
689
|
+
},
|
|
690
|
+
"ui_config": {
|
|
691
|
+
"profile": resolved_ui.profile,
|
|
692
|
+
"path": resolved_ui.path,
|
|
693
|
+
"url": resolved_ui.url,
|
|
694
|
+
},
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if ks3_config:
|
|
698
|
+
update_data["ks3"] = ks3_config
|
|
699
|
+
elif artifact_type == "Container":
|
|
700
|
+
image_credential = self._image_credential_from_env(artifact_path)
|
|
701
|
+
if image_credential:
|
|
702
|
+
update_data["image_credential"] = image_credential
|
|
703
|
+
click.echo(
|
|
704
|
+
f" 🔑 镜像凭证: {image_credential['username']}@{image_credential['endpoint']}"
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
# 加载全局配置 + 本地 .env,并通过部署参数注入到运行时环境变量。
|
|
708
|
+
env_vars, env_file_exists, project_env_count = self._load_deploy_env_vars(
|
|
709
|
+
project_dir,
|
|
710
|
+
target.extra.get("env_vars") or {},
|
|
711
|
+
)
|
|
712
|
+
env_vars = self._inject_ui_runtime_env(env_vars, ui_state, local_state)
|
|
713
|
+
if env_vars:
|
|
714
|
+
update_data["env_vars"] = env_vars
|
|
715
|
+
if env_file_exists:
|
|
716
|
+
click.echo(
|
|
717
|
+
f" 📦 更新环境变量: {len(env_vars)} 项 "
|
|
718
|
+
f"(全局配置 + {project_env_count} 项 from .env)"
|
|
719
|
+
)
|
|
720
|
+
else:
|
|
721
|
+
click.echo(f" 📦 更新环境变量: {len(env_vars)} 项 from 全局配置")
|
|
722
|
+
|
|
723
|
+
network_config = self._serialize_network_config(target, is_update=True)
|
|
724
|
+
if network_config:
|
|
725
|
+
update_data["network"] = network_config
|
|
726
|
+
|
|
727
|
+
storage_config = self._serialize_storage_config(target)
|
|
728
|
+
if storage_config:
|
|
729
|
+
update_data["storage"] = storage_config
|
|
730
|
+
|
|
731
|
+
# 注入更新时间戳,强制触发 Rolling Update (Pod 重启)
|
|
732
|
+
if "env_vars" not in update_data:
|
|
733
|
+
update_data["env_vars"] = {}
|
|
734
|
+
|
|
735
|
+
import time
|
|
736
|
+
update_data["env_vars"]["KSADK_UPDATED_AT"] = str(int(time.time()))
|
|
737
|
+
|
|
738
|
+
# DEBUG: 打印 Payload 确认 trigger 是否存在
|
|
739
|
+
click.echo(f" 🔄 更新 Trigger: KSADK_UPDATED_AT={update_data['env_vars']['KSADK_UPDATED_AT']}")
|
|
740
|
+
|
|
741
|
+
res = await client.update_agent(existing_agent_id, update_data)
|
|
742
|
+
latest_access = {}
|
|
743
|
+
if not is_dry_run:
|
|
744
|
+
latest_access = await get_latest_agent_access(
|
|
745
|
+
client,
|
|
746
|
+
agent_id=existing_agent_id,
|
|
747
|
+
include_api_key=True,
|
|
748
|
+
on_error=lambda exc: logger.warning(
|
|
749
|
+
"Failed to refresh quick access for %s: %s",
|
|
750
|
+
existing_agent_id,
|
|
751
|
+
exc,
|
|
752
|
+
),
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
# 如果是 Dry Run,手动构造假响应以避免崩溃
|
|
756
|
+
if is_dry_run and not res:
|
|
757
|
+
res = {"name": package_info.name, "endpoint": "http://dry-run-endpoint"}
|
|
758
|
+
|
|
759
|
+
# 更新本地状态 (保留旧字段如 api_key)
|
|
760
|
+
new_state = local_state.copy()
|
|
761
|
+
new_state.update({
|
|
762
|
+
"agent_id": latest_access.get("agent_id") or existing_agent_id,
|
|
763
|
+
"name": latest_access.get("name") or res.get("name"),
|
|
764
|
+
"region": target.region,
|
|
765
|
+
"endpoint": latest_access.get("endpoint") or res.get("endpoint"),
|
|
766
|
+
"updated_at": self._now_iso(),
|
|
767
|
+
**ui_state,
|
|
768
|
+
})
|
|
769
|
+
if latest_access.get("api_key"):
|
|
770
|
+
new_state["api_key"] = latest_access["api_key"]
|
|
771
|
+
self._save_state(state_file, new_state)
|
|
772
|
+
|
|
773
|
+
return DeployResult(
|
|
774
|
+
status=DeployStatus.DEPLOYING,
|
|
775
|
+
agent_id=existing_agent_id,
|
|
776
|
+
agent_name=res.get("name"),
|
|
777
|
+
endpoint=res.get("endpoint"),
|
|
778
|
+
message=f"✅ Agent 已更新: {existing_agent_id}"
|
|
779
|
+
)
|
|
780
|
+
# else: agent_exists = False,继续执行创建逻辑
|
|
781
|
+
|
|
782
|
+
# 没有本地状态,或本地状态对应的 Agent 在服务器上不存在 → 创建新 Agent
|
|
783
|
+
if not existing_agent_id or not agent_exists:
|
|
784
|
+
click.echo(f" 创建新 Agent: {package_info.name}")
|
|
785
|
+
|
|
786
|
+
request_data = {
|
|
787
|
+
"name": package_info.name,
|
|
788
|
+
"framework": package_info.framework,
|
|
789
|
+
"artifact_type": artifact_type,
|
|
790
|
+
"artifact_path": artifact_path,
|
|
791
|
+
"region": target.region,
|
|
792
|
+
"instance_id": target.extra.get("instance_id", "default"),
|
|
793
|
+
"resources": {
|
|
794
|
+
"cpu": target.resources.cpu,
|
|
795
|
+
"memory": target.resources.memory
|
|
796
|
+
},
|
|
797
|
+
"scaling": {
|
|
798
|
+
"min_replicas": target.scaling.min_replicas,
|
|
799
|
+
"max_replicas": target.scaling.max_replicas,
|
|
800
|
+
"concurrency": target.scaling.concurrency
|
|
801
|
+
},
|
|
802
|
+
"observability": {
|
|
803
|
+
"langfuse_enabled": target.extra.get("enable_observability", True)
|
|
804
|
+
},
|
|
805
|
+
"ui_config": {
|
|
806
|
+
"profile": resolved_ui.profile,
|
|
807
|
+
"path": resolved_ui.path,
|
|
808
|
+
"url": resolved_ui.url,
|
|
809
|
+
},
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if ks3_config:
|
|
813
|
+
request_data["ks3"] = ks3_config
|
|
814
|
+
|
|
815
|
+
# Container 模式: 传递镜像凭证
|
|
816
|
+
if artifact_type == "Container":
|
|
817
|
+
image_credential = self._image_credential_from_env(artifact_path)
|
|
818
|
+
if image_credential:
|
|
819
|
+
request_data["image_credential"] = image_credential
|
|
820
|
+
click.echo(
|
|
821
|
+
f" 🔑 镜像凭证: {image_credential['username']}@{image_credential['endpoint']}"
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
# 加载全局配置 + 本地 .env,并通过部署参数注入到运行时环境变量。
|
|
825
|
+
env_vars, env_file_exists, project_env_count = self._load_deploy_env_vars(
|
|
826
|
+
project_dir,
|
|
827
|
+
target.extra.get("env_vars") or {},
|
|
828
|
+
)
|
|
829
|
+
env_vars = self._inject_ui_runtime_env(env_vars, ui_state, local_state)
|
|
830
|
+
if env_vars:
|
|
831
|
+
if env_file_exists:
|
|
832
|
+
click.echo(
|
|
833
|
+
f" 📦 加载环境变量: {len(env_vars)} 项 "
|
|
834
|
+
f"(全局配置 + {project_env_count} 项 from .env)"
|
|
835
|
+
)
|
|
836
|
+
else:
|
|
837
|
+
click.echo(f" 📦 加载环境变量: {len(env_vars)} 项 from 全局配置")
|
|
838
|
+
|
|
839
|
+
if env_vars:
|
|
840
|
+
request_data["env_vars"] = env_vars
|
|
841
|
+
|
|
842
|
+
network_config = self._serialize_network_config(target, is_update=False)
|
|
843
|
+
if network_config:
|
|
844
|
+
request_data["network"] = network_config
|
|
845
|
+
|
|
846
|
+
storage_config = self._serialize_storage_config(target)
|
|
847
|
+
if storage_config:
|
|
848
|
+
request_data["storage"] = storage_config
|
|
849
|
+
|
|
850
|
+
# 获取 Account ID (用于 Server 端的 user_id)
|
|
851
|
+
extra_headers = {}
|
|
852
|
+
ksyun_account_id = os.getenv("KSYUN_ACCOUNT_ID")
|
|
853
|
+
if ksyun_account_id:
|
|
854
|
+
extra_headers["X-Ksc-Account-Id"] = ksyun_account_id
|
|
855
|
+
|
|
856
|
+
# 重新构造一个带 Header 的 client
|
|
857
|
+
async with AgentEngineClient(region=target.region, extra_headers=extra_headers, dry_run=is_dry_run) as new_client:
|
|
858
|
+
res = await new_client.create_agent(request_data)
|
|
859
|
+
|
|
860
|
+
# 如果是 Dry Run,手动构造假响应
|
|
861
|
+
if is_dry_run and not res:
|
|
862
|
+
res = {
|
|
863
|
+
"agent_id": "dry-run-agent-id",
|
|
864
|
+
"name": package_info.name,
|
|
865
|
+
"endpoint": "http://dry-run-endpoint",
|
|
866
|
+
"api_key": "dry-run-key"
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
# CreateAgentProduct 返回 order_id,需要轮询 list_agents 获取 agent_id
|
|
870
|
+
new_agent_id = res.get("agent_id")
|
|
871
|
+
order_id = res.get("order_id")
|
|
872
|
+
agent_name = res.get("name") or package_info.name
|
|
873
|
+
agent_endpoint = res.get("endpoint")
|
|
874
|
+
agent_api_key = res.get("api_key")
|
|
875
|
+
|
|
876
|
+
if order_id and not new_agent_id:
|
|
877
|
+
click.echo(f" 📋 订单已创建: {order_id},等待实例创建...")
|
|
878
|
+
import time
|
|
879
|
+
for i in range(12): # 最多等 60s
|
|
880
|
+
time.sleep(5)
|
|
881
|
+
try:
|
|
882
|
+
detail = await client.get_agent(name=package_info.name, include_api_key=True)
|
|
883
|
+
qa = detail.get("quick_access", {})
|
|
884
|
+
basic = detail.get("basic", {})
|
|
885
|
+
new_agent_id = basic.get("agent_id")
|
|
886
|
+
agent_name = basic.get("name") or agent_name
|
|
887
|
+
agent_endpoint = qa.get("public_endpoint")
|
|
888
|
+
agent_api_key = qa.get("api_key")
|
|
889
|
+
if new_agent_id:
|
|
890
|
+
click.secho(f" ✅ 实例已创建: {new_agent_id}", fg="green")
|
|
891
|
+
break
|
|
892
|
+
except Exception:
|
|
893
|
+
pass
|
|
894
|
+
click.echo(f" ⏳ 等待中... ({(i+1)*5}s)")
|
|
895
|
+
|
|
896
|
+
if not new_agent_id:
|
|
897
|
+
click.secho(" ⚠️ 实例仍在创建中,稍后使用 'agentengine status' 查看", fg="yellow")
|
|
898
|
+
|
|
899
|
+
latest_access = {}
|
|
900
|
+
if new_agent_id and not is_dry_run:
|
|
901
|
+
latest_access = await get_latest_agent_access(
|
|
902
|
+
new_client,
|
|
903
|
+
agent_id=new_agent_id,
|
|
904
|
+
include_api_key=True,
|
|
905
|
+
retry_delays=(0.3, 0.7, 1.0),
|
|
906
|
+
on_error=lambda exc: logger.warning(
|
|
907
|
+
"Failed to refresh quick access for %s: %s",
|
|
908
|
+
new_agent_id,
|
|
909
|
+
exc,
|
|
910
|
+
),
|
|
911
|
+
)
|
|
912
|
+
new_agent_id = latest_access.get("agent_id") or new_agent_id
|
|
913
|
+
agent_name = latest_access.get("name") or agent_name
|
|
914
|
+
agent_endpoint = latest_access.get("endpoint") or agent_endpoint
|
|
915
|
+
agent_api_key = latest_access.get("api_key") or agent_api_key
|
|
916
|
+
|
|
917
|
+
# 保存 agent_id 到本地状态文件
|
|
918
|
+
self._save_state(state_file, {
|
|
919
|
+
"agent_id": new_agent_id,
|
|
920
|
+
"name": agent_name,
|
|
921
|
+
"region": target.region,
|
|
922
|
+
"endpoint": agent_endpoint,
|
|
923
|
+
"api_key": agent_api_key, # 只在首次保存
|
|
924
|
+
"order_id": order_id,
|
|
925
|
+
"created_at": self._now_iso(),
|
|
926
|
+
**ui_state,
|
|
927
|
+
})
|
|
928
|
+
|
|
929
|
+
click.echo(f" 💾 已保存状态到 .agentengine.state")
|
|
930
|
+
|
|
931
|
+
return DeployResult(
|
|
932
|
+
status=DeployStatus.DEPLOYING,
|
|
933
|
+
agent_id=new_agent_id,
|
|
934
|
+
agent_name=agent_name,
|
|
935
|
+
endpoint=agent_endpoint,
|
|
936
|
+
api_key=agent_api_key,
|
|
937
|
+
message=f"✅ Agent ID: {new_agent_id or '(创建中)'} (首次部署, 订单: {order_id or '-'})"
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
except DryRunExit as e:
|
|
941
|
+
return DeployResult(
|
|
942
|
+
status=DeployStatus.SKIPPED,
|
|
943
|
+
message="✅ Dry Run Completed: 请求已打印,未执行实际变更。",
|
|
944
|
+
metadata={"dry_run_request": e.payload or {}},
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
except Exception as e:
|
|
948
|
+
logger.error(f"Deploy failed: {e}")
|
|
949
|
+
|
|
950
|
+
# 检测名称冲突
|
|
951
|
+
err_msg = str(e)
|
|
952
|
+
if "Conflict" in err_msg or "409" in err_msg or "already exists" in err_msg:
|
|
953
|
+
return DeployResult(
|
|
954
|
+
status=DeployStatus.FAILED,
|
|
955
|
+
message=f"❌ 部署失败: Agent 名称 '{package_info.name}' 已存在。\n"
|
|
956
|
+
f" 提示: 请检查是否重复创建。\n"
|
|
957
|
+
f" 👉 解决方法: 请在 agentengine.yaml 中修改 'name' 字段 (如添加后缀) 后重试。"
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
return DeployResult(
|
|
961
|
+
status=DeployStatus.FAILED,
|
|
962
|
+
message=f"Server 请求失败: {str(e)}"
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
async def get_status(self, agent_id: str, target: DeployTarget) -> DeployResult:
|
|
966
|
+
"""获取 Agent 状态"""
|
|
967
|
+
dry_run = target.extra.get("dry_run", False)
|
|
968
|
+
try:
|
|
969
|
+
async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
|
|
970
|
+
res = await client.get_agent(agent_id)
|
|
971
|
+
|
|
972
|
+
status_map = {
|
|
973
|
+
"running": DeployStatus.RUNNING,
|
|
974
|
+
"ready": DeployStatus.RUNNING,
|
|
975
|
+
"creating": DeployStatus.DEPLOYING,
|
|
976
|
+
"updating": DeployStatus.UPDATING,
|
|
977
|
+
"terminating": DeployStatus.STOPPING,
|
|
978
|
+
"scaling": DeployStatus.UPDATING,
|
|
979
|
+
"failed": DeployStatus.FAILED,
|
|
980
|
+
"error": DeployStatus.FAILED,
|
|
981
|
+
"unknown": DeployStatus.UNKNOWN
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
# 使状态匹配不区分大小写,以兼容服务端的改动 (Creating -> CREATING)
|
|
985
|
+
current_status = (res.get("status") or "").lower()
|
|
986
|
+
return DeployResult(
|
|
987
|
+
status=status_map.get(current_status, DeployStatus.UNKNOWN),
|
|
988
|
+
agent_id=res.get("agent_id"),
|
|
989
|
+
agent_name=res.get("name"),
|
|
990
|
+
endpoint=res.get("endpoint"),
|
|
991
|
+
message=f"Status: {res.get('status')} ({res.get('phase')})"
|
|
992
|
+
)
|
|
993
|
+
except DryRunExit:
|
|
994
|
+
return DeployResult(status=DeployStatus.SKIPPED, message="Dry Run executed.")
|
|
995
|
+
except Exception as e:
|
|
996
|
+
return DeployResult(
|
|
997
|
+
status=DeployStatus.UNKNOWN,
|
|
998
|
+
message=f"查询失败: {e}"
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
async def destroy(self, agent_id: str, target: DeployTarget) -> bool:
|
|
1002
|
+
"""销毁 Agent"""
|
|
1003
|
+
dry_run = target.extra.get("dry_run", False)
|
|
1004
|
+
project_dir = str(target.extra.get("project_dir") or "").strip()
|
|
1005
|
+
state_file = (Path(project_dir).resolve() if project_dir else Path(".").resolve()) / ".agentengine.state"
|
|
1006
|
+
|
|
1007
|
+
try:
|
|
1008
|
+
async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
|
|
1009
|
+
click.echo(f"正在通过 Server 删除 Agent: {agent_id}...")
|
|
1010
|
+
success = await client.delete_agent(agent_id)
|
|
1011
|
+
if success and not dry_run and state_file.exists():
|
|
1012
|
+
local_state = self._load_state(state_file)
|
|
1013
|
+
if str(local_state.get("agent_id") or "").strip() == str(agent_id).strip():
|
|
1014
|
+
try:
|
|
1015
|
+
os.remove(state_file)
|
|
1016
|
+
logger.info(f"Deleted local state file: {state_file}")
|
|
1017
|
+
except Exception as cleanup_error:
|
|
1018
|
+
logger.warning(f"Failed to delete local state file {state_file}: {cleanup_error}")
|
|
1019
|
+
return success
|
|
1020
|
+
except DryRunExit:
|
|
1021
|
+
# 让异常冒泡给 CLI 处理
|
|
1022
|
+
raise
|
|
1023
|
+
except Exception as e:
|
|
1024
|
+
logger.error(f"Failed to delete agent: {e}")
|
|
1025
|
+
return False
|
|
1026
|
+
|
|
1027
|
+
async def list_agents(self, target: DeployTarget) -> List[DeployResult]:
|
|
1028
|
+
"""列出所有 Agent"""
|
|
1029
|
+
dry_run = target.extra.get("dry_run", False)
|
|
1030
|
+
try:
|
|
1031
|
+
async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
|
|
1032
|
+
res = await client.list_agents()
|
|
1033
|
+
|
|
1034
|
+
results = []
|
|
1035
|
+
for agent in res.get("Agents", []):
|
|
1036
|
+
current_status = (agent.get("status") or "").lower()
|
|
1037
|
+
results.append(DeployResult(
|
|
1038
|
+
status=DeployStatus.RUNNING if current_status in ("running", "ready") else (
|
|
1039
|
+
DeployStatus.DEPLOYING if current_status == "creating" else DeployStatus.UNKNOWN
|
|
1040
|
+
),
|
|
1041
|
+
agent_id=agent.get("agent_id"),
|
|
1042
|
+
agent_name=agent.get("name"),
|
|
1043
|
+
endpoint=agent.get("endpoint"),
|
|
1044
|
+
message=agent.get("status")
|
|
1045
|
+
))
|
|
1046
|
+
return results
|
|
1047
|
+
except DryRunExit:
|
|
1048
|
+
raise
|
|
1049
|
+
except Exception as e:
|
|
1050
|
+
logger.error(f"List agents failed: {e}")
|
|
1051
|
+
return []
|
|
1052
|
+
|
|
1053
|
+
async def invoke(self, agent_id: str, message: str, target: DeployTarget) -> str:
|
|
1054
|
+
"""调用 Agent"""
|
|
1055
|
+
dry_run = target.extra.get("dry_run", False)
|
|
1056
|
+
try:
|
|
1057
|
+
async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
|
|
1058
|
+
response = await client.chat(agent_id, message, stream=False)
|
|
1059
|
+
return response.get("output", "")
|
|
1060
|
+
except DryRunExit:
|
|
1061
|
+
raise
|
|
1062
|
+
except Exception as e:
|
|
1063
|
+
return f"Error: {e}"
|
|
1064
|
+
|
|
1065
|
+
def _load_state(self, state_file: Path) -> Dict[str, Any]:
|
|
1066
|
+
"""读取本地状态文件"""
|
|
1067
|
+
if state_file.exists():
|
|
1068
|
+
try:
|
|
1069
|
+
with open(state_file, 'r', encoding='utf-8') as f:
|
|
1070
|
+
return yaml.safe_load(f) or {}
|
|
1071
|
+
except Exception as e:
|
|
1072
|
+
logger.warning(f"Failed to load state file: {e}")
|
|
1073
|
+
return {}
|
|
1074
|
+
|
|
1075
|
+
def _load_project_config(self, project_dir: Path) -> Dict[str, Any]:
|
|
1076
|
+
"""读取项目配置文件。"""
|
|
1077
|
+
for file_name in ("agentengine.yaml", "ksadk.yaml"):
|
|
1078
|
+
config_file = project_dir / file_name
|
|
1079
|
+
if not config_file.exists():
|
|
1080
|
+
continue
|
|
1081
|
+
try:
|
|
1082
|
+
with open(config_file, "r", encoding="utf-8-sig") as f:
|
|
1083
|
+
return yaml.safe_load(f) or {}
|
|
1084
|
+
except Exception as e:
|
|
1085
|
+
logger.warning(f"Failed to load project config {config_file}: {e}")
|
|
1086
|
+
return {}
|
|
1087
|
+
|
|
1088
|
+
def _merge_ui_runtime_state(
|
|
1089
|
+
self,
|
|
1090
|
+
*,
|
|
1091
|
+
local_state: Dict[str, Any],
|
|
1092
|
+
project_config: Dict[str, Any],
|
|
1093
|
+
) -> Dict[str, Any]:
|
|
1094
|
+
"""合并状态文件与项目配置中的 UI 运行时信息。"""
|
|
1095
|
+
merged = dict(local_state or {})
|
|
1096
|
+
for key in ("ui_profile", "ui_path", "ui_url", "ui_bundle_path", "ui_bundle_dir"):
|
|
1097
|
+
value = project_config.get(key)
|
|
1098
|
+
if value and not merged.get(key):
|
|
1099
|
+
merged[key] = value
|
|
1100
|
+
return merged
|
|
1101
|
+
|
|
1102
|
+
def _save_state(self, state_file: Path, state: Dict[str, Any]) -> None:
|
|
1103
|
+
"""保存状态到本地文件"""
|
|
1104
|
+
import yaml
|
|
1105
|
+
try:
|
|
1106
|
+
with open(state_file, 'w', encoding='utf-8') as f:
|
|
1107
|
+
yaml.dump(state, f, default_flow_style=False, allow_unicode=True)
|
|
1108
|
+
logger.debug(f"State saved to {state_file}")
|
|
1109
|
+
except Exception as e:
|
|
1110
|
+
logger.warning(f"Failed to save state file: {e}")
|
|
1111
|
+
|
|
1112
|
+
def _now_iso(self) -> str:
|
|
1113
|
+
"""返回当前时间 ISO 格式"""
|
|
1114
|
+
from datetime import datetime
|
|
1115
|
+
return datetime.now().isoformat()
|