agentengine-sdk-python 0.8.0__py3-none-any.whl → 0.8.2__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.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
- agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
- ksadk/a2a/__init__.py +2 -0
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/bootstrap.py +0 -2
- ksadk/a2a/card.py +6 -111
- ksadk/a2a/control_plane.py +239 -1
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +195 -172
- ksadk/a2a/routes.py +12 -5
- ksadk/a2a/server.py +1 -8
- ksadk/a2a/service_env.py +33 -0
- ksadk/a2a/space_client.py +43 -349
- ksadk/a2a_card.py +101 -0
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +474 -275
- ksadk/agui/config.py +11 -5
- ksadk/agui/routes.py +7 -15
- ksadk/api/client.py +530 -40
- ksadk/api/environment.py +27 -0
- ksadk/builders/code_builder.py +352 -53
- ksadk/builders/container_builder.py +71 -44
- ksadk/builders/framework_requirements.py +2 -1
- ksadk/builders/ks3_uploader.py +2 -2
- ksadk/builders/managed_runtime_builder.py +60 -28
- ksadk/cli/__init__.py +25 -0
- ksadk/cli/cmd_a2a.py +18 -31
- ksadk/cli/cmd_create.py +13 -26
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +28 -3
- ksadk/cli/cmd_eval.py +443 -0
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +137 -163
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_launch.py +12 -0
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +85 -479
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_run.py +29 -67
- ksadk/cli/cmd_studio.py +201 -0
- ksadk/cli/cmd_web.py +15 -6
- ksadk/cli/dry_run.py +2 -1
- ksadk/cli/env_options.py +83 -0
- ksadk/cli/global_options.py +3 -1
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/network_options.py +7 -5
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/cli/runtime_bootstrap.py +51 -0
- ksadk/cli/ui.py +6 -0
- ksadk/cli/workflow_common.py +5 -5
- ksadk/codex/__init__.py +3 -3
- ksadk/codex/client.py +833 -49
- ksadk/codex/phase.py +1 -1
- ksadk/codex/runtime.py +720 -127
- ksadk/common/kop_client.py +232 -0
- ksadk/configs/__init__.py +1 -3
- ksadk/configs/env_registry.py +244 -45
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +6 -1
- ksadk/configs/settings.py +5 -108
- ksadk/context_engine/__init__.py +98 -0
- ksadk/context_engine/assembler.py +176 -0
- ksadk/context_engine/baseline.py +419 -0
- ksadk/context_engine/cache_observability.py +228 -0
- ksadk/context_engine/capabilities.py +443 -0
- ksadk/context_engine/contributors.py +344 -0
- ksadk/context_engine/hosted_pipeline.py +417 -0
- ksadk/context_engine/models.py +117 -0
- ksadk/context_engine/planner.py +698 -0
- ksadk/context_engine/policies.py +319 -0
- ksadk/context_engine/projection.py +31 -0
- ksadk/context_engine/shadow_plan.py +266 -0
- ksadk/context_engine/tokenizer.py +164 -0
- ksadk/conversations/__init__.py +85 -94
- ksadk/conversations/context.py +126 -6
- ksadk/conversations/message_projection.py +292 -28
- ksadk/conversations/model_context.py +49 -3
- ksadk/conversations/runtime_compaction.py +207 -5
- ksadk/conversations/runtime_input.py +193 -3
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +157 -4
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +280 -2
- ksadk/conversations/runtime_resume.py +71 -32
- ksadk/conversations/runtime_stream_events.py +94 -9
- ksadk/conversations/runtime_streaming.py +175 -22
- ksadk/conversations/semantic_summary.py +311 -1
- ksadk/conversations/session_lock.py +51 -0
- ksadk/deployment/env_forward.py +72 -0
- ksadk/deployment/managed_runtime.py +38 -0
- ksadk/deployment/manager.py +24 -8
- ksadk/deployment/providers/serverless.py +228 -92
- ksadk/evaluation/__init__.py +116 -0
- ksadk/evaluation/a2a_adapter.py +452 -0
- ksadk/evaluation/adapters.py +67 -0
- ksadk/evaluation/agent_eval_client.py +395 -0
- ksadk/evaluation/cloud_binding.py +80 -0
- ksadk/evaluation/cloud_converter.py +194 -0
- ksadk/evaluation/cloud_service.py +199 -0
- ksadk/evaluation/contracts.py +478 -0
- ksadk/evaluation/evalset.py +351 -0
- ksadk/evaluation/evaluators.py +627 -0
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +143 -0
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/storage.py +68 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +82 -0
- ksadk/events/__init__.py +13 -13
- ksadk/events/_v1_compat/__init__.py +1 -0
- ksadk/events/_v1_compat/models.py +299 -0
- ksadk/events/_v1_compat/parser.py +247 -0
- ksadk/events/_v1_compat/projection.py +753 -0
- ksadk/events/adapters/__init__.py +34 -0
- ksadk/events/adapters/_a2a_snapshot.py +622 -0
- ksadk/events/adapters/_a2a_support.py +275 -0
- ksadk/events/adapters/_codex_interactions.py +375 -0
- ksadk/events/adapters/_codex_items.py +801 -0
- ksadk/events/adapters/_codex_validators.py +311 -0
- ksadk/events/adapters/_langgraph_support.py +785 -0
- ksadk/events/adapters/a2a.py +841 -0
- ksadk/events/adapters/adk.py +503 -0
- ksadk/events/adapters/codex.py +717 -0
- ksadk/events/adapters/langgraph.py +745 -0
- ksadk/events/canonical.py +440 -0
- ksadk/events/canonical_replay.py +497 -0
- ksadk/events/canonical_store.py +468 -0
- ksadk/events/cold_recovery.py +257 -0
- ksadk/events/content.py +89 -0
- ksadk/events/identity.py +86 -0
- ksadk/events/pipeline.py +455 -0
- ksadk/events/projections.py +66 -0
- ksadk/events/reducer.py +661 -0
- ksadk/events/replay.py +3 -36
- ksadk/events/runtime_event.py +20 -256
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/__init__.py +3 -2
- ksadk/harness/app.py +114 -123
- ksadk/harness/config.py +1 -1
- ksadk/harness/runtime.py +467 -0
- ksadk/hermes_terminal.py +5 -0
- ksadk/identity/resolver.py +4 -3
- ksadk/interaction/__init__.py +28 -0
- ksadk/interaction/contracts.py +136 -0
- ksadk/interaction/ledger.py +189 -0
- ksadk/interaction/provider.py +122 -0
- ksadk/interaction/providers/__init__.py +52 -0
- ksadk/interaction/providers/adk.py +40 -0
- ksadk/interaction/providers/codex.py +94 -0
- ksadk/interaction/providers/langgraph.py +58 -0
- ksadk/kernel/__init__.py +117 -0
- ksadk/kernel/authorization.py +258 -0
- ksadk/kernel/bootstrap.py +1074 -0
- ksadk/kernel/contract_fingerprints.py +61 -0
- ksadk/kernel/contracts.py +362 -0
- ksadk/kernel/control.py +219 -0
- ksadk/kernel/errors.py +79 -0
- ksadk/kernel/ingress.py +1102 -0
- ksadk/kernel/mapping.py +111 -0
- ksadk/kernel/memory_store.py +1133 -0
- ksadk/kernel/postgres_store.py +1756 -0
- ksadk/kernel/recovery.py +452 -0
- ksadk/kernel/runtime_identity.py +103 -0
- ksadk/kernel/sql/001_agent_kernel.sql +120 -0
- ksadk/kernel/sqlite_store.py +1410 -0
- ksadk/kernel/state.py +145 -0
- ksadk/kernel/store.py +232 -0
- ksadk/kernel/worker.py +953 -0
- ksadk/knowledge_base/client.py +22 -4
- ksadk/knowledge_base/service.py +32 -5
- ksadk/managed_a2a_card.py +160 -0
- ksadk/managed_runtime.py +16 -9
- ksadk/memory/__init__.py +61 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
- ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
- ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- ksadk/memory/adk/long_term_memory.py +3 -2
- ksadk/memory/coordinator.py +461 -0
- ksadk/memory/events.py +202 -0
- ksadk/memory/extraction.py +228 -0
- ksadk/memory/ltm_backend_factory.py +16 -3
- ksadk/memory/models.py +316 -0
- ksadk/memory/policy.py +215 -0
- ksadk/memory/provider.py +47 -0
- ksadk/memory/provider_adapter.py +159 -0
- ksadk/memory/provider_resolver.py +71 -0
- ksadk/memory/providers/__init__.py +5 -0
- ksadk/memory/providers/local_sqlite.py +490 -0
- ksadk/memory/resolved_policy.py +145 -0
- ksadk/memory/service.py +198 -8
- ksadk/memory/tool.py +4 -0
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/config.py +22 -1
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +67 -15
- ksadk/model_proxy/server.py +168 -6
- ksadk/model_proxy/transform.py +105 -3
- ksadk/observability/__init__.py +25 -0
- ksadk/observability/session_log.py +420 -0
- ksadk/observability/trajectory.py +105 -0
- ksadk/prompts/__init__.py +71 -0
- ksadk/prompts/compiler.py +215 -0
- ksadk/prompts/models.py +82 -0
- ksadk/prompts/projection.py +89 -0
- ksadk/prompts/resolved.py +137 -0
- ksadk/prompts/sources.py +233 -0
- ksadk/runners/_langgraph_runner_streams.py +822 -0
- ksadk/runners/adk_runner.py +14 -31
- ksadk/runners/base_runner.py +44 -3
- ksadk/runners/factory.py +3 -3
- ksadk/runners/langgraph_runner.py +252 -433
- ksadk/runners/utils/__init__.py +0 -10
- ksadk/runners/utils/loader.py +16 -0
- ksadk/runtime/__init__.py +67 -41
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +199 -16
- ksadk/runtime/conversation_execution.py +687 -0
- ksadk/runtime/executor.py +392 -0
- ksadk/runtime/factory.py +342 -0
- ksadk/runtime/framework_adapters.py +6 -12
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +61 -0
- ksadk/runtime/preprocessing.py +49 -14
- ksadk/runtime/runner_adapter.py +330 -391
- ksadk/runtime/runner_loading.py +36 -0
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +36 -3
- ksadk/server/__init__.py +39 -4
- ksadk/server/app.py +18 -492
- ksadk/server/composition.py +36 -2
- ksadk/server/factory.py +210 -149
- ksadk/server/routes/common.py +15 -46
- ksadk/server/routes/control.py +13 -9
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/misc.py +10 -33
- ksadk/server/routes/models.py +14 -6
- ksadk/server/routes/openai_compat.py +129 -27
- ksadk/server/routes/projection.py +84 -16
- ksadk/server/routes/run.py +347 -460
- ksadk/server/routes/sessions.py +37 -40
- ksadk/server/routes/streaming.py +52 -7
- ksadk/server/routes/workspace.py +54 -21
- ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
- ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
- ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
- ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
- ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
- ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
- ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
- ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
- ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
- ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
- ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
- ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
- ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
- ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
- ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
- ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
- ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
- ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
- ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
- ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
- ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
- ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
- ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
- ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
- ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
- ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
- ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
- ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
- ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
- ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
- ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
- ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
- ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
- ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
- ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
- ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
- ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
- ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
- ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
- ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
- ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
- ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
- ksadk/server/static/index.html +2 -2
- ksadk/sessions/__init__.py +26 -9
- ksadk/sessions/_local_service_sync.py +688 -0
- ksadk/sessions/_local_tables.py +21 -0
- ksadk/sessions/_postgres_schema.py +305 -0
- ksadk/sessions/_postgres_tables.py +15 -0
- ksadk/sessions/base.py +80 -1
- ksadk/sessions/in_memory.py +53 -2
- ksadk/sessions/local_service.py +63 -604
- ksadk/sessions/postgres_service.py +58 -148
- ksadk/sessions/resilient.py +35 -1
- ksadk/skills/runtime/backends/e2b.py +1 -1
- ksadk/skills/service_client.py +13 -3
- ksadk/studio/__init__.py +5 -0
- ksadk/studio/agent_avatar_assets.py +120 -0
- ksadk/studio/agent_lifecycle.py +51 -0
- ksadk/studio/api.py +1598 -0
- ksadk/studio/api_catalog_routes.py +324 -0
- ksadk/studio/api_contracts.py +286 -0
- ksadk/studio/api_helpers.py +132 -0
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +749 -0
- ksadk/studio/authoring_coordinator.py +1017 -0
- ksadk/studio/builder.py +397 -0
- ksadk/studio/capabilities.py +368 -0
- ksadk/studio/cloud.py +2099 -0
- ksadk/studio/codex_agent_service.py +694 -0
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +437 -0
- ksadk/studio/codex_manifest.py +307 -0
- ksadk/studio/codex_run.py +395 -0
- ksadk/studio/compiler.py +190 -0
- ksadk/studio/contracts.py +755 -0
- ksadk/studio/errors.py +47 -0
- ksadk/studio/event_store.py +214 -0
- ksadk/studio/framework_run.py +342 -0
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/identifiers.py +33 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/mcp_runtime.py +229 -0
- ksadk/studio/model_client.py +629 -0
- ksadk/studio/model_profile_service.py +214 -0
- ksadk/studio/operations.py +275 -0
- ksadk/studio/otel_trace.py +1060 -0
- ksadk/studio/pagination.py +182 -0
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/python_tool_inspection.py +183 -0
- ksadk/studio/repository.py +327 -0
- ksadk/studio/resource_catalog.py +1260 -0
- ksadk/studio/run_service.py +1133 -0
- ksadk/studio/runtime_catalog.py +74 -0
- ksadk/studio/runtime_source.py +365 -0
- ksadk/studio/service.py +2249 -0
- ksadk/studio/shared_web.py +1004 -0
- ksadk/studio/skill_discovery.py +628 -0
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
- ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- ksadk/studio/static/index.html +15 -0
- ksadk/studio/templates.py +722 -0
- ksadk/studio/validator.py +265 -0
- ksadk/studio/workspace.py +135 -0
- ksadk/tools/gateway.py +9 -0
- ksadk/toolsets/__init__.py +27 -1
- ksadk/toolsets/a2a.py +426 -0
- ksadk/tracing/__init__.py +19 -21
- ksadk/tracing/setup.py +317 -403
- ksadk/tracing/span_utils.py +0 -44
- ksadk/version.py +1 -1
- ksadk_runtime_common/memory_backend/registry.py +56 -56
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk_runtime_common/workspace_files/router.py +8 -2
- agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
- ksadk/a2a/card_builder.py +0 -64
- ksadk/a2a/client.py +0 -309
- ksadk/events/parser.py +0 -191
- ksadk/harness/runner.py +0 -211
- ksadk/runners/codex_runner.py +0 -132
- ksadk/runners/utils/langfuse.py +0 -230
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
- ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
- ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
- ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
- ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
- ksadk/server/static/assets/katex-DATssjp1.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
- ksadk/tracing/exporters/langfuse_exporter.py +0 -460
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
ksadk/studio/service.py
ADDED
|
@@ -0,0 +1,2249 @@
|
|
|
1
|
+
"""Application service composing Studio modules behind one local API boundary."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hashlib
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Callable, Literal, cast
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
|
|
15
|
+
from ksadk.api import AgentEngineClient
|
|
16
|
+
from ksadk.evaluation import (
|
|
17
|
+
EvaluationConfig as PublicEvaluationConfig,
|
|
18
|
+
)
|
|
19
|
+
from ksadk.evaluation import (
|
|
20
|
+
EvaluationExecutionError,
|
|
21
|
+
EvaluationNotImplementedError,
|
|
22
|
+
EvaluationStorage,
|
|
23
|
+
EvaluationStorageError,
|
|
24
|
+
TargetKind,
|
|
25
|
+
TargetRef,
|
|
26
|
+
execute_evaluation,
|
|
27
|
+
load_evalset,
|
|
28
|
+
)
|
|
29
|
+
from ksadk.evaluation import (
|
|
30
|
+
EvaluationRequest as PublicEvaluationRequest,
|
|
31
|
+
)
|
|
32
|
+
from ksadk.evaluation.evalset import EvalSetParseError
|
|
33
|
+
from ksadk.evaluation.evidence import EvidenceStore
|
|
34
|
+
from ksadk.evaluation.studio_build_adapter import (
|
|
35
|
+
StudioBuildResolution,
|
|
36
|
+
StudioBuildTargetAdapter,
|
|
37
|
+
StudioBuildTargetError,
|
|
38
|
+
)
|
|
39
|
+
from ksadk.events.store import RuntimeEventStore
|
|
40
|
+
from ksadk.observability.session_log import SessionLogError, export_session_log
|
|
41
|
+
from ksadk.observability.trajectory import encode_sse, project_trajectory_event
|
|
42
|
+
from ksadk.runtime import RuntimeExecutor, build_default_runtime_registry
|
|
43
|
+
from ksadk.sessions.local_service import LocalSessionService
|
|
44
|
+
from ksadk.studio.agent_avatar_assets import AgentAvatarAssetStore
|
|
45
|
+
from ksadk.studio.agent_lifecycle import delete_framework_agent
|
|
46
|
+
from ksadk.studio.authoring_coordinator import StudioAuthoringCoordinator
|
|
47
|
+
from ksadk.studio.builder import AgentBundleBuilder
|
|
48
|
+
from ksadk.studio.capabilities import builtin_tool_contracts
|
|
49
|
+
from ksadk.studio.cloud import (
|
|
50
|
+
CloudDeploymentGateway,
|
|
51
|
+
CloudDeploymentService,
|
|
52
|
+
DirectAgentEngineCloudDeploymentGateway,
|
|
53
|
+
UnavailableCloudGateway,
|
|
54
|
+
)
|
|
55
|
+
from ksadk.studio.codex_agent_service import CodexAgentService, CodexDraftRepository
|
|
56
|
+
from ksadk.studio.codex_builder import (
|
|
57
|
+
CodexBuildRecord,
|
|
58
|
+
CodexBuildRepository,
|
|
59
|
+
CodexStudioBuilder,
|
|
60
|
+
RuntimeInspector,
|
|
61
|
+
current_proxy_mode,
|
|
62
|
+
normalize_proxy_mode,
|
|
63
|
+
proxy_mode_env_value,
|
|
64
|
+
)
|
|
65
|
+
from ksadk.studio.codex_manifest import (
|
|
66
|
+
CodexAgentManifest,
|
|
67
|
+
CodexManifestRepository,
|
|
68
|
+
)
|
|
69
|
+
from ksadk.studio.codex_run import CodexRunSpecResolver
|
|
70
|
+
from ksadk.studio.compiler import AgentCompiler
|
|
71
|
+
from ksadk.studio.contracts import (
|
|
72
|
+
AgentAppearance,
|
|
73
|
+
AgentBindings,
|
|
74
|
+
AgentDraft,
|
|
75
|
+
AgentSpec,
|
|
76
|
+
AgentTemplateComposeRequest,
|
|
77
|
+
AgentTemplateComposition,
|
|
78
|
+
BuildStatus,
|
|
79
|
+
DeploymentRequest,
|
|
80
|
+
Operation,
|
|
81
|
+
OperationKind,
|
|
82
|
+
RunEvent,
|
|
83
|
+
RunStatus,
|
|
84
|
+
RuntimeRef,
|
|
85
|
+
ToolContract,
|
|
86
|
+
)
|
|
87
|
+
from ksadk.studio.errors import StudioError
|
|
88
|
+
from ksadk.studio.event_store import RunEventStore
|
|
89
|
+
from ksadk.studio.framework_run import FrameworkRunSpecResolver
|
|
90
|
+
from ksadk.studio.mcp_runtime import MCPRuntimeAdapter
|
|
91
|
+
from ksadk.studio.model_client import CredentialResolver, OpenAICompatibleModelClient
|
|
92
|
+
from ksadk.studio.model_profile_service import test_model_profile_connection
|
|
93
|
+
from ksadk.studio.operations import OperationManager
|
|
94
|
+
from ksadk.studio.repository import AgentDraftRepository, BuildRepository, load_yaml_file
|
|
95
|
+
from ksadk.studio.resource_catalog import LocalResourceCatalog
|
|
96
|
+
from ksadk.studio.run_service import StudioRunService
|
|
97
|
+
from ksadk.studio.runtime_catalog import inspect_runtime_catalog
|
|
98
|
+
from ksadk.studio.runtime_source import materialize_generated_runtime_source
|
|
99
|
+
from ksadk.studio.templates import (
|
|
100
|
+
compose_blank_agent,
|
|
101
|
+
compose_research_agent,
|
|
102
|
+
default_agent_spec,
|
|
103
|
+
list_agent_templates,
|
|
104
|
+
)
|
|
105
|
+
from ksadk.studio.validator import AgentValidator
|
|
106
|
+
from ksadk.studio.workspace import Workspace
|
|
107
|
+
|
|
108
|
+
_TRAJECTORY_KEEPALIVE_SECONDS = 15.0
|
|
109
|
+
_EVALUATION_TARGET_LABELS = {
|
|
110
|
+
TargetKind.A2A: "A2A Agent",
|
|
111
|
+
TargetKind.LOCAL_SOURCE: "本地源码",
|
|
112
|
+
TargetKind.STUDIO_BUILD: "Studio Build",
|
|
113
|
+
TargetKind.CODEX_WORKTREE: "Codex Worktree",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True)
|
|
118
|
+
class _OperationResource:
|
|
119
|
+
id: str
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class StudioService:
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
root: Path | str,
|
|
126
|
+
*,
|
|
127
|
+
model_client: OpenAICompatibleModelClient | None = None,
|
|
128
|
+
credential_resolver: CredentialResolver | None = None,
|
|
129
|
+
cloud_gateway: CloudDeploymentGateway | None = None,
|
|
130
|
+
codex_runtime_inspector: RuntimeInspector | None = None,
|
|
131
|
+
runtime_executor: RuntimeExecutor | None = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
self.workspace = Workspace(root)
|
|
134
|
+
self.workspace.initialize()
|
|
135
|
+
self._apply_persisted_settings()
|
|
136
|
+
self.avatar_assets = AgentAvatarAssetStore(self.workspace)
|
|
137
|
+
self.drafts = AgentDraftRepository(self.workspace)
|
|
138
|
+
self.catalog = LocalResourceCatalog(self.workspace)
|
|
139
|
+
self.builds = BuildRepository(self.workspace)
|
|
140
|
+
self.validator = AgentValidator()
|
|
141
|
+
self.builder = AgentBundleBuilder(
|
|
142
|
+
self.workspace,
|
|
143
|
+
compiler=AgentCompiler(
|
|
144
|
+
self.workspace,
|
|
145
|
+
validator=self.validator,
|
|
146
|
+
catalog=self.catalog,
|
|
147
|
+
),
|
|
148
|
+
repository=self.builds,
|
|
149
|
+
)
|
|
150
|
+
self.event_store = RunEventStore(self.workspace)
|
|
151
|
+
self.session_service = LocalSessionService(project_dir=str(self.workspace.root))
|
|
152
|
+
self.runtime_events = RuntimeEventStore(self.session_service)
|
|
153
|
+
self.codex_manifests = CodexManifestRepository(self.workspace)
|
|
154
|
+
self.codex_builds = CodexBuildRepository(self.workspace)
|
|
155
|
+
self.codex_drafts = CodexDraftRepository(self.workspace)
|
|
156
|
+
codex_builder_kwargs = {}
|
|
157
|
+
if codex_runtime_inspector is not None:
|
|
158
|
+
codex_builder_kwargs["runtime_inspector"] = codex_runtime_inspector
|
|
159
|
+
self.codex_builder = CodexStudioBuilder(
|
|
160
|
+
self.workspace,
|
|
161
|
+
manifest_repository=self.codex_manifests,
|
|
162
|
+
build_repository=self.codex_builds,
|
|
163
|
+
resource_catalog=self.catalog,
|
|
164
|
+
draft_repository=self.codex_drafts,
|
|
165
|
+
**codex_builder_kwargs,
|
|
166
|
+
)
|
|
167
|
+
self.runtime_executor = runtime_executor or RuntimeExecutor(
|
|
168
|
+
build_default_runtime_registry()
|
|
169
|
+
)
|
|
170
|
+
self.run_service = StudioRunService(
|
|
171
|
+
self.workspace,
|
|
172
|
+
self.runtime_executor,
|
|
173
|
+
event_store=self.event_store,
|
|
174
|
+
session_service=self.session_service,
|
|
175
|
+
runtime_events=self.runtime_events,
|
|
176
|
+
)
|
|
177
|
+
self.credentials = (
|
|
178
|
+
credential_resolver
|
|
179
|
+
or getattr(model_client, "credential_resolver", None)
|
|
180
|
+
or CredentialResolver(self.workspace)
|
|
181
|
+
)
|
|
182
|
+
self.codex_runs = CodexRunSpecResolver(
|
|
183
|
+
self.workspace,
|
|
184
|
+
build_repository=self.codex_builds,
|
|
185
|
+
manifest_repository=self.codex_manifests,
|
|
186
|
+
credential_resolver=self.credentials,
|
|
187
|
+
resource_catalog=self.catalog,
|
|
188
|
+
)
|
|
189
|
+
self.framework_runs = FrameworkRunSpecResolver(
|
|
190
|
+
self.workspace,
|
|
191
|
+
build_repository=self.builds,
|
|
192
|
+
)
|
|
193
|
+
runtime_model_client = model_client or OpenAICompatibleModelClient(
|
|
194
|
+
credential_resolver=self.credentials
|
|
195
|
+
)
|
|
196
|
+
self.model_client = runtime_model_client
|
|
197
|
+
self.mcp_runtime = MCPRuntimeAdapter(self.workspace, credentials=self.credentials)
|
|
198
|
+
self._cloud_gateway_override = cloud_gateway
|
|
199
|
+
self.cloud = CloudDeploymentService(
|
|
200
|
+
self.workspace,
|
|
201
|
+
gateway=cloud_gateway or self._configured_cloud_gateway(),
|
|
202
|
+
build_repository=self.builds,
|
|
203
|
+
)
|
|
204
|
+
self.operations = OperationManager(self.workspace)
|
|
205
|
+
self.evaluation_storage = EvaluationStorage(
|
|
206
|
+
self.workspace.resolve(".agentkit/evaluations")
|
|
207
|
+
)
|
|
208
|
+
self.authoring = StudioAuthoringCoordinator(self)
|
|
209
|
+
self.codex_agents = CodexAgentService(self)
|
|
210
|
+
|
|
211
|
+
def runtime_catalog(self) -> list[dict]:
|
|
212
|
+
return inspect_runtime_catalog(self.runtime_executor)
|
|
213
|
+
|
|
214
|
+
def codex_manifest_state(self, agent_id: str | None = None) -> dict:
|
|
215
|
+
return self.codex_agents.manifest_state(agent_id)
|
|
216
|
+
|
|
217
|
+
def save_codex_manifest(self, manifest: CodexAgentManifest) -> dict:
|
|
218
|
+
return self.codex_agents.save_manifest(manifest)
|
|
219
|
+
|
|
220
|
+
def list_codex_agents(self, *, query: str = "", limit: int = 50) -> list[AgentDraft]:
|
|
221
|
+
return self.codex_agents.list(query=query, limit=limit)
|
|
222
|
+
|
|
223
|
+
def create_codex_agent(
|
|
224
|
+
self,
|
|
225
|
+
*,
|
|
226
|
+
agent_id: str,
|
|
227
|
+
spec: AgentSpec | None,
|
|
228
|
+
name: str | None = None,
|
|
229
|
+
labels: dict[str, str] | None = None,
|
|
230
|
+
) -> AgentDraft:
|
|
231
|
+
return self.codex_agents.create(
|
|
232
|
+
agent_id=agent_id,
|
|
233
|
+
spec=spec,
|
|
234
|
+
name=name,
|
|
235
|
+
labels=labels,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def update_codex_agent(
|
|
239
|
+
self,
|
|
240
|
+
agent_id: str,
|
|
241
|
+
spec: AgentSpec,
|
|
242
|
+
*,
|
|
243
|
+
expected_revision: int,
|
|
244
|
+
name: str | None = None,
|
|
245
|
+
) -> AgentDraft:
|
|
246
|
+
return self.codex_agents.update(
|
|
247
|
+
agent_id,
|
|
248
|
+
spec,
|
|
249
|
+
expected_revision=expected_revision,
|
|
250
|
+
name=name,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def delete_codex_agent(self, agent_id: str, *, purge: bool = False) -> None:
|
|
254
|
+
self.codex_agents.delete(agent_id, purge=purge)
|
|
255
|
+
|
|
256
|
+
def codex_agent_detail(self, agent_id: str | None = None) -> dict:
|
|
257
|
+
return self.codex_agents.detail(agent_id)
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def codex_build_view(record: CodexBuildRecord) -> dict:
|
|
261
|
+
return CodexAgentService.build_view(record)
|
|
262
|
+
|
|
263
|
+
def submit_codex_build(
|
|
264
|
+
self,
|
|
265
|
+
*,
|
|
266
|
+
idempotency_key: str,
|
|
267
|
+
agent_id: str | None = None,
|
|
268
|
+
) -> Operation:
|
|
269
|
+
return self.codex_agents.submit_build(
|
|
270
|
+
idempotency_key=idempotency_key,
|
|
271
|
+
agent_id=agent_id,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def submit_codex_run(
|
|
275
|
+
self,
|
|
276
|
+
build_id: str,
|
|
277
|
+
user_input: str,
|
|
278
|
+
*,
|
|
279
|
+
session_id: str | None,
|
|
280
|
+
model: str | None = None,
|
|
281
|
+
sandbox: str | None = None,
|
|
282
|
+
approval_mode: str | None = None,
|
|
283
|
+
collaboration_mode: str | None = None,
|
|
284
|
+
goal_objective: str | None = None,
|
|
285
|
+
reasoning_effort: str | None = None,
|
|
286
|
+
runtime_input: Any = None,
|
|
287
|
+
idempotency_key: str,
|
|
288
|
+
on_event: Callable[[RunEvent], None] | None = None,
|
|
289
|
+
) -> Operation:
|
|
290
|
+
return self.codex_agents.submit_run(
|
|
291
|
+
build_id,
|
|
292
|
+
user_input,
|
|
293
|
+
session_id=session_id,
|
|
294
|
+
model=model,
|
|
295
|
+
sandbox=sandbox,
|
|
296
|
+
approval_mode=approval_mode,
|
|
297
|
+
collaboration_mode=collaboration_mode,
|
|
298
|
+
goal_objective=goal_objective,
|
|
299
|
+
reasoning_effort=reasoning_effort,
|
|
300
|
+
runtime_input=runtime_input,
|
|
301
|
+
idempotency_key=idempotency_key,
|
|
302
|
+
on_event=on_event,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
async def delete_session(self, session_id: str) -> None:
|
|
306
|
+
from ksadk.studio.errors import not_found
|
|
307
|
+
|
|
308
|
+
runs = self.event_store.list_runs(session_id=session_id)
|
|
309
|
+
if not runs:
|
|
310
|
+
raise not_found("session", session_id)
|
|
311
|
+
if any(run.status == RunStatus.RUNNING for run in runs):
|
|
312
|
+
raise StudioError(
|
|
313
|
+
"SESSION_RUN_ACTIVE",
|
|
314
|
+
"会话仍在运行,请先停止运行后再删除",
|
|
315
|
+
status_code=409,
|
|
316
|
+
details={"sessionId": session_id},
|
|
317
|
+
)
|
|
318
|
+
await self.session_service.delete_session(session_id)
|
|
319
|
+
self.event_store.delete_session(session_id)
|
|
320
|
+
|
|
321
|
+
async def _require_runtime_session(self, session_id: str) -> None:
|
|
322
|
+
if await self.session_service.get_session_metadata(session_id) is None:
|
|
323
|
+
raise StudioError(
|
|
324
|
+
"SESSION_NOT_FOUND",
|
|
325
|
+
"session 不存在",
|
|
326
|
+
status_code=404,
|
|
327
|
+
details={"id": session_id},
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
async def trajectory_page(
|
|
331
|
+
self,
|
|
332
|
+
session_id: str,
|
|
333
|
+
*,
|
|
334
|
+
before_seq_id: int | None = None,
|
|
335
|
+
invocation_id: str | None = None,
|
|
336
|
+
limit: int = 100,
|
|
337
|
+
) -> dict[str, Any]:
|
|
338
|
+
"""Return the bounded, canonical RuntimeEvent trajectory for a session."""
|
|
339
|
+
|
|
340
|
+
await self._require_runtime_session(session_id)
|
|
341
|
+
events = await self.runtime_events.list(
|
|
342
|
+
session_id,
|
|
343
|
+
before_seq=before_seq_id,
|
|
344
|
+
run_id=invocation_id,
|
|
345
|
+
limit=limit,
|
|
346
|
+
)
|
|
347
|
+
oldest_seq_id = events[0].seq if events else None
|
|
348
|
+
latest_seq_id = events[-1].seq if events else None
|
|
349
|
+
older = (
|
|
350
|
+
await self.runtime_events.list(
|
|
351
|
+
session_id,
|
|
352
|
+
before_seq=oldest_seq_id,
|
|
353
|
+
run_id=invocation_id,
|
|
354
|
+
limit=1,
|
|
355
|
+
)
|
|
356
|
+
if oldest_seq_id is not None
|
|
357
|
+
else []
|
|
358
|
+
)
|
|
359
|
+
return {
|
|
360
|
+
"items": [project_trajectory_event(event) for event in events],
|
|
361
|
+
"page": {
|
|
362
|
+
"oldestSeqId": oldest_seq_id,
|
|
363
|
+
"latestSeqId": latest_seq_id,
|
|
364
|
+
"hasMore": bool(older),
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async def stream_trajectory(
|
|
369
|
+
self,
|
|
370
|
+
session_id: str,
|
|
371
|
+
after_seq_id: int = 0,
|
|
372
|
+
*,
|
|
373
|
+
invocation_id: str | None = None,
|
|
374
|
+
):
|
|
375
|
+
await self._require_runtime_session(session_id)
|
|
376
|
+
cursor = after_seq_id
|
|
377
|
+
while True:
|
|
378
|
+
async for event in self.runtime_events.subscribe_session(
|
|
379
|
+
session_id,
|
|
380
|
+
after_seq=cursor,
|
|
381
|
+
poll_interval=min(0.25, _TRAJECTORY_KEEPALIVE_SECONDS),
|
|
382
|
+
timeout=_TRAJECTORY_KEEPALIVE_SECONDS,
|
|
383
|
+
):
|
|
384
|
+
cursor = event.seq
|
|
385
|
+
if invocation_id is not None and event.run_id != invocation_id:
|
|
386
|
+
continue
|
|
387
|
+
yield encode_sse(project_trajectory_event(event), event_id=cursor)
|
|
388
|
+
yield ": keepalive\n\n"
|
|
389
|
+
|
|
390
|
+
async def export_runtime_session(
|
|
391
|
+
self,
|
|
392
|
+
session_id: str,
|
|
393
|
+
*,
|
|
394
|
+
filename: str,
|
|
395
|
+
invocation_id: str | None = None,
|
|
396
|
+
) -> dict[str, Any]:
|
|
397
|
+
await self._require_runtime_session(session_id)
|
|
398
|
+
relative = Path(filename)
|
|
399
|
+
if (
|
|
400
|
+
not filename
|
|
401
|
+
or relative.is_absolute()
|
|
402
|
+
or len(relative.parts) != 1
|
|
403
|
+
or relative.name in {".", ".."}
|
|
404
|
+
):
|
|
405
|
+
raise StudioError(
|
|
406
|
+
"SESSION_EXPORT_FILENAME_INVALID",
|
|
407
|
+
"导出文件名必须是普通文件名",
|
|
408
|
+
status_code=422,
|
|
409
|
+
field="filename",
|
|
410
|
+
)
|
|
411
|
+
export_dir = self.workspace.resolve(".agentkit/exports")
|
|
412
|
+
export_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
413
|
+
export_dir.chmod(0o700)
|
|
414
|
+
try:
|
|
415
|
+
result = await export_session_log(
|
|
416
|
+
self.session_service,
|
|
417
|
+
session_id,
|
|
418
|
+
export_dir / relative.name,
|
|
419
|
+
invocation_id=invocation_id,
|
|
420
|
+
)
|
|
421
|
+
except SessionLogError as exc:
|
|
422
|
+
code = str(exc).partition(":")[0]
|
|
423
|
+
raise StudioError(
|
|
424
|
+
code,
|
|
425
|
+
str(exc),
|
|
426
|
+
status_code=409 if code == "SESSION_LOG_TARGET_EXISTS" else 422,
|
|
427
|
+
) from exc
|
|
428
|
+
return {
|
|
429
|
+
"path": result.path.relative_to(self.workspace.root).as_posix(),
|
|
430
|
+
"eventCount": result.event_count,
|
|
431
|
+
"firstSeqId": result.first_seq_id,
|
|
432
|
+
"lastSeqId": result.last_seq_id,
|
|
433
|
+
"exportedThroughSeqId": result.exported_through_seq_id,
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async def test_model_profile(self, resource_id: str) -> dict:
|
|
437
|
+
return await test_model_profile_connection(
|
|
438
|
+
catalog=self.catalog,
|
|
439
|
+
model_client=self.model_client,
|
|
440
|
+
resource_id=resource_id,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
def create_agent(
|
|
444
|
+
self,
|
|
445
|
+
*,
|
|
446
|
+
agent_id: str,
|
|
447
|
+
name: str,
|
|
448
|
+
description: str = "",
|
|
449
|
+
template: str = "blank",
|
|
450
|
+
spec: AgentSpec | None = None,
|
|
451
|
+
labels: dict[str, str] | None = None,
|
|
452
|
+
):
|
|
453
|
+
if self.codex_manifests.exists(agent_id):
|
|
454
|
+
from ksadk.studio.errors import StudioError
|
|
455
|
+
|
|
456
|
+
raise StudioError(
|
|
457
|
+
"AGENT_ALREADY_EXISTS",
|
|
458
|
+
"Agent ID 已存在",
|
|
459
|
+
status_code=409,
|
|
460
|
+
details={"id": agent_id},
|
|
461
|
+
)
|
|
462
|
+
resolved_spec = spec or default_agent_spec(
|
|
463
|
+
template,
|
|
464
|
+
description=description,
|
|
465
|
+
)
|
|
466
|
+
self._validate_bindings(resolved_spec.bindings)
|
|
467
|
+
self._validate_framework_binding_support(
|
|
468
|
+
resolved_spec.runtime,
|
|
469
|
+
resolved_spec.bindings,
|
|
470
|
+
)
|
|
471
|
+
draft = self.drafts.create(
|
|
472
|
+
agent_id=agent_id,
|
|
473
|
+
name=name,
|
|
474
|
+
description=description,
|
|
475
|
+
template=template,
|
|
476
|
+
spec=resolved_spec,
|
|
477
|
+
labels=labels,
|
|
478
|
+
)
|
|
479
|
+
materialize_generated_runtime_source(
|
|
480
|
+
self.workspace,
|
|
481
|
+
draft,
|
|
482
|
+
catalog=self.catalog,
|
|
483
|
+
)
|
|
484
|
+
return draft
|
|
485
|
+
|
|
486
|
+
def create_authored_agent(
|
|
487
|
+
self,
|
|
488
|
+
*,
|
|
489
|
+
name: str,
|
|
490
|
+
slug: str | None = None,
|
|
491
|
+
runtime_type: str,
|
|
492
|
+
template: str = "blank",
|
|
493
|
+
description: str = "",
|
|
494
|
+
spec: AgentSpec | None = None,
|
|
495
|
+
) -> AgentDraft:
|
|
496
|
+
return self.authoring.create(
|
|
497
|
+
name=name,
|
|
498
|
+
slug=slug,
|
|
499
|
+
runtime_type=runtime_type,
|
|
500
|
+
description=description,
|
|
501
|
+
template=template,
|
|
502
|
+
spec=spec,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def inspect_agent_import(self, content: bytes, *, filename: str) -> dict:
|
|
506
|
+
return self.authoring.inspect_import(content, filename=filename)
|
|
507
|
+
|
|
508
|
+
def commit_agent_import(
|
|
509
|
+
self,
|
|
510
|
+
inspection_token: str,
|
|
511
|
+
*,
|
|
512
|
+
name: str | None = None,
|
|
513
|
+
slug: str | None = None,
|
|
514
|
+
) -> AgentDraft:
|
|
515
|
+
return self.authoring.commit_import(
|
|
516
|
+
inspection_token,
|
|
517
|
+
name=name,
|
|
518
|
+
slug=slug,
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def inspect_agent_project(self, project_path: str) -> dict:
|
|
522
|
+
return self.authoring.inspect_project(project_path)
|
|
523
|
+
|
|
524
|
+
def commit_agent_project(
|
|
525
|
+
self,
|
|
526
|
+
inspection_token: str,
|
|
527
|
+
*,
|
|
528
|
+
name: str | None = None,
|
|
529
|
+
slug: str | None = None,
|
|
530
|
+
model_profile_id: str | None = None,
|
|
531
|
+
) -> AgentDraft:
|
|
532
|
+
return self.authoring.commit_project(
|
|
533
|
+
inspection_token,
|
|
534
|
+
name=name,
|
|
535
|
+
slug=slug,
|
|
536
|
+
model_profile_id=model_profile_id,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
async def compose_agent_conversation(
|
|
540
|
+
self,
|
|
541
|
+
*,
|
|
542
|
+
messages: list[dict[str, str]],
|
|
543
|
+
model_profile_id: str,
|
|
544
|
+
runtime_type: str = "codex",
|
|
545
|
+
agent_model_profile_ids: list[str] | None = None,
|
|
546
|
+
agent_default_model_profile_id: str | None = None,
|
|
547
|
+
tool_resource_ids: list[str] | None = None,
|
|
548
|
+
mcp_resource_ids: list[str] | None = None,
|
|
549
|
+
skill_resource_ids: list[str] | None = None,
|
|
550
|
+
request_id: str | None = None,
|
|
551
|
+
) -> dict:
|
|
552
|
+
return await self.authoring.compose_conversation(
|
|
553
|
+
messages=messages,
|
|
554
|
+
model_profile_id=model_profile_id,
|
|
555
|
+
runtime_type=runtime_type,
|
|
556
|
+
agent_model_profile_ids=agent_model_profile_ids,
|
|
557
|
+
agent_default_model_profile_id=agent_default_model_profile_id,
|
|
558
|
+
tool_resource_ids=tool_resource_ids,
|
|
559
|
+
mcp_resource_ids=mcp_resource_ids,
|
|
560
|
+
skill_resource_ids=skill_resource_ids,
|
|
561
|
+
request_id=request_id,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
def conversation_authoring_status(self, request_id: str) -> dict | None:
|
|
565
|
+
"""Return the in-memory stage snapshot for one authoring request."""
|
|
566
|
+
|
|
567
|
+
return self.authoring.conversation_status(request_id)
|
|
568
|
+
|
|
569
|
+
def is_codex_agent(self, agent_id: str) -> bool:
|
|
570
|
+
"""Return whether one Agent is backed by the Codex YAML contract."""
|
|
571
|
+
|
|
572
|
+
return self.codex_manifests.exists(agent_id)
|
|
573
|
+
|
|
574
|
+
def agent_runtime_type(self, agent_id: str) -> str:
|
|
575
|
+
"""Resolve runtime from the Agent itself, never from Studio process state."""
|
|
576
|
+
|
|
577
|
+
if self.is_codex_agent(agent_id):
|
|
578
|
+
return "codex"
|
|
579
|
+
draft = self.drafts.get(agent_id)
|
|
580
|
+
runtime = draft.spec.runtime
|
|
581
|
+
if runtime is None:
|
|
582
|
+
# Read compatibility for Agent drafts written before RuntimeRef existed.
|
|
583
|
+
framework = draft.metadata.labels.get("agentkit.ksyun.com/framework", "")
|
|
584
|
+
return framework.strip().lower() or "adk"
|
|
585
|
+
return runtime.type
|
|
586
|
+
|
|
587
|
+
def _agent_prompt_sources(self, agent_id: str) -> tuple[str, str, str]:
|
|
588
|
+
"""Resolve the canonical prompt inputs used by preview and execution."""
|
|
589
|
+
|
|
590
|
+
if self.is_codex_agent(agent_id):
|
|
591
|
+
snapshot = self.codex_manifests.load(agent_id)
|
|
592
|
+
return (
|
|
593
|
+
snapshot.manifest.prompt or "",
|
|
594
|
+
snapshot.manifest.task_prompt or "",
|
|
595
|
+
"codex",
|
|
596
|
+
)
|
|
597
|
+
draft = self.drafts.get(agent_id)
|
|
598
|
+
instructions = draft.spec.instructions
|
|
599
|
+
return (
|
|
600
|
+
str(instructions.system or ""),
|
|
601
|
+
str(instructions.task or ""),
|
|
602
|
+
self.agent_runtime_type(agent_id),
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
def compile_prompt_preview(
|
|
606
|
+
self,
|
|
607
|
+
agent_id: str,
|
|
608
|
+
*,
|
|
609
|
+
request_instructions: str = "",
|
|
610
|
+
include_content: bool = False,
|
|
611
|
+
) -> dict:
|
|
612
|
+
"""Compile the real prompt without creating a Session, Trace, or Build."""
|
|
613
|
+
|
|
614
|
+
from ksadk.prompts.resolved import (
|
|
615
|
+
ResolvedPromptSources,
|
|
616
|
+
compile_resolved_prompt_dict,
|
|
617
|
+
get_default_platform_policy_source,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
agent_system, agent_task, runtime_type = self._agent_prompt_sources(agent_id)
|
|
621
|
+
compiled = compile_resolved_prompt_dict(
|
|
622
|
+
ResolvedPromptSources(
|
|
623
|
+
agent_system=agent_system,
|
|
624
|
+
agent_task=agent_task,
|
|
625
|
+
request_instructions=str(request_instructions or "").strip(),
|
|
626
|
+
platform_policy_source=get_default_platform_policy_source(),
|
|
627
|
+
)
|
|
628
|
+
)
|
|
629
|
+
if compiled is None:
|
|
630
|
+
return {
|
|
631
|
+
"promptVersion": "v1",
|
|
632
|
+
"contentHash": "",
|
|
633
|
+
"stablePrefixHash": "",
|
|
634
|
+
"sections": [],
|
|
635
|
+
"runtimeType": runtime_type,
|
|
636
|
+
"warnings": ["no prompt content to compile"],
|
|
637
|
+
}
|
|
638
|
+
result: dict[str, Any] = {
|
|
639
|
+
"promptVersion": compiled["prompt_compiler_version"],
|
|
640
|
+
"contentHash": compiled["prompt_content_hash"],
|
|
641
|
+
"stablePrefixHash": compiled["prompt_stable_prefix_hash"],
|
|
642
|
+
"sectionHashes": compiled["prompt_section_hashes"],
|
|
643
|
+
"tokensBySection": compiled["prompt_tokens_by_section"],
|
|
644
|
+
"estimatedTokens": compiled["prompt_estimated_tokens"],
|
|
645
|
+
"sectionCount": compiled["prompt_section_count"],
|
|
646
|
+
"runtimeType": runtime_type,
|
|
647
|
+
"platformPolicyVersion": compiled.get("prompt_platform_policy_version"),
|
|
648
|
+
"warnings": [],
|
|
649
|
+
}
|
|
650
|
+
if include_content:
|
|
651
|
+
result["content"] = compiled["prompt_content"]
|
|
652
|
+
return result
|
|
653
|
+
|
|
654
|
+
def reveal_run_prompt(self, run_id: str) -> dict:
|
|
655
|
+
"""Rebuild immutable Run prompt sections and reveal only on hash match."""
|
|
656
|
+
|
|
657
|
+
from ksadk.prompts.resolved import (
|
|
658
|
+
ResolvedPromptSources,
|
|
659
|
+
compile_resolved_prompt_dict,
|
|
660
|
+
get_default_platform_policy_source,
|
|
661
|
+
sections_from_resolved_sources,
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
record = self.event_store.get(run_id)
|
|
665
|
+
resolver = self.codex_runs if record.runtime_type == "codex" else self.framework_runs
|
|
666
|
+
spec = resolver.resolve(record.build_id, model=record.model or None)
|
|
667
|
+
config = dict(spec.request_config or {})
|
|
668
|
+
sources = ResolvedPromptSources(
|
|
669
|
+
agent_system=str(config.get("agent_system") or ""),
|
|
670
|
+
agent_task=str(config.get("agent_task") or ""),
|
|
671
|
+
request_instructions=str(config.get("instructions") or ""),
|
|
672
|
+
platform_policy_source=get_default_platform_policy_source(),
|
|
673
|
+
)
|
|
674
|
+
compiled = compile_resolved_prompt_dict(sources)
|
|
675
|
+
expected_hash = str((record.prompt_evidence or {}).get("contentHash") or "")
|
|
676
|
+
actual_hash = str((compiled or {}).get("prompt_content_hash") or "")
|
|
677
|
+
if not compiled or not expected_hash or actual_hash != expected_hash:
|
|
678
|
+
return {
|
|
679
|
+
"available": False,
|
|
680
|
+
"reason": "无法证明重建内容与本次运行一致,已拒绝展示正文。",
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
"available": True,
|
|
684
|
+
"contentHash": actual_hash,
|
|
685
|
+
"sections": [
|
|
686
|
+
{
|
|
687
|
+
"id": section.section_id,
|
|
688
|
+
"source": section.source,
|
|
689
|
+
"content": section.content,
|
|
690
|
+
}
|
|
691
|
+
for section in sections_from_resolved_sources(sources)
|
|
692
|
+
],
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async def preview_context(
|
|
696
|
+
self,
|
|
697
|
+
agent_id: str,
|
|
698
|
+
*,
|
|
699
|
+
user_input: str = "",
|
|
700
|
+
request_instructions: str = "",
|
|
701
|
+
simulated_history: list[dict[str, str]] | None = None,
|
|
702
|
+
include_content: bool = False,
|
|
703
|
+
) -> dict:
|
|
704
|
+
"""Run the production context planner without model or persistence side effects."""
|
|
705
|
+
|
|
706
|
+
from ksadk.context_engine.capabilities import capabilities_for_runtime_type
|
|
707
|
+
from ksadk.context_engine.hosted_pipeline import run_hosted_pipeline
|
|
708
|
+
from ksadk.prompts.resolved import (
|
|
709
|
+
ResolvedPromptSources,
|
|
710
|
+
compile_resolved_prompt_dict,
|
|
711
|
+
get_default_platform_policy_source,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
agent_system, agent_task, runtime_type = self._agent_prompt_sources(agent_id)
|
|
715
|
+
capabilities = capabilities_for_runtime_type(runtime_type)
|
|
716
|
+
compiled_prompt = compile_resolved_prompt_dict(
|
|
717
|
+
ResolvedPromptSources(
|
|
718
|
+
agent_system=agent_system,
|
|
719
|
+
agent_task=agent_task,
|
|
720
|
+
request_instructions=str(request_instructions or "").strip(),
|
|
721
|
+
platform_policy_source=get_default_platform_policy_source(),
|
|
722
|
+
)
|
|
723
|
+
)
|
|
724
|
+
history = [
|
|
725
|
+
{
|
|
726
|
+
"role": item.get("role", "user"),
|
|
727
|
+
"content": item.get("content", ""),
|
|
728
|
+
}
|
|
729
|
+
for item in (simulated_history or [])
|
|
730
|
+
]
|
|
731
|
+
pipeline = await run_hosted_pipeline(
|
|
732
|
+
compiled_prompt=compiled_prompt,
|
|
733
|
+
user_input=str(user_input or "").strip(),
|
|
734
|
+
history=history,
|
|
735
|
+
working_state=None,
|
|
736
|
+
model_metadata={
|
|
737
|
+
"context_window_tokens": 200_000,
|
|
738
|
+
"max_output_tokens": 32_000,
|
|
739
|
+
},
|
|
740
|
+
contributors=None,
|
|
741
|
+
integration_mode=capabilities.integration_mode,
|
|
742
|
+
accounting_accuracy="estimated",
|
|
743
|
+
session_id=f"preview-{agent_id}",
|
|
744
|
+
invocation_id=f"preview-{agent_id}",
|
|
745
|
+
)
|
|
746
|
+
if pipeline is None:
|
|
747
|
+
return {
|
|
748
|
+
"accuracy": "estimated",
|
|
749
|
+
"warnings": ["no content to plan"],
|
|
750
|
+
"items": [],
|
|
751
|
+
}
|
|
752
|
+
plan = pipeline.plan
|
|
753
|
+
assembled = pipeline.assembled
|
|
754
|
+
return {
|
|
755
|
+
"accuracy": plan.get("accounting_accuracy", "estimated"),
|
|
756
|
+
"policyVersion": plan.get("policy_version"),
|
|
757
|
+
"planId": plan.get("plan_id"),
|
|
758
|
+
"budget": {
|
|
759
|
+
"maxInputTokens": (plan.get("budget") or {}).get("max_input_tokens"),
|
|
760
|
+
"softLimitTokens": (plan.get("budget") or {}).get("soft_limit_tokens"),
|
|
761
|
+
"hardLimitTokens": (plan.get("budget") or {}).get("hard_limit_tokens"),
|
|
762
|
+
},
|
|
763
|
+
"items": plan.get("selected", []),
|
|
764
|
+
"decisions": plan.get("decisions", []),
|
|
765
|
+
"totalsByKind": plan.get("tokens_by_kind", {}),
|
|
766
|
+
"plannedInputTokens": plan.get("planned_input_tokens"),
|
|
767
|
+
"warnings": list(assembled.warnings),
|
|
768
|
+
"projection": {
|
|
769
|
+
"runtimeType": runtime_type,
|
|
770
|
+
"integrationMode": capabilities.integration_mode,
|
|
771
|
+
"promptOwner": capabilities.prompt_owner,
|
|
772
|
+
},
|
|
773
|
+
**({"system": assembled.system} if include_content else {}),
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
def detect_importable_project(self) -> dict | None:
|
|
777
|
+
"""Expose a root framework project for explicit Studio import only."""
|
|
778
|
+
from ksadk.studio.manifest_resolver import detect_manifest_kind
|
|
779
|
+
|
|
780
|
+
result = detect_manifest_kind(self.workspace.root)
|
|
781
|
+
if result.kind != "framework":
|
|
782
|
+
return None
|
|
783
|
+
import yaml
|
|
784
|
+
|
|
785
|
+
try:
|
|
786
|
+
payload = yaml.safe_load(result.path.read_text(encoding="utf-8-sig")) or {}
|
|
787
|
+
except Exception: # noqa: BLE001
|
|
788
|
+
return None
|
|
789
|
+
return {
|
|
790
|
+
"kind": "framework",
|
|
791
|
+
"runtimeType": result.framework or result.runtime_type,
|
|
792
|
+
"name": str(payload.get("name") or self.workspace.root.name or "imported-agent"),
|
|
793
|
+
"model": str(payload.get("model") or ""),
|
|
794
|
+
"prompt": str(payload.get("prompt") or payload.get("instruction") or ""),
|
|
795
|
+
"task": str(payload.get("task") or ""),
|
|
796
|
+
"manifestPath": "agentengine.yaml",
|
|
797
|
+
"requiresConfirmation": True,
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
def import_root_project(
|
|
801
|
+
self,
|
|
802
|
+
*,
|
|
803
|
+
name: str | None = None,
|
|
804
|
+
slug: str | None = None,
|
|
805
|
+
) -> AgentDraft:
|
|
806
|
+
"""Import an explicitly detected root Framework project as a Studio draft."""
|
|
807
|
+
|
|
808
|
+
importable = self.detect_importable_project()
|
|
809
|
+
if importable is None:
|
|
810
|
+
raise StudioError(
|
|
811
|
+
"PROJECT_NOT_IMPORTABLE",
|
|
812
|
+
"当前工作区根没有可导入的 Framework 项目",
|
|
813
|
+
status_code=422,
|
|
814
|
+
)
|
|
815
|
+
inspection = self.inspect_agent_project(".")
|
|
816
|
+
return self.commit_agent_project(
|
|
817
|
+
inspection["inspectionToken"],
|
|
818
|
+
name=name or importable.get("name"),
|
|
819
|
+
slug=slug or importable.get("name"),
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
def list_agents(self, *, query: str = "", limit: int = 50) -> list[AgentDraft]:
|
|
823
|
+
"""List all local Agents from one registry view across runtime types."""
|
|
824
|
+
|
|
825
|
+
if limit < 1:
|
|
826
|
+
return []
|
|
827
|
+
normalized = query.strip().lower()
|
|
828
|
+
combined: dict[str, AgentDraft] = {
|
|
829
|
+
draft.metadata.id: draft for draft in self.list_codex_agents(query=query, limit=limit)
|
|
830
|
+
}
|
|
831
|
+
for draft in self.drafts.list(query=query, limit=limit):
|
|
832
|
+
combined.setdefault(draft.metadata.id, draft)
|
|
833
|
+
values = list(combined.values())
|
|
834
|
+
if normalized:
|
|
835
|
+
values = [
|
|
836
|
+
item
|
|
837
|
+
for item in values
|
|
838
|
+
if normalized in item.metadata.id.lower()
|
|
839
|
+
or normalized in item.metadata.name.lower()
|
|
840
|
+
]
|
|
841
|
+
return values[:limit]
|
|
842
|
+
|
|
843
|
+
def agent_detail(self, agent_id: str) -> dict:
|
|
844
|
+
if self.is_codex_agent(agent_id):
|
|
845
|
+
return self.codex_agent_detail(agent_id)
|
|
846
|
+
draft = self.drafts.get(agent_id)
|
|
847
|
+
return {
|
|
848
|
+
"draft": draft,
|
|
849
|
+
"builds": self.builds.list_for_agent(agent_id)[:10],
|
|
850
|
+
"validation": self.validator.validate(draft),
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
def create_studio_agent(
|
|
854
|
+
self,
|
|
855
|
+
*,
|
|
856
|
+
agent_id: str,
|
|
857
|
+
name: str,
|
|
858
|
+
description: str = "",
|
|
859
|
+
template: str = "blank",
|
|
860
|
+
spec: AgentSpec | None = None,
|
|
861
|
+
runtime: RuntimeRef | None = None,
|
|
862
|
+
labels: dict[str, str] | None = None,
|
|
863
|
+
) -> AgentDraft:
|
|
864
|
+
"""Create one Agent and dispatch from its RuntimeRef."""
|
|
865
|
+
|
|
866
|
+
resolved_spec = (spec or default_agent_spec(template, description=description)).model_copy(
|
|
867
|
+
deep=True
|
|
868
|
+
)
|
|
869
|
+
selected = runtime or resolved_spec.runtime
|
|
870
|
+
resolved_spec.runtime = selected
|
|
871
|
+
if selected is not None and selected.type == "codex":
|
|
872
|
+
return cast(
|
|
873
|
+
AgentDraft,
|
|
874
|
+
self.create_codex_agent(
|
|
875
|
+
agent_id=agent_id,
|
|
876
|
+
spec=resolved_spec,
|
|
877
|
+
name=name,
|
|
878
|
+
labels=labels,
|
|
879
|
+
),
|
|
880
|
+
)
|
|
881
|
+
return cast(
|
|
882
|
+
AgentDraft,
|
|
883
|
+
self.create_agent(
|
|
884
|
+
agent_id=agent_id,
|
|
885
|
+
name=name,
|
|
886
|
+
description=description,
|
|
887
|
+
template=template,
|
|
888
|
+
spec=resolved_spec,
|
|
889
|
+
labels=labels,
|
|
890
|
+
),
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
def update_studio_agent(
|
|
894
|
+
self,
|
|
895
|
+
agent_id: str,
|
|
896
|
+
spec: AgentSpec,
|
|
897
|
+
*,
|
|
898
|
+
expected_revision: int,
|
|
899
|
+
name: str | None = None,
|
|
900
|
+
) -> AgentDraft:
|
|
901
|
+
if self.is_codex_agent(agent_id):
|
|
902
|
+
spec.runtime = self.agent_detail(agent_id)["draft"].spec.runtime
|
|
903
|
+
return cast(
|
|
904
|
+
AgentDraft,
|
|
905
|
+
self.update_codex_agent(
|
|
906
|
+
agent_id,
|
|
907
|
+
spec,
|
|
908
|
+
expected_revision=expected_revision,
|
|
909
|
+
name=name,
|
|
910
|
+
),
|
|
911
|
+
)
|
|
912
|
+
current = self.drafts.get(agent_id)
|
|
913
|
+
if spec.runtime is None:
|
|
914
|
+
spec.runtime = current.spec.runtime
|
|
915
|
+
elif current.spec.runtime is not None and spec.runtime.type != current.spec.runtime.type:
|
|
916
|
+
from ksadk.studio.errors import StudioError
|
|
917
|
+
|
|
918
|
+
raise StudioError(
|
|
919
|
+
"AGENT_RUNTIME_IMMUTABLE",
|
|
920
|
+
"编辑 Agent 时不能直接切换 Runtime;请通过导入/迁移创建新 Agent",
|
|
921
|
+
status_code=422,
|
|
922
|
+
field="runtime.type",
|
|
923
|
+
)
|
|
924
|
+
return cast(
|
|
925
|
+
AgentDraft,
|
|
926
|
+
self.update_agent(
|
|
927
|
+
agent_id,
|
|
928
|
+
spec,
|
|
929
|
+
expected_revision=expected_revision,
|
|
930
|
+
name=name,
|
|
931
|
+
),
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
def update_studio_agent_bindings(
|
|
935
|
+
self,
|
|
936
|
+
agent_id: str,
|
|
937
|
+
bindings: AgentBindings,
|
|
938
|
+
*,
|
|
939
|
+
expected_revision: int,
|
|
940
|
+
) -> AgentDraft:
|
|
941
|
+
detail = self.agent_detail(agent_id)
|
|
942
|
+
spec = detail["draft"].spec.model_copy(deep=True)
|
|
943
|
+
spec.bindings = bindings
|
|
944
|
+
return self.update_studio_agent(
|
|
945
|
+
agent_id,
|
|
946
|
+
spec,
|
|
947
|
+
expected_revision=expected_revision,
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
def update_studio_agent_appearance(
|
|
951
|
+
self,
|
|
952
|
+
agent_id: str,
|
|
953
|
+
appearance: AgentAppearance,
|
|
954
|
+
*,
|
|
955
|
+
expected_revision: int,
|
|
956
|
+
) -> AgentDraft:
|
|
957
|
+
if self.is_codex_agent(agent_id):
|
|
958
|
+
return self.codex_agents.update_appearance(
|
|
959
|
+
agent_id,
|
|
960
|
+
appearance,
|
|
961
|
+
expected_revision=expected_revision,
|
|
962
|
+
)
|
|
963
|
+
return self.drafts.update_appearance(
|
|
964
|
+
agent_id,
|
|
965
|
+
appearance,
|
|
966
|
+
expected_revision=expected_revision,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
def delete_studio_agent(self, agent_id: str, *, purge: bool = False) -> None:
|
|
970
|
+
if self.is_codex_agent(agent_id):
|
|
971
|
+
self.delete_codex_agent(agent_id, purge=purge)
|
|
972
|
+
return
|
|
973
|
+
delete_framework_agent(self, agent_id, purge=purge)
|
|
974
|
+
|
|
975
|
+
def validate_studio_agent(
|
|
976
|
+
self,
|
|
977
|
+
agent_id: str,
|
|
978
|
+
*,
|
|
979
|
+
revision: int,
|
|
980
|
+
level: Literal["schema", "build", "release"] = "build",
|
|
981
|
+
):
|
|
982
|
+
detail = self.agent_detail(agent_id)
|
|
983
|
+
draft = detail["draft"]
|
|
984
|
+
if draft.metadata.revision != revision:
|
|
985
|
+
from ksadk.studio.errors import StudioError
|
|
986
|
+
|
|
987
|
+
raise StudioError(
|
|
988
|
+
"AGENT_REVISION_CONFLICT",
|
|
989
|
+
"Validation revision 与当前 Agent 不一致",
|
|
990
|
+
status_code=409,
|
|
991
|
+
)
|
|
992
|
+
if self.is_codex_agent(agent_id):
|
|
993
|
+
return detail["validation"]
|
|
994
|
+
return self.validator.validate(draft, level=level)
|
|
995
|
+
|
|
996
|
+
def submit_studio_build(
|
|
997
|
+
self,
|
|
998
|
+
agent_id: str,
|
|
999
|
+
*,
|
|
1000
|
+
revision: int,
|
|
1001
|
+
idempotency_key: str,
|
|
1002
|
+
) -> Operation:
|
|
1003
|
+
runtime_type = self.agent_runtime_type(agent_id)
|
|
1004
|
+
runtime = next(
|
|
1005
|
+
(item for item in self.runtime_catalog() if item["runtimeType"] == runtime_type),
|
|
1006
|
+
None,
|
|
1007
|
+
)
|
|
1008
|
+
if runtime is None:
|
|
1009
|
+
from ksadk.studio.errors import StudioError
|
|
1010
|
+
|
|
1011
|
+
raise StudioError(
|
|
1012
|
+
"RUNTIME_NOT_REGISTERED",
|
|
1013
|
+
"Agent 引用的 RuntimeAdapter 未注册",
|
|
1014
|
+
status_code=422,
|
|
1015
|
+
details={"runtimeType": runtime_type},
|
|
1016
|
+
)
|
|
1017
|
+
if runtime["status"] != "ready":
|
|
1018
|
+
from ksadk.studio.errors import StudioError
|
|
1019
|
+
|
|
1020
|
+
raise StudioError(
|
|
1021
|
+
"RUNTIME_DEPENDENCY_MISSING",
|
|
1022
|
+
f"{runtime['displayName']} Runtime 依赖未安装",
|
|
1023
|
+
status_code=422,
|
|
1024
|
+
details={
|
|
1025
|
+
"runtimeType": runtime_type,
|
|
1026
|
+
"installCommand": runtime["installCommand"],
|
|
1027
|
+
},
|
|
1028
|
+
)
|
|
1029
|
+
if self.is_codex_agent(agent_id):
|
|
1030
|
+
detail = self.agent_detail(agent_id)
|
|
1031
|
+
if revision != detail["draft"].metadata.revision:
|
|
1032
|
+
from ksadk.studio.errors import StudioError
|
|
1033
|
+
|
|
1034
|
+
raise StudioError(
|
|
1035
|
+
"AGENT_REVISION_CONFLICT",
|
|
1036
|
+
"Build revision 与当前 Agent 不一致",
|
|
1037
|
+
status_code=409,
|
|
1038
|
+
)
|
|
1039
|
+
return self.submit_codex_build(
|
|
1040
|
+
idempotency_key=idempotency_key,
|
|
1041
|
+
agent_id=agent_id,
|
|
1042
|
+
)
|
|
1043
|
+
return self.submit_build(
|
|
1044
|
+
agent_id,
|
|
1045
|
+
revision=revision,
|
|
1046
|
+
idempotency_key=idempotency_key,
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
def build_view(self, build_id: str):
|
|
1050
|
+
try:
|
|
1051
|
+
return self.codex_build_view(self.codex_builds.get(build_id))
|
|
1052
|
+
except Exception as exc: # repository not-found is the only fallback contract
|
|
1053
|
+
if getattr(exc, "status_code", None) != 404:
|
|
1054
|
+
raise
|
|
1055
|
+
return self.builds.get(build_id)
|
|
1056
|
+
|
|
1057
|
+
def submit_studio_run(
|
|
1058
|
+
self,
|
|
1059
|
+
build_id: str,
|
|
1060
|
+
user_input: str,
|
|
1061
|
+
*,
|
|
1062
|
+
session_id: str | None,
|
|
1063
|
+
model: str | None,
|
|
1064
|
+
idempotency_key: str,
|
|
1065
|
+
sandbox: str | None = None,
|
|
1066
|
+
approval_mode: str | None = None,
|
|
1067
|
+
collaboration_mode: str | None = None,
|
|
1068
|
+
goal_objective: str | None = None,
|
|
1069
|
+
reasoning_effort: str | None = None,
|
|
1070
|
+
runtime_input: Any = None,
|
|
1071
|
+
on_event: Callable[[RunEvent], None] | None = None,
|
|
1072
|
+
) -> Operation:
|
|
1073
|
+
try:
|
|
1074
|
+
self.codex_builds.get(build_id)
|
|
1075
|
+
except Exception as exc:
|
|
1076
|
+
if getattr(exc, "status_code", None) != 404:
|
|
1077
|
+
raise
|
|
1078
|
+
else:
|
|
1079
|
+
return self.submit_codex_run(
|
|
1080
|
+
build_id,
|
|
1081
|
+
user_input,
|
|
1082
|
+
session_id=session_id,
|
|
1083
|
+
model=model,
|
|
1084
|
+
sandbox=sandbox,
|
|
1085
|
+
approval_mode=approval_mode,
|
|
1086
|
+
collaboration_mode=collaboration_mode,
|
|
1087
|
+
goal_objective=goal_objective,
|
|
1088
|
+
reasoning_effort=reasoning_effort,
|
|
1089
|
+
runtime_input=runtime_input,
|
|
1090
|
+
idempotency_key=idempotency_key,
|
|
1091
|
+
on_event=on_event,
|
|
1092
|
+
)
|
|
1093
|
+
return self.submit_run(
|
|
1094
|
+
build_id,
|
|
1095
|
+
user_input,
|
|
1096
|
+
session_id=session_id,
|
|
1097
|
+
model=model,
|
|
1098
|
+
sandbox=sandbox,
|
|
1099
|
+
approval_mode=approval_mode,
|
|
1100
|
+
collaboration_mode=collaboration_mode,
|
|
1101
|
+
goal_objective=goal_objective,
|
|
1102
|
+
reasoning_effort=reasoning_effort,
|
|
1103
|
+
runtime_input=runtime_input,
|
|
1104
|
+
idempotency_key=idempotency_key,
|
|
1105
|
+
on_event=on_event,
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
def _draft_exists(self, agent_id: str) -> bool:
|
|
1109
|
+
try:
|
|
1110
|
+
self.drafts.get(agent_id)
|
|
1111
|
+
except Exception as exc:
|
|
1112
|
+
if getattr(exc, "status_code", None) == 404:
|
|
1113
|
+
return False
|
|
1114
|
+
raise
|
|
1115
|
+
return True
|
|
1116
|
+
|
|
1117
|
+
@staticmethod
|
|
1118
|
+
def list_agent_templates() -> list[dict]:
|
|
1119
|
+
return list_agent_templates()
|
|
1120
|
+
|
|
1121
|
+
def compose_agent_template(
|
|
1122
|
+
self,
|
|
1123
|
+
template_id: str,
|
|
1124
|
+
request: AgentTemplateComposeRequest,
|
|
1125
|
+
) -> AgentTemplateComposition:
|
|
1126
|
+
composers = {
|
|
1127
|
+
"blank": compose_blank_agent,
|
|
1128
|
+
"research": compose_research_agent,
|
|
1129
|
+
}
|
|
1130
|
+
composer = composers.get(template_id)
|
|
1131
|
+
if composer is None:
|
|
1132
|
+
from ksadk.studio.errors import StudioError
|
|
1133
|
+
|
|
1134
|
+
raise StudioError(
|
|
1135
|
+
"AGENT_TEMPLATE_UNSUPPORTED",
|
|
1136
|
+
"当前模板不支持自动编排",
|
|
1137
|
+
status_code=422,
|
|
1138
|
+
field="templateId",
|
|
1139
|
+
details={"templateId": template_id},
|
|
1140
|
+
)
|
|
1141
|
+
composition = composer(self.workspace, self.catalog, request)
|
|
1142
|
+
self._validate_bindings(composition.spec.bindings)
|
|
1143
|
+
return composition
|
|
1144
|
+
|
|
1145
|
+
def update_agent(
|
|
1146
|
+
self,
|
|
1147
|
+
agent_id: str,
|
|
1148
|
+
spec: AgentSpec,
|
|
1149
|
+
*,
|
|
1150
|
+
expected_revision: int,
|
|
1151
|
+
name: str | None = None,
|
|
1152
|
+
):
|
|
1153
|
+
current = self.drafts.get(agent_id)
|
|
1154
|
+
if current.metadata.revision != expected_revision:
|
|
1155
|
+
raise StudioError(
|
|
1156
|
+
"AGENT_REVISION_CONFLICT",
|
|
1157
|
+
"Agent 已被其他操作更新",
|
|
1158
|
+
status_code=409,
|
|
1159
|
+
field="metadata.revision",
|
|
1160
|
+
details={
|
|
1161
|
+
"expected": expected_revision,
|
|
1162
|
+
"actual": current.metadata.revision,
|
|
1163
|
+
},
|
|
1164
|
+
)
|
|
1165
|
+
materializable_bindings = self._materializable_update_bindings(
|
|
1166
|
+
current.spec.bindings,
|
|
1167
|
+
spec.bindings,
|
|
1168
|
+
runtime=spec.runtime or current.spec.runtime,
|
|
1169
|
+
)
|
|
1170
|
+
self._validate_bindings(materializable_bindings)
|
|
1171
|
+
self._validate_framework_binding_support(
|
|
1172
|
+
spec.runtime or current.spec.runtime,
|
|
1173
|
+
materializable_bindings,
|
|
1174
|
+
)
|
|
1175
|
+
preview = current.model_copy(deep=True)
|
|
1176
|
+
preview.metadata.revision += 1
|
|
1177
|
+
if name is not None:
|
|
1178
|
+
preview.metadata.name = name
|
|
1179
|
+
preview.spec = spec.model_copy(deep=True)
|
|
1180
|
+
preview.spec.bindings = materializable_bindings
|
|
1181
|
+
# Generated source is a materialized view of the candidate Revision.
|
|
1182
|
+
# Complete it before the authoritative draft write so any resolver,
|
|
1183
|
+
# digest or runtime-capability failure leaves Revision/content intact.
|
|
1184
|
+
materialize_generated_runtime_source(
|
|
1185
|
+
self.workspace,
|
|
1186
|
+
preview,
|
|
1187
|
+
catalog=self.catalog,
|
|
1188
|
+
)
|
|
1189
|
+
return self.drafts.update(
|
|
1190
|
+
agent_id,
|
|
1191
|
+
spec,
|
|
1192
|
+
expected_revision=expected_revision,
|
|
1193
|
+
name=name,
|
|
1194
|
+
)
|
|
1195
|
+
|
|
1196
|
+
def update_agent_bindings(
|
|
1197
|
+
self,
|
|
1198
|
+
agent_id: str,
|
|
1199
|
+
bindings: AgentBindings,
|
|
1200
|
+
*,
|
|
1201
|
+
expected_revision: int,
|
|
1202
|
+
):
|
|
1203
|
+
draft = self.drafts.get(agent_id)
|
|
1204
|
+
spec = draft.spec.model_copy(deep=True)
|
|
1205
|
+
spec.bindings = bindings
|
|
1206
|
+
return self.update_agent(
|
|
1207
|
+
agent_id,
|
|
1208
|
+
spec,
|
|
1209
|
+
expected_revision=expected_revision,
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
def _validate_bindings(self, bindings: AgentBindings) -> None:
|
|
1213
|
+
self.catalog.resolve_model(bindings)
|
|
1214
|
+
self.catalog.resolve_models(bindings)
|
|
1215
|
+
self.catalog.policy_preview(bindings)
|
|
1216
|
+
self.catalog.resolve_mcp_servers(bindings)
|
|
1217
|
+
self.catalog.resolve_mcp_tools(bindings)
|
|
1218
|
+
self.catalog.resolve_skills(bindings)
|
|
1219
|
+
|
|
1220
|
+
def _materializable_update_bindings(
|
|
1221
|
+
self,
|
|
1222
|
+
current: AgentBindings,
|
|
1223
|
+
candidate: AgentBindings,
|
|
1224
|
+
*,
|
|
1225
|
+
runtime: RuntimeRef | None,
|
|
1226
|
+
) -> AgentBindings:
|
|
1227
|
+
"""Return bindings that can be safely resolved into this Revision.
|
|
1228
|
+
|
|
1229
|
+
Unknown bindings written by older Studio versions remain authoritative
|
|
1230
|
+
draft data, but are dormant until they re-enter the resource catalog.
|
|
1231
|
+
A new or modified unknown binding is still rejected by the normal
|
|
1232
|
+
resolver. Framework MCP bindings are likewise read-only until generated
|
|
1233
|
+
runtime source has a real injection path.
|
|
1234
|
+
"""
|
|
1235
|
+
|
|
1236
|
+
if (
|
|
1237
|
+
runtime is not None
|
|
1238
|
+
and runtime.type in {"adk", "langgraph"}
|
|
1239
|
+
and candidate.mcp_servers != current.mcp_servers
|
|
1240
|
+
):
|
|
1241
|
+
raise StudioError(
|
|
1242
|
+
"MCP_RUNTIME_INCOMPATIBLE",
|
|
1243
|
+
"当前 Runtime 尚未支持把 MCP 绑定注入生成源码;历史绑定仅可保留",
|
|
1244
|
+
status_code=422,
|
|
1245
|
+
field="spec.bindings.mcpServers",
|
|
1246
|
+
details={"runtimeType": runtime.type},
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
known = {item.resource_id for item in self.catalog.list(limit=10_000)}
|
|
1250
|
+
|
|
1251
|
+
def selected_for_materialization(
|
|
1252
|
+
selected: list,
|
|
1253
|
+
historical: list,
|
|
1254
|
+
) -> list:
|
|
1255
|
+
return [
|
|
1256
|
+
binding
|
|
1257
|
+
for binding in selected
|
|
1258
|
+
if binding.resource_id in known or binding not in historical
|
|
1259
|
+
]
|
|
1260
|
+
|
|
1261
|
+
resolved = candidate.model_copy(deep=True)
|
|
1262
|
+
if candidate.model_profile_id not in known:
|
|
1263
|
+
unchanged_model = (
|
|
1264
|
+
candidate.model_profile_id == current.model_profile_id
|
|
1265
|
+
and candidate.model_profile_ids == current.model_profile_ids
|
|
1266
|
+
)
|
|
1267
|
+
if unchanged_model:
|
|
1268
|
+
resolved.model_profile_id = None
|
|
1269
|
+
resolved.model_profile_ids = []
|
|
1270
|
+
if resolved.model_profile_id is not None:
|
|
1271
|
+
resolved.model_profile_ids = [
|
|
1272
|
+
resource_id
|
|
1273
|
+
for resource_id in resolved.model_profile_ids
|
|
1274
|
+
if resource_id in known
|
|
1275
|
+
or resource_id not in current.model_profile_ids
|
|
1276
|
+
]
|
|
1277
|
+
resolved.skills = selected_for_materialization(
|
|
1278
|
+
candidate.skills,
|
|
1279
|
+
current.skills,
|
|
1280
|
+
)
|
|
1281
|
+
resolved.tools = selected_for_materialization(
|
|
1282
|
+
candidate.tools,
|
|
1283
|
+
current.tools,
|
|
1284
|
+
)
|
|
1285
|
+
# No framework generated source consumes MCP today. Exact historical
|
|
1286
|
+
# values remain in `candidate`, while this preview omits them honestly.
|
|
1287
|
+
if runtime is not None and runtime.type in {"adk", "langgraph"}:
|
|
1288
|
+
resolved.mcp_servers = []
|
|
1289
|
+
else:
|
|
1290
|
+
resolved.mcp_servers = selected_for_materialization(
|
|
1291
|
+
candidate.mcp_servers,
|
|
1292
|
+
current.mcp_servers,
|
|
1293
|
+
)
|
|
1294
|
+
return resolved
|
|
1295
|
+
|
|
1296
|
+
def _validate_framework_binding_support(
|
|
1297
|
+
self,
|
|
1298
|
+
runtime: RuntimeRef | None,
|
|
1299
|
+
bindings: AgentBindings,
|
|
1300
|
+
) -> None:
|
|
1301
|
+
if runtime is None or runtime.type not in {"adk", "langgraph"}:
|
|
1302
|
+
return
|
|
1303
|
+
if bindings.mcp_servers:
|
|
1304
|
+
raise StudioError(
|
|
1305
|
+
"MCP_RUNTIME_INCOMPATIBLE",
|
|
1306
|
+
"当前 Runtime 尚未支持把 MCP 绑定注入生成源码",
|
|
1307
|
+
status_code=422,
|
|
1308
|
+
field="spec.bindings.mcpServers",
|
|
1309
|
+
details={"runtimeType": runtime.type},
|
|
1310
|
+
)
|
|
1311
|
+
unsupported: list[str] = []
|
|
1312
|
+
for binding in bindings.tools:
|
|
1313
|
+
if not binding.enabled:
|
|
1314
|
+
continue
|
|
1315
|
+
descriptor = self.catalog.get(binding.resource_id)
|
|
1316
|
+
contract = ToolContract.model_validate(descriptor.contract)
|
|
1317
|
+
if contract.executor not in {"builtin", "python"}:
|
|
1318
|
+
unsupported.append(binding.resource_id)
|
|
1319
|
+
if unsupported:
|
|
1320
|
+
raise StudioError(
|
|
1321
|
+
"TOOL_RUNTIME_INCOMPATIBLE",
|
|
1322
|
+
"当前 Runtime 仅支持 builtin/python Tool",
|
|
1323
|
+
status_code=422,
|
|
1324
|
+
field="spec.bindings.tools",
|
|
1325
|
+
details={
|
|
1326
|
+
"runtimeType": runtime.type,
|
|
1327
|
+
"resourceIds": unsupported,
|
|
1328
|
+
},
|
|
1329
|
+
)
|
|
1330
|
+
|
|
1331
|
+
def validate_agent(
|
|
1332
|
+
self,
|
|
1333
|
+
agent_id: str,
|
|
1334
|
+
*,
|
|
1335
|
+
level: Literal["schema", "build", "release"] = "build",
|
|
1336
|
+
):
|
|
1337
|
+
return self.validator.validate(self.drafts.get(agent_id), level=level)
|
|
1338
|
+
|
|
1339
|
+
def submit_build(
|
|
1340
|
+
self,
|
|
1341
|
+
agent_id: str,
|
|
1342
|
+
*,
|
|
1343
|
+
revision: int,
|
|
1344
|
+
idempotency_key: str,
|
|
1345
|
+
) -> Operation:
|
|
1346
|
+
draft = self.drafts.get(agent_id)
|
|
1347
|
+
if draft.metadata.revision != revision:
|
|
1348
|
+
from ksadk.studio.errors import StudioError
|
|
1349
|
+
|
|
1350
|
+
raise StudioError(
|
|
1351
|
+
"AGENT_REVISION_CONFLICT",
|
|
1352
|
+
"Build revision 与当前 Agent 不一致",
|
|
1353
|
+
status_code=409,
|
|
1354
|
+
details={"expected": revision, "actual": draft.metadata.revision},
|
|
1355
|
+
)
|
|
1356
|
+
snapshot = draft.model_copy(deep=True)
|
|
1357
|
+
|
|
1358
|
+
async def runner(_operation_id: str):
|
|
1359
|
+
return await asyncio.to_thread(self.builder.build, snapshot)
|
|
1360
|
+
|
|
1361
|
+
return self.operations.submit(
|
|
1362
|
+
kind=OperationKind.BUILD,
|
|
1363
|
+
resource_id=agent_id,
|
|
1364
|
+
idempotency_key=idempotency_key,
|
|
1365
|
+
runner=runner,
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
def submit_run(
|
|
1369
|
+
self,
|
|
1370
|
+
build_id: str,
|
|
1371
|
+
user_input: str,
|
|
1372
|
+
*,
|
|
1373
|
+
session_id: str | None,
|
|
1374
|
+
model: str | None = None,
|
|
1375
|
+
sandbox: str | None = None,
|
|
1376
|
+
approval_mode: str | None = None,
|
|
1377
|
+
collaboration_mode: str | None = None,
|
|
1378
|
+
goal_objective: str | None = None,
|
|
1379
|
+
reasoning_effort: str | None = None,
|
|
1380
|
+
runtime_input: Any = None,
|
|
1381
|
+
idempotency_key: str,
|
|
1382
|
+
on_event: Callable[[RunEvent], None] | None = None,
|
|
1383
|
+
) -> Operation:
|
|
1384
|
+
async def runner(_operation_id: str):
|
|
1385
|
+
return await self.run_build(
|
|
1386
|
+
build_id,
|
|
1387
|
+
user_input,
|
|
1388
|
+
session_id,
|
|
1389
|
+
model=model,
|
|
1390
|
+
sandbox=sandbox,
|
|
1391
|
+
approval_mode=approval_mode,
|
|
1392
|
+
collaboration_mode=collaboration_mode,
|
|
1393
|
+
goal_objective=goal_objective,
|
|
1394
|
+
reasoning_effort=reasoning_effort,
|
|
1395
|
+
runtime_input=runtime_input,
|
|
1396
|
+
on_event=on_event,
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
return self.operations.submit(
|
|
1400
|
+
kind=OperationKind.RUN,
|
|
1401
|
+
resource_id=build_id,
|
|
1402
|
+
idempotency_key=idempotency_key,
|
|
1403
|
+
runner=runner,
|
|
1404
|
+
)
|
|
1405
|
+
|
|
1406
|
+
async def run_build(
|
|
1407
|
+
self,
|
|
1408
|
+
build_id: str,
|
|
1409
|
+
user_input: str,
|
|
1410
|
+
session_id: str | None,
|
|
1411
|
+
*,
|
|
1412
|
+
model: str | None = None,
|
|
1413
|
+
sandbox: str | None = None,
|
|
1414
|
+
approval_mode: str | None = None,
|
|
1415
|
+
collaboration_mode: str | None = None,
|
|
1416
|
+
goal_objective: str | None = None,
|
|
1417
|
+
reasoning_effort: str | None = None,
|
|
1418
|
+
runtime_input: Any = None,
|
|
1419
|
+
on_event: Callable[[RunEvent], None] | None = None,
|
|
1420
|
+
):
|
|
1421
|
+
"""Execute any immutable Studio Build through the canonical executor."""
|
|
1422
|
+
|
|
1423
|
+
try:
|
|
1424
|
+
spec = self.codex_runs.resolve(
|
|
1425
|
+
build_id,
|
|
1426
|
+
model=model,
|
|
1427
|
+
sandbox=sandbox,
|
|
1428
|
+
approval_mode=approval_mode,
|
|
1429
|
+
)
|
|
1430
|
+
except Exception as exc:
|
|
1431
|
+
if getattr(exc, "status_code", None) != 404:
|
|
1432
|
+
raise
|
|
1433
|
+
spec = self.framework_runs.resolve(
|
|
1434
|
+
build_id,
|
|
1435
|
+
model=model,
|
|
1436
|
+
approval_mode=approval_mode,
|
|
1437
|
+
)
|
|
1438
|
+
if collaboration_mode or goal_objective or reasoning_effort:
|
|
1439
|
+
from dataclasses import replace
|
|
1440
|
+
|
|
1441
|
+
request_config = dict(spec.request_config)
|
|
1442
|
+
if collaboration_mode:
|
|
1443
|
+
request_config["collaboration_mode"] = collaboration_mode
|
|
1444
|
+
if goal_objective:
|
|
1445
|
+
request_config["goal_objective"] = goal_objective
|
|
1446
|
+
request_config["ephemeral"] = False
|
|
1447
|
+
if reasoning_effort:
|
|
1448
|
+
request_config["effort"] = reasoning_effort
|
|
1449
|
+
spec = replace(spec, request_config=request_config)
|
|
1450
|
+
return await self.run_service.run(
|
|
1451
|
+
spec,
|
|
1452
|
+
user_input,
|
|
1453
|
+
runtime_input=runtime_input,
|
|
1454
|
+
session_id=session_id,
|
|
1455
|
+
on_event=on_event,
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1458
|
+
def submit_public_evaluation(
|
|
1459
|
+
self,
|
|
1460
|
+
evalset_file: str,
|
|
1461
|
+
target: TargetRef,
|
|
1462
|
+
config: PublicEvaluationConfig,
|
|
1463
|
+
*,
|
|
1464
|
+
idempotency_key: str,
|
|
1465
|
+
) -> Operation:
|
|
1466
|
+
"""Queue the public CLI/Studio handoff without exposing adapter internals."""
|
|
1467
|
+
|
|
1468
|
+
try:
|
|
1469
|
+
path = self.workspace.resolve(evalset_file, must_exist=True)
|
|
1470
|
+
except StudioError:
|
|
1471
|
+
raise
|
|
1472
|
+
if not path.is_file():
|
|
1473
|
+
raise StudioError(
|
|
1474
|
+
"EVALSET_FILE_INVALID",
|
|
1475
|
+
"EvalSet 必须是工作区内的文件",
|
|
1476
|
+
status_code=422,
|
|
1477
|
+
field="evalsetFile",
|
|
1478
|
+
)
|
|
1479
|
+
try:
|
|
1480
|
+
evalset = load_evalset(path)
|
|
1481
|
+
except EvalSetParseError as exc:
|
|
1482
|
+
raise StudioError(
|
|
1483
|
+
exc.code,
|
|
1484
|
+
str(exc),
|
|
1485
|
+
status_code=422,
|
|
1486
|
+
field="evalsetFile",
|
|
1487
|
+
) from exc
|
|
1488
|
+
target = self._normalize_public_evaluation_target(target)
|
|
1489
|
+
request = PublicEvaluationRequest(
|
|
1490
|
+
evalset=evalset,
|
|
1491
|
+
target=target,
|
|
1492
|
+
config=config,
|
|
1493
|
+
report_dir=str(self.evaluation_storage.root),
|
|
1494
|
+
)
|
|
1495
|
+
evaluation_id = f"eval_{uuid4().hex}"
|
|
1496
|
+
|
|
1497
|
+
async def runner(operation_id: str):
|
|
1498
|
+
try:
|
|
1499
|
+
report = await execute_evaluation(
|
|
1500
|
+
request,
|
|
1501
|
+
adapter=self._public_evaluation_adapter(request),
|
|
1502
|
+
run_id=evaluation_id,
|
|
1503
|
+
on_case_started=lambda case_id, index, total: self.operations.append(
|
|
1504
|
+
operation_id,
|
|
1505
|
+
"evaluation.case.started",
|
|
1506
|
+
{"caseId": case_id, "index": index, "total": total},
|
|
1507
|
+
),
|
|
1508
|
+
)
|
|
1509
|
+
except EvaluationNotImplementedError as exc:
|
|
1510
|
+
raise StudioError(
|
|
1511
|
+
"EVALUATION_EXECUTOR_UNAVAILABLE",
|
|
1512
|
+
str(exc),
|
|
1513
|
+
status_code=501,
|
|
1514
|
+
) from exc
|
|
1515
|
+
except EvaluationExecutionError as exc:
|
|
1516
|
+
raise StudioError(
|
|
1517
|
+
"EVALUATION_EXECUTION_FAILED",
|
|
1518
|
+
str(exc),
|
|
1519
|
+
status_code=502,
|
|
1520
|
+
) from exc
|
|
1521
|
+
return _OperationResource(id=report.spec.id)
|
|
1522
|
+
|
|
1523
|
+
return self.operations.submit(
|
|
1524
|
+
kind=OperationKind.EVALUATION,
|
|
1525
|
+
resource_id=evaluation_id,
|
|
1526
|
+
idempotency_key=idempotency_key,
|
|
1527
|
+
metadata={
|
|
1528
|
+
"evalset": {"name": evalset.name, "caseCount": len(evalset.cases)},
|
|
1529
|
+
"target": {
|
|
1530
|
+
"kind": target.kind.value,
|
|
1531
|
+
"label": self._evaluation_target_label(target.kind, build_id=target.locator),
|
|
1532
|
+
},
|
|
1533
|
+
"evaluators": list(config.evaluators),
|
|
1534
|
+
},
|
|
1535
|
+
runner=runner,
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
def list_public_evaluations(self):
|
|
1539
|
+
return self.evaluation_storage.list_reports()
|
|
1540
|
+
|
|
1541
|
+
def list_public_evaluation_runs(self) -> list[dict[str, Any]]:
|
|
1542
|
+
return [
|
|
1543
|
+
self._public_evaluation_run(operation)
|
|
1544
|
+
for operation in self.operations.list(kind=OperationKind.EVALUATION)
|
|
1545
|
+
]
|
|
1546
|
+
|
|
1547
|
+
def get_public_evaluation_run(self, evaluation_id: str) -> dict[str, Any]:
|
|
1548
|
+
operation = next(
|
|
1549
|
+
(
|
|
1550
|
+
item
|
|
1551
|
+
for item in self.operations.list(kind=OperationKind.EVALUATION)
|
|
1552
|
+
if item.resource_id == evaluation_id
|
|
1553
|
+
),
|
|
1554
|
+
None,
|
|
1555
|
+
)
|
|
1556
|
+
if operation is None:
|
|
1557
|
+
raise StudioError(
|
|
1558
|
+
"EVALUATION_RUN_NOT_FOUND",
|
|
1559
|
+
"Evaluation Run 不存在",
|
|
1560
|
+
status_code=404,
|
|
1561
|
+
details={"id": evaluation_id},
|
|
1562
|
+
)
|
|
1563
|
+
return self._public_evaluation_run(operation, include_report=True)
|
|
1564
|
+
|
|
1565
|
+
def _public_evaluation_run(
|
|
1566
|
+
self, operation: Operation, *, include_report: bool = False
|
|
1567
|
+
) -> dict[str, Any]:
|
|
1568
|
+
try:
|
|
1569
|
+
report = self.evaluation_storage.read_report(operation.resource_id)
|
|
1570
|
+
except EvaluationStorageError:
|
|
1571
|
+
report = None
|
|
1572
|
+
progress = None
|
|
1573
|
+
for event in self.operations.events(operation.id):
|
|
1574
|
+
if event.type == "evaluation.case.started":
|
|
1575
|
+
progress = {
|
|
1576
|
+
"current": event.data.get("index", 0),
|
|
1577
|
+
"total": event.data.get("total", 0),
|
|
1578
|
+
"caseId": event.data.get("caseId"),
|
|
1579
|
+
}
|
|
1580
|
+
metadata = operation.metadata or {}
|
|
1581
|
+
evalset = metadata.get("evalset")
|
|
1582
|
+
target = metadata.get("target")
|
|
1583
|
+
evaluators = metadata.get("evaluators")
|
|
1584
|
+
if report is not None:
|
|
1585
|
+
if not evalset:
|
|
1586
|
+
evalset = {
|
|
1587
|
+
"name": report.spec.evalset.name,
|
|
1588
|
+
"caseCount": len(report.spec.evalset.cases),
|
|
1589
|
+
}
|
|
1590
|
+
if not target:
|
|
1591
|
+
target = {
|
|
1592
|
+
"kind": report.spec.target.kind.value,
|
|
1593
|
+
"label": self._evaluation_target_label(
|
|
1594
|
+
report.spec.target.kind,
|
|
1595
|
+
metadata=report.spec.target.metadata,
|
|
1596
|
+
),
|
|
1597
|
+
}
|
|
1598
|
+
if not evaluators:
|
|
1599
|
+
evaluators = list(report.spec.config.evaluators)
|
|
1600
|
+
payload: dict[str, Any] = {
|
|
1601
|
+
"id": operation.resource_id,
|
|
1602
|
+
"operationId": operation.id,
|
|
1603
|
+
"status": report.status.value if report else operation.status,
|
|
1604
|
+
"createdAt": operation.created_at,
|
|
1605
|
+
"completedAt": operation.completed_at,
|
|
1606
|
+
"evalset": evalset or {},
|
|
1607
|
+
"target": target or {},
|
|
1608
|
+
"evaluators": evaluators or [],
|
|
1609
|
+
"progress": progress,
|
|
1610
|
+
"summary": report.summary if report else None,
|
|
1611
|
+
"hasReport": report is not None,
|
|
1612
|
+
"error": operation.error,
|
|
1613
|
+
}
|
|
1614
|
+
if include_report:
|
|
1615
|
+
payload["report"] = report
|
|
1616
|
+
return payload
|
|
1617
|
+
|
|
1618
|
+
def _evaluation_target_label(
|
|
1619
|
+
self,
|
|
1620
|
+
kind: TargetKind,
|
|
1621
|
+
*,
|
|
1622
|
+
build_id: str | None = None,
|
|
1623
|
+
metadata: dict[str, Any] | None = None,
|
|
1624
|
+
) -> str:
|
|
1625
|
+
if kind is not TargetKind.STUDIO_BUILD:
|
|
1626
|
+
return _EVALUATION_TARGET_LABELS[kind]
|
|
1627
|
+
agent_id = str((metadata or {}).get("agentId") or "")
|
|
1628
|
+
if not agent_id and build_id:
|
|
1629
|
+
agent_id = self.builds.get(build_id).agent_id
|
|
1630
|
+
if not agent_id:
|
|
1631
|
+
return _EVALUATION_TARGET_LABELS[kind]
|
|
1632
|
+
try:
|
|
1633
|
+
return self.drafts.get(agent_id).metadata.name
|
|
1634
|
+
except StudioError as exc:
|
|
1635
|
+
if exc.status_code != 404:
|
|
1636
|
+
raise
|
|
1637
|
+
return agent_id
|
|
1638
|
+
|
|
1639
|
+
def evaluation_catalog(self) -> dict[str, list[dict]]:
|
|
1640
|
+
builds = [
|
|
1641
|
+
{
|
|
1642
|
+
"id": record.id,
|
|
1643
|
+
"agentId": record.agent_id,
|
|
1644
|
+
"runtime": record.runtime_type,
|
|
1645
|
+
"digest": _sha256_digest(record.bundle_digest),
|
|
1646
|
+
"createdAt": record.created_at.isoformat().replace("+00:00", "Z"),
|
|
1647
|
+
}
|
|
1648
|
+
for record in self.builds.list()
|
|
1649
|
+
if record.status == BuildStatus.SUCCEEDED and record.artifact_path
|
|
1650
|
+
]
|
|
1651
|
+
builds.sort(key=lambda item: item["createdAt"], reverse=True)
|
|
1652
|
+
evalsets: list[dict] = []
|
|
1653
|
+
candidates: set[Path] = set()
|
|
1654
|
+
for pattern in ("*.yaml", "*.yml", "*.json"):
|
|
1655
|
+
candidates.update(self.workspace.root.glob(pattern))
|
|
1656
|
+
candidates.update(self.workspace.root.glob(f"evaluations/**/{pattern}"))
|
|
1657
|
+
candidates.update(self.workspace.root.glob(f"agents/*/evaluations/**/{pattern}"))
|
|
1658
|
+
for path in sorted(candidates):
|
|
1659
|
+
try:
|
|
1660
|
+
evalset = load_evalset(path)
|
|
1661
|
+
except (EvalSetParseError, OSError):
|
|
1662
|
+
continue
|
|
1663
|
+
evalsets.append({
|
|
1664
|
+
"path": path.relative_to(self.workspace.root).as_posix(),
|
|
1665
|
+
"name": evalset.name,
|
|
1666
|
+
"caseCount": len(evalset.cases),
|
|
1667
|
+
"contentDigest": evalset.content_digest,
|
|
1668
|
+
})
|
|
1669
|
+
evalsets.sort(key=lambda item: item["path"])
|
|
1670
|
+
return {"builds": builds, "evalsets": evalsets}
|
|
1671
|
+
|
|
1672
|
+
def import_evaluation_file(self, content: bytes, *, filename: str) -> dict:
|
|
1673
|
+
if len(content) > 2 * 1024 * 1024:
|
|
1674
|
+
raise StudioError(
|
|
1675
|
+
"EVALSET_FILE_TOO_LARGE",
|
|
1676
|
+
"EvalSet 文件不能超过 2 MiB",
|
|
1677
|
+
status_code=413,
|
|
1678
|
+
field="file",
|
|
1679
|
+
)
|
|
1680
|
+
safe_name = Path(filename.replace("\\", "/")).name or "evalset.yaml"
|
|
1681
|
+
if Path(safe_name).suffix.lower() not in {".yaml", ".yml", ".json"}:
|
|
1682
|
+
raise StudioError(
|
|
1683
|
+
"EVALSET_FILE_TYPE_INVALID",
|
|
1684
|
+
"EvalSet 只支持 YAML 或 JSON 文件",
|
|
1685
|
+
status_code=422,
|
|
1686
|
+
field="file",
|
|
1687
|
+
)
|
|
1688
|
+
target = self.workspace.resolve(
|
|
1689
|
+
Path("evaluations/uploads") / f"{uuid4().hex[:12]}-{safe_name}"
|
|
1690
|
+
)
|
|
1691
|
+
self.workspace.atomic_write_bytes(target, content)
|
|
1692
|
+
try:
|
|
1693
|
+
evalset = load_evalset(target)
|
|
1694
|
+
except EvalSetParseError as exc:
|
|
1695
|
+
target.unlink(missing_ok=True)
|
|
1696
|
+
raise StudioError(exc.code, str(exc), status_code=422, field="file") from exc
|
|
1697
|
+
return {
|
|
1698
|
+
"path": self.workspace.relative(target),
|
|
1699
|
+
"name": evalset.name,
|
|
1700
|
+
"caseCount": len(evalset.cases),
|
|
1701
|
+
"contentDigest": evalset.content_digest,
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
def get_public_evaluation(self, evaluation_id: str):
|
|
1705
|
+
try:
|
|
1706
|
+
return self.evaluation_storage.read_report(evaluation_id)
|
|
1707
|
+
except EvaluationStorageError as exc:
|
|
1708
|
+
raise StudioError(
|
|
1709
|
+
"EVALUATION_NOT_FOUND",
|
|
1710
|
+
"Evaluation 不存在",
|
|
1711
|
+
status_code=404,
|
|
1712
|
+
details={"id": evaluation_id},
|
|
1713
|
+
) from exc
|
|
1714
|
+
|
|
1715
|
+
def _normalize_public_evaluation_target(self, target: TargetRef) -> TargetRef:
|
|
1716
|
+
if target.kind == TargetKind.A2A:
|
|
1717
|
+
parsed = urlparse(target.locator)
|
|
1718
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
1719
|
+
raise StudioError(
|
|
1720
|
+
"EVALUATION_A2A_URL_INVALID",
|
|
1721
|
+
"A2A target 必须是 http 或 https URL",
|
|
1722
|
+
status_code=422,
|
|
1723
|
+
field="target.locator",
|
|
1724
|
+
)
|
|
1725
|
+
return target
|
|
1726
|
+
if target.kind == TargetKind.STUDIO_BUILD:
|
|
1727
|
+
self._validate_evaluation_build(target.locator)
|
|
1728
|
+
return target
|
|
1729
|
+
path = self.workspace.resolve(target.locator, must_exist=True)
|
|
1730
|
+
if not path.is_dir():
|
|
1731
|
+
raise StudioError(
|
|
1732
|
+
"EVALUATION_TARGET_INVALID",
|
|
1733
|
+
"本地 target 必须是工作区内的目录",
|
|
1734
|
+
status_code=422,
|
|
1735
|
+
field="target.locator",
|
|
1736
|
+
)
|
|
1737
|
+
return target.model_copy(update={"locator": str(path)})
|
|
1738
|
+
|
|
1739
|
+
def _public_evaluation_adapter(self, request: PublicEvaluationRequest):
|
|
1740
|
+
if request.target.kind != TargetKind.STUDIO_BUILD:
|
|
1741
|
+
return None
|
|
1742
|
+
evidence_store = EvidenceStore(request.report_dir) if request.report_dir else None
|
|
1743
|
+
return StudioBuildTargetAdapter(
|
|
1744
|
+
timeout_seconds=request.config.timeout_seconds,
|
|
1745
|
+
resolve_build=self._resolve_evaluation_build,
|
|
1746
|
+
run_service=self.run_service,
|
|
1747
|
+
evidence_store=evidence_store,
|
|
1748
|
+
)
|
|
1749
|
+
|
|
1750
|
+
def _resolve_evaluation_build(self, build_id: str) -> StudioBuildResolution:
|
|
1751
|
+
try:
|
|
1752
|
+
self.codex_builds.get(build_id)
|
|
1753
|
+
except Exception as exc:
|
|
1754
|
+
if getattr(exc, "status_code", None) != 404:
|
|
1755
|
+
raise
|
|
1756
|
+
else:
|
|
1757
|
+
raise StudioError(
|
|
1758
|
+
"CODEX_BUILD_NOT_IMMUTABLE",
|
|
1759
|
+
"Codex Build 尚未冻结工作区源码,不能作为不可变评测 Target",
|
|
1760
|
+
status_code=422,
|
|
1761
|
+
field="target.locator",
|
|
1762
|
+
)
|
|
1763
|
+
build = self.builds.get(build_id)
|
|
1764
|
+
if build.status != BuildStatus.SUCCEEDED or not build.artifact_path:
|
|
1765
|
+
raise StudioBuildTargetError(
|
|
1766
|
+
"STUDIO_BUILD_NOT_READY",
|
|
1767
|
+
"Studio Build must be SUCCEEDED before evaluation",
|
|
1768
|
+
)
|
|
1769
|
+
run_spec = self.framework_runs.resolve(build_id)
|
|
1770
|
+
revision_digest = str(build.bundle_digest or build.resolved_digest).strip()
|
|
1771
|
+
if not revision_digest:
|
|
1772
|
+
raise StudioBuildTargetError(
|
|
1773
|
+
"STUDIO_BUILD_INVALID",
|
|
1774
|
+
"Studio Build is missing an immutable digest",
|
|
1775
|
+
)
|
|
1776
|
+
return StudioBuildResolution(
|
|
1777
|
+
build_id=build.id,
|
|
1778
|
+
agent_id=build.agent_id,
|
|
1779
|
+
revision_digest=revision_digest,
|
|
1780
|
+
runtime=build.runtime_type,
|
|
1781
|
+
model=run_spec.model,
|
|
1782
|
+
run_spec=run_spec,
|
|
1783
|
+
metadata={
|
|
1784
|
+
"bundleDigest": build.bundle_digest,
|
|
1785
|
+
"resolvedDigest": build.resolved_digest,
|
|
1786
|
+
"sourceDigest": build.source_digest,
|
|
1787
|
+
"sourceRevision": build.source_revision,
|
|
1788
|
+
},
|
|
1789
|
+
)
|
|
1790
|
+
|
|
1791
|
+
def _validate_evaluation_build(self, build_id: str) -> None:
|
|
1792
|
+
try:
|
|
1793
|
+
self.codex_builds.get(build_id)
|
|
1794
|
+
except Exception as exc:
|
|
1795
|
+
if getattr(exc, "status_code", None) != 404:
|
|
1796
|
+
raise
|
|
1797
|
+
else:
|
|
1798
|
+
raise StudioError(
|
|
1799
|
+
"CODEX_BUILD_NOT_IMMUTABLE",
|
|
1800
|
+
"Codex Build 尚未冻结工作区源码,不能作为不可变评测 Target",
|
|
1801
|
+
status_code=422,
|
|
1802
|
+
field="target.locator",
|
|
1803
|
+
)
|
|
1804
|
+
build = self.builds.get(build_id)
|
|
1805
|
+
if build.status != BuildStatus.SUCCEEDED or not build.artifact_path:
|
|
1806
|
+
raise StudioBuildTargetError(
|
|
1807
|
+
"STUDIO_BUILD_NOT_READY",
|
|
1808
|
+
"Studio Build must be SUCCEEDED before evaluation",
|
|
1809
|
+
)
|
|
1810
|
+
|
|
1811
|
+
def submit_deployment(
|
|
1812
|
+
self,
|
|
1813
|
+
build_id: str,
|
|
1814
|
+
request: DeploymentRequest,
|
|
1815
|
+
*,
|
|
1816
|
+
idempotency_key: str,
|
|
1817
|
+
) -> Operation:
|
|
1818
|
+
logging.getLogger(__name__).info(
|
|
1819
|
+
"deployment submitted: build=%s idempotencyKey=%s environment=%s",
|
|
1820
|
+
build_id,
|
|
1821
|
+
idempotency_key,
|
|
1822
|
+
getattr(request, "environment", "-"),
|
|
1823
|
+
)
|
|
1824
|
+
try:
|
|
1825
|
+
codex_build = self.codex_builds.get(build_id)
|
|
1826
|
+
except StudioError as exc:
|
|
1827
|
+
if exc.status_code != 404:
|
|
1828
|
+
raise
|
|
1829
|
+
else:
|
|
1830
|
+
snapshot = self.codex_manifests.load(codex_build.agent_name)
|
|
1831
|
+
if not self.codex_builder.is_current(codex_build):
|
|
1832
|
+
raise StudioError(
|
|
1833
|
+
"BUILD_NOT_CURRENT",
|
|
1834
|
+
"Codex YAML 已变更,请重新 Build 后再部署",
|
|
1835
|
+
status_code=409,
|
|
1836
|
+
details={"buildId": build_id},
|
|
1837
|
+
)
|
|
1838
|
+
# The deployed YAML records only the selected model identity. Its
|
|
1839
|
+
# credential stays in the local resolver and is materialized for
|
|
1840
|
+
# this outbound control-plane request only, never in the Build or
|
|
1841
|
+
# deployment receipt.
|
|
1842
|
+
launch = self.codex_runs.resolve(build_id)
|
|
1843
|
+
runtime_environment = dict(
|
|
1844
|
+
(launch.launch_context.config or {}).get("env") or {}
|
|
1845
|
+
)
|
|
1846
|
+
# Retrying a YAML deployment must update the Agent that the prior
|
|
1847
|
+
# receipt already created. CreateAgent can have succeeded before
|
|
1848
|
+
# a downstream Runtime-Service start failed; creating again would
|
|
1849
|
+
# leave duplicate cloud Agents and make the retry path non-idempotent.
|
|
1850
|
+
# A YAML edit necessarily produces a new immutable Build. Match
|
|
1851
|
+
# the prior receipt through that Build's immutable agent_name,
|
|
1852
|
+
# rather than by build_id, otherwise every edit attempts a second
|
|
1853
|
+
# CreateAgent and Server correctly rejects the duplicate name.
|
|
1854
|
+
replacement_candidates = []
|
|
1855
|
+
for deployment in self.cloud.list():
|
|
1856
|
+
if deployment.artifact_id != "managed-runtime" or not deployment.agent_id:
|
|
1857
|
+
continue
|
|
1858
|
+
try:
|
|
1859
|
+
deployed_build = self.codex_builds.get(deployment.build_id)
|
|
1860
|
+
except StudioError:
|
|
1861
|
+
# A malformed/legacy local receipt cannot establish
|
|
1862
|
+
# ownership of this Agent and must not block a new deploy.
|
|
1863
|
+
continue
|
|
1864
|
+
if deployed_build.agent_name == codex_build.agent_name:
|
|
1865
|
+
replacement_candidates.append((deployed_build, deployment))
|
|
1866
|
+
replacing = (
|
|
1867
|
+
max(
|
|
1868
|
+
replacement_candidates,
|
|
1869
|
+
key=lambda item: (item[0].source_revision, item[0].created_at),
|
|
1870
|
+
)[1]
|
|
1871
|
+
if replacement_candidates
|
|
1872
|
+
else None
|
|
1873
|
+
)
|
|
1874
|
+
|
|
1875
|
+
async def managed_runtime_runner(_operation_id: str):
|
|
1876
|
+
return await self.cloud.deploy_managed_runtime(
|
|
1877
|
+
build_id=build_id,
|
|
1878
|
+
agent_name=codex_build.agent_name,
|
|
1879
|
+
manifest=snapshot.source_bytes.decode("utf-8"),
|
|
1880
|
+
runtime_name=codex_build.runtime_name,
|
|
1881
|
+
runtime_version=codex_build.runtime_version,
|
|
1882
|
+
# Server canonicalizes and records its own digest. This
|
|
1883
|
+
# source digest remains the immutable Studio Build receipt.
|
|
1884
|
+
manifest_digest=codex_build.manifest_sha256,
|
|
1885
|
+
request=request,
|
|
1886
|
+
runtime_environment=runtime_environment,
|
|
1887
|
+
replacing=replacing,
|
|
1888
|
+
)
|
|
1889
|
+
|
|
1890
|
+
return self.operations.submit(
|
|
1891
|
+
kind=OperationKind.DEPLOYMENT,
|
|
1892
|
+
resource_id=build_id,
|
|
1893
|
+
idempotency_key=idempotency_key,
|
|
1894
|
+
runner=managed_runtime_runner,
|
|
1895
|
+
)
|
|
1896
|
+
|
|
1897
|
+
async def runner(_operation_id: str):
|
|
1898
|
+
return await self.cloud.deploy(build_id, request)
|
|
1899
|
+
|
|
1900
|
+
return self.operations.submit(
|
|
1901
|
+
kind=OperationKind.DEPLOYMENT,
|
|
1902
|
+
resource_id=build_id,
|
|
1903
|
+
idempotency_key=idempotency_key,
|
|
1904
|
+
runner=runner,
|
|
1905
|
+
)
|
|
1906
|
+
|
|
1907
|
+
def submit_rollback(
|
|
1908
|
+
self,
|
|
1909
|
+
deployment_id: str,
|
|
1910
|
+
*,
|
|
1911
|
+
target_build_id: str,
|
|
1912
|
+
idempotency_key: str,
|
|
1913
|
+
) -> Operation:
|
|
1914
|
+
deployment = self.cloud.get(deployment_id)
|
|
1915
|
+
if deployment.artifact_id == "managed-runtime":
|
|
1916
|
+
deployed_build = self.codex_builds.get(deployment.build_id)
|
|
1917
|
+
target_build = self.codex_builds.get(target_build_id)
|
|
1918
|
+
if target_build.agent_name != deployed_build.agent_name:
|
|
1919
|
+
raise StudioError(
|
|
1920
|
+
"MANAGED_RUNTIME_ROLLBACK_AGENT_MISMATCH",
|
|
1921
|
+
"声明式 Agent 只能回滚到同一 Agent 的 Build",
|
|
1922
|
+
status_code=409,
|
|
1923
|
+
details={
|
|
1924
|
+
"deploymentId": deployment_id,
|
|
1925
|
+
"targetBuildId": target_build_id,
|
|
1926
|
+
},
|
|
1927
|
+
)
|
|
1928
|
+
manifest = self.codex_builds.manifest_text(target_build)
|
|
1929
|
+
request = self.cloud.request_for(deployment_id)
|
|
1930
|
+
|
|
1931
|
+
async def managed_runtime_runner(_operation_id: str):
|
|
1932
|
+
return await self.cloud.deploy_managed_runtime(
|
|
1933
|
+
build_id=target_build.id,
|
|
1934
|
+
agent_name=target_build.agent_name,
|
|
1935
|
+
manifest=manifest,
|
|
1936
|
+
runtime_name=target_build.runtime_name,
|
|
1937
|
+
runtime_version=target_build.runtime_version,
|
|
1938
|
+
manifest_digest=target_build.manifest_sha256,
|
|
1939
|
+
request=request,
|
|
1940
|
+
replacing=deployment,
|
|
1941
|
+
)
|
|
1942
|
+
|
|
1943
|
+
return self.operations.submit(
|
|
1944
|
+
kind=OperationKind.DEPLOYMENT,
|
|
1945
|
+
resource_id=deployment_id,
|
|
1946
|
+
idempotency_key=idempotency_key,
|
|
1947
|
+
runner=managed_runtime_runner,
|
|
1948
|
+
)
|
|
1949
|
+
|
|
1950
|
+
deployed_build = self.builds.get(deployment.build_id)
|
|
1951
|
+
target_build = self.builds.get(target_build_id)
|
|
1952
|
+
if target_build.agent_id != deployed_build.agent_id:
|
|
1953
|
+
raise StudioError(
|
|
1954
|
+
"DEPLOYMENT_ROLLBACK_AGENT_MISMATCH",
|
|
1955
|
+
"高代码 Agent 只能回滚到同一 Agent 的 Build",
|
|
1956
|
+
status_code=409,
|
|
1957
|
+
details={
|
|
1958
|
+
"deploymentId": deployment_id,
|
|
1959
|
+
"targetBuildId": target_build_id,
|
|
1960
|
+
},
|
|
1961
|
+
)
|
|
1962
|
+
|
|
1963
|
+
async def runner(_operation_id: str):
|
|
1964
|
+
return await self.cloud.rollback(
|
|
1965
|
+
deployment_id,
|
|
1966
|
+
target_build_id=target_build_id,
|
|
1967
|
+
)
|
|
1968
|
+
|
|
1969
|
+
return self.operations.submit(
|
|
1970
|
+
kind=OperationKind.DEPLOYMENT,
|
|
1971
|
+
resource_id=deployment_id,
|
|
1972
|
+
idempotency_key=idempotency_key,
|
|
1973
|
+
runner=runner,
|
|
1974
|
+
)
|
|
1975
|
+
|
|
1976
|
+
def submit_account_agent_version_rollback(
|
|
1977
|
+
self,
|
|
1978
|
+
agent_id: str,
|
|
1979
|
+
*,
|
|
1980
|
+
version_id: str,
|
|
1981
|
+
idempotency_key: str,
|
|
1982
|
+
) -> Operation:
|
|
1983
|
+
normalized_agent_id = str(agent_id or "").strip()
|
|
1984
|
+
normalized_version_id = str(version_id or "").strip()
|
|
1985
|
+
if not normalized_agent_id:
|
|
1986
|
+
raise StudioError(
|
|
1987
|
+
"CLOUD_AGENT_NOT_FOUND",
|
|
1988
|
+
"云端 Agent 标识不能为空",
|
|
1989
|
+
status_code=404,
|
|
1990
|
+
)
|
|
1991
|
+
if not normalized_version_id:
|
|
1992
|
+
raise StudioError(
|
|
1993
|
+
"CLOUD_AGENT_VERSION_REQUIRED",
|
|
1994
|
+
"请选择要回滚的云端版本",
|
|
1995
|
+
status_code=422,
|
|
1996
|
+
field="versionId",
|
|
1997
|
+
)
|
|
1998
|
+
resource_id = f"{normalized_agent_id}:{normalized_version_id}"
|
|
1999
|
+
|
|
2000
|
+
async def runner(_operation_id: str):
|
|
2001
|
+
await self.cloud.rollback_account_agent_version(
|
|
2002
|
+
normalized_agent_id,
|
|
2003
|
+
version_id=normalized_version_id,
|
|
2004
|
+
)
|
|
2005
|
+
return _OperationResource(id=resource_id)
|
|
2006
|
+
|
|
2007
|
+
return self.operations.submit(
|
|
2008
|
+
kind=OperationKind.DEPLOYMENT,
|
|
2009
|
+
resource_id=resource_id,
|
|
2010
|
+
idempotency_key=idempotency_key,
|
|
2011
|
+
metadata={
|
|
2012
|
+
"agentId": normalized_agent_id,
|
|
2013
|
+
"targetVersionId": normalized_version_id,
|
|
2014
|
+
"source": "server-version",
|
|
2015
|
+
},
|
|
2016
|
+
runner=runner,
|
|
2017
|
+
)
|
|
2018
|
+
|
|
2019
|
+
async def deployment_dashboard_access(self, deployment_id: str) -> dict[str, str | None]:
|
|
2020
|
+
"""Return a receipt-bound private Hosted UI link on explicit user request."""
|
|
2021
|
+
|
|
2022
|
+
return await self.cloud.dashboard_access(deployment_id)
|
|
2023
|
+
|
|
2024
|
+
def deployment_operation_scope(self) -> dict[str, str]:
|
|
2025
|
+
"""Return opaque browser storage scopes without exposing cloud credentials."""
|
|
2026
|
+
|
|
2027
|
+
workspace_identity = str(self.workspace.root.resolve())
|
|
2028
|
+
region = (
|
|
2029
|
+
os.environ.get("AGENTENGINE_REGION")
|
|
2030
|
+
or os.environ.get("KSYUN_REGION")
|
|
2031
|
+
or "cn-beijing-6"
|
|
2032
|
+
).strip()
|
|
2033
|
+
access_key = (
|
|
2034
|
+
os.environ.get("KSYUN_ACCESS_KEY")
|
|
2035
|
+
or os.environ.get("KS3_ACCESS_KEY")
|
|
2036
|
+
or "unsigned"
|
|
2037
|
+
).strip()
|
|
2038
|
+
|
|
2039
|
+
def opaque(value: str) -> str:
|
|
2040
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
2041
|
+
|
|
2042
|
+
return {
|
|
2043
|
+
"workspace": opaque(workspace_identity),
|
|
2044
|
+
"cloudCredential": opaque(f"{region}\0{access_key}"),
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
def get_settings(self) -> dict[str, Any]:
|
|
2048
|
+
path = self.workspace.resolve(".agentkit/settings.yaml")
|
|
2049
|
+
data: dict[str, Any] = {}
|
|
2050
|
+
if path.is_file():
|
|
2051
|
+
try:
|
|
2052
|
+
data = load_yaml_file(path) or {}
|
|
2053
|
+
except Exception:
|
|
2054
|
+
data = {}
|
|
2055
|
+
defaults = {
|
|
2056
|
+
"sandbox": os.environ.get("KSADK_CODEX_SANDBOX", "read_only"),
|
|
2057
|
+
"buildAfterCreate": True,
|
|
2058
|
+
"codexProxy": current_proxy_mode(),
|
|
2059
|
+
"cloudRegion": os.environ.get(
|
|
2060
|
+
"AGENTENGINE_REGION", os.environ.get("KSYUN_REGION", "cn-beijing-6")
|
|
2061
|
+
),
|
|
2062
|
+
"cloudBucket": os.environ.get("KS3_BUCKET", ""),
|
|
2063
|
+
"cloudAccountId": (
|
|
2064
|
+
os.environ.get("KSYUN_ACCOUNT_ID", "").strip()
|
|
2065
|
+
),
|
|
2066
|
+
# AK/SK 不回显(避免本地 UI 回传 secret);只暴露配置状态。
|
|
2067
|
+
# 已配置 = 启动环境或已持久化 settings 里 AK+SK 齐全。
|
|
2068
|
+
"cloudAccountConfigured": bool(
|
|
2069
|
+
(
|
|
2070
|
+
os.environ.get("KSYUN_ACCESS_KEY")
|
|
2071
|
+
or os.environ.get("KS3_ACCESS_KEY", "")
|
|
2072
|
+
).strip()
|
|
2073
|
+
and (
|
|
2074
|
+
os.environ.get("KSYUN_SECRET_KEY")
|
|
2075
|
+
or os.environ.get("KS3_SECRET_KEY", "")
|
|
2076
|
+
).strip()
|
|
2077
|
+
),
|
|
2078
|
+
"cloudSignedAccountConfigured": bool(
|
|
2079
|
+
(
|
|
2080
|
+
os.environ.get("KSYUN_ACCESS_KEY")
|
|
2081
|
+
or os.environ.get("KS3_ACCESS_KEY", "")
|
|
2082
|
+
).strip()
|
|
2083
|
+
and (
|
|
2084
|
+
os.environ.get("KSYUN_SECRET_KEY")
|
|
2085
|
+
or os.environ.get("KS3_SECRET_KEY", "")
|
|
2086
|
+
).strip()
|
|
2087
|
+
),
|
|
2088
|
+
"traceContent": os.environ.get("KSADK_STUDIO_TRACE_CONTENT", "1") != "0",
|
|
2089
|
+
}
|
|
2090
|
+
defaults.update({k: v for k, v in data.items() if k in defaults and v is not None})
|
|
2091
|
+
defaults["codexProxy"] = normalize_proxy_mode(defaults.get("codexProxy"))
|
|
2092
|
+
return defaults
|
|
2093
|
+
|
|
2094
|
+
def update_settings(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
2095
|
+
allowed = {
|
|
2096
|
+
"sandbox",
|
|
2097
|
+
"buildAfterCreate",
|
|
2098
|
+
"codexProxy",
|
|
2099
|
+
"cloudRegion",
|
|
2100
|
+
"cloudBucket",
|
|
2101
|
+
"traceContent",
|
|
2102
|
+
# 云账号:AK/SK 留空 = 不修改(保留已存值);AccountID 可单独更新。
|
|
2103
|
+
"cloudAccessKey",
|
|
2104
|
+
"cloudSecretKey",
|
|
2105
|
+
"cloudAccountId",
|
|
2106
|
+
}
|
|
2107
|
+
data = {k: payload[k] for k in allowed if k in payload}
|
|
2108
|
+
if "cloudAccessKey" in data and not str(data["cloudAccessKey"]).strip():
|
|
2109
|
+
data.pop("cloudAccessKey")
|
|
2110
|
+
if "cloudSecretKey" in data and not str(data["cloudSecretKey"]).strip():
|
|
2111
|
+
data.pop("cloudSecretKey")
|
|
2112
|
+
if "cloudAccountId" in data:
|
|
2113
|
+
data["cloudAccountId"] = str(data["cloudAccountId"]).strip()
|
|
2114
|
+
if not data["cloudAccountId"]:
|
|
2115
|
+
data.pop("cloudAccountId")
|
|
2116
|
+
if data.get("sandbox") and data["sandbox"] not in {
|
|
2117
|
+
"read-only",
|
|
2118
|
+
"workspace-write",
|
|
2119
|
+
"workspace-write-auto",
|
|
2120
|
+
"full-access",
|
|
2121
|
+
"read_only",
|
|
2122
|
+
"workspace_write",
|
|
2123
|
+
"workspace_write_auto",
|
|
2124
|
+
"full_access",
|
|
2125
|
+
}:
|
|
2126
|
+
raise StudioError("SETTINGS_INVALID", "sandbox 取值非法", status_code=422)
|
|
2127
|
+
if "codexProxy" in data and data["codexProxy"] not in {"auto", "forced", "direct"}:
|
|
2128
|
+
raise StudioError("SETTINGS_INVALID", "codexProxy 取值非法", status_code=422)
|
|
2129
|
+
path = self.workspace.resolve(".agentkit/settings.yaml")
|
|
2130
|
+
self.workspace.atomic_write_yaml(path, data)
|
|
2131
|
+
# env 桥接用落盘后的全量数据:局部更新(只改 AccountID)时,
|
|
2132
|
+
# 已持久化的 AK/SK 也要继续桥接,否则签名凭证丢失。
|
|
2133
|
+
try:
|
|
2134
|
+
persisted = load_yaml_file(path) or {}
|
|
2135
|
+
except Exception:
|
|
2136
|
+
persisted = data
|
|
2137
|
+
self._apply_settings_to_env(persisted if isinstance(persisted, dict) else data)
|
|
2138
|
+
if self._cloud_gateway_override is None:
|
|
2139
|
+
self.cloud.gateway = self._configured_cloud_gateway()
|
|
2140
|
+
return self.get_settings()
|
|
2141
|
+
|
|
2142
|
+
def _apply_persisted_settings(self) -> None:
|
|
2143
|
+
"""启动时把 settings.yaml 回填到进程环境。
|
|
2144
|
+
|
|
2145
|
+
update_settings 只在 PUT 时桥接 env;重启后 env 丢失,运行解析
|
|
2146
|
+
(如 _resolve_sandbox 读 KSADK_CODEX_SANDBOX)会回落默认,表现为
|
|
2147
|
+
「设置页显示 workspace-write-auto,实际运行 read-only」。
|
|
2148
|
+
"""
|
|
2149
|
+
path = self.workspace.resolve(".agentkit/settings.yaml")
|
|
2150
|
+
if not path.is_file():
|
|
2151
|
+
return
|
|
2152
|
+
try:
|
|
2153
|
+
data = load_yaml_file(path) or {}
|
|
2154
|
+
except Exception:
|
|
2155
|
+
return
|
|
2156
|
+
if isinstance(data, dict):
|
|
2157
|
+
self._apply_settings_to_env(data)
|
|
2158
|
+
|
|
2159
|
+
@staticmethod
|
|
2160
|
+
def _apply_settings_to_env(data: dict[str, Any]) -> None:
|
|
2161
|
+
if data.get("sandbox"):
|
|
2162
|
+
os.environ["KSADK_CODEX_SANDBOX"] = data["sandbox"]
|
|
2163
|
+
if "codexProxy" in data:
|
|
2164
|
+
proxy_env = proxy_mode_env_value(data["codexProxy"])
|
|
2165
|
+
if proxy_env is None:
|
|
2166
|
+
os.environ.pop("KSADK_CODEX_USE_PROXY", None)
|
|
2167
|
+
else:
|
|
2168
|
+
os.environ["KSADK_CODEX_USE_PROXY"] = proxy_env
|
|
2169
|
+
if data.get("cloudRegion"):
|
|
2170
|
+
os.environ["AGENTENGINE_REGION"] = data["cloudRegion"]
|
|
2171
|
+
if data.get("cloudBucket"):
|
|
2172
|
+
os.environ["KS3_BUCKET"] = data["cloudBucket"]
|
|
2173
|
+
if "traceContent" in data:
|
|
2174
|
+
os.environ["KSADK_STUDIO_TRACE_CONTENT"] = "1" if data["traceContent"] else "0"
|
|
2175
|
+
# 云账号凭证:桥接到 KSYUN_* env(AgentEngineClient 的 V4 签名与
|
|
2176
|
+
# X-Ksc-Account-Id 注入都从这里取值)。
|
|
2177
|
+
if data.get("cloudAccessKey"):
|
|
2178
|
+
os.environ["KSYUN_ACCESS_KEY"] = str(data["cloudAccessKey"])
|
|
2179
|
+
if data.get("cloudSecretKey"):
|
|
2180
|
+
os.environ["KSYUN_SECRET_KEY"] = str(data["cloudSecretKey"])
|
|
2181
|
+
if data.get("cloudAccountId"):
|
|
2182
|
+
os.environ["KSYUN_ACCOUNT_ID"] = str(data["cloudAccountId"])
|
|
2183
|
+
|
|
2184
|
+
@staticmethod
|
|
2185
|
+
def _configured_cloud_gateway() -> CloudDeploymentGateway:
|
|
2186
|
+
"""Compose the existing signed Code deployment path from process-only credentials."""
|
|
2187
|
+
|
|
2188
|
+
access_key = (
|
|
2189
|
+
os.environ.get("KSYUN_ACCESS_KEY") or os.environ.get("KS3_ACCESS_KEY", "")
|
|
2190
|
+
).strip()
|
|
2191
|
+
secret_key = (
|
|
2192
|
+
os.environ.get("KSYUN_SECRET_KEY") or os.environ.get("KS3_SECRET_KEY", "")
|
|
2193
|
+
).strip()
|
|
2194
|
+
region = os.environ.get(
|
|
2195
|
+
"AGENTENGINE_REGION", os.environ.get("KSYUN_REGION", "")
|
|
2196
|
+
).strip()
|
|
2197
|
+
if not all((access_key, secret_key, region)):
|
|
2198
|
+
return UnavailableCloudGateway()
|
|
2199
|
+
control_client = AgentEngineClient(
|
|
2200
|
+
region=region,
|
|
2201
|
+
access_key=access_key,
|
|
2202
|
+
secret_key=secret_key,
|
|
2203
|
+
)
|
|
2204
|
+
stream_base_url = os.environ.get("AGENTENGINE_STREAM_SERVER_URL", "").strip()
|
|
2205
|
+
if not stream_base_url and region.lower() == "pre-online":
|
|
2206
|
+
# The pre-online KOP response path currently buffers SSE until
|
|
2207
|
+
# EOF. Its internal Server ingress validates the same V4
|
|
2208
|
+
# signature and preserves the RunAgent streaming response.
|
|
2209
|
+
stream_base_url = "http://agent-api-pre.kspmas-internal.ksyun.com"
|
|
2210
|
+
stream_client = (
|
|
2211
|
+
AgentEngineClient(
|
|
2212
|
+
base_url=stream_base_url,
|
|
2213
|
+
region=region,
|
|
2214
|
+
access_key=access_key,
|
|
2215
|
+
secret_key=secret_key,
|
|
2216
|
+
)
|
|
2217
|
+
if stream_base_url
|
|
2218
|
+
else control_client
|
|
2219
|
+
)
|
|
2220
|
+
return DirectAgentEngineCloudDeploymentGateway(
|
|
2221
|
+
region=region,
|
|
2222
|
+
client=control_client,
|
|
2223
|
+
stream_client=stream_client,
|
|
2224
|
+
bucket=os.environ.get("KS3_BUCKET", "").strip() or None,
|
|
2225
|
+
ks3_credentials={
|
|
2226
|
+
"access_key": access_key,
|
|
2227
|
+
"secret_key": secret_key,
|
|
2228
|
+
},
|
|
2229
|
+
)
|
|
2230
|
+
|
|
2231
|
+
@staticmethod
|
|
2232
|
+
def builtin_capabilities():
|
|
2233
|
+
return list(builtin_tool_contracts().values())
|
|
2234
|
+
|
|
2235
|
+
def list_capabilities(
|
|
2236
|
+
self,
|
|
2237
|
+
*,
|
|
2238
|
+
kind: str | None = None,
|
|
2239
|
+
query: str = "",
|
|
2240
|
+
) -> list[dict]:
|
|
2241
|
+
return [
|
|
2242
|
+
item.model_dump(by_alias=True, exclude_none=True, mode="json")
|
|
2243
|
+
for item in self.catalog.list(kind=kind, query=query, limit=200)
|
|
2244
|
+
]
|
|
2245
|
+
|
|
2246
|
+
|
|
2247
|
+
def _sha256_digest(value: str) -> str:
|
|
2248
|
+
digest = str(value or "").strip()
|
|
2249
|
+
return digest if digest.startswith("sha256:") else f"sha256:{digest}"
|