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
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
"""Inspect-first Agent authoring workflows used by AgentKit Studio.
|
|
2
|
+
|
|
3
|
+
Every flow returns a proposal or inspection before it mutates the canonical
|
|
4
|
+
Agent registry. Quick creation is the only one-step flow and still delegates
|
|
5
|
+
ID allocation to the server.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import copy
|
|
11
|
+
import hashlib
|
|
12
|
+
import io
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import stat
|
|
17
|
+
import zipfile
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path, PurePosixPath
|
|
20
|
+
from typing import Any, Literal, cast
|
|
21
|
+
from urllib.parse import urlparse
|
|
22
|
+
from uuid import uuid4
|
|
23
|
+
|
|
24
|
+
import yaml # type: ignore[import-untyped]
|
|
25
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
|
|
26
|
+
|
|
27
|
+
from ksadk.detection.detector import FrameworkDetector
|
|
28
|
+
from ksadk.managed_runtime import installed_runtime_version
|
|
29
|
+
from ksadk.studio.capabilities import canonical_json, sha256_digest
|
|
30
|
+
from ksadk.studio.codex_manifest import CodexAgentManifest
|
|
31
|
+
from ksadk.studio.contracts import (
|
|
32
|
+
AgentDraft,
|
|
33
|
+
AgentSpec,
|
|
34
|
+
NetworkPolicy,
|
|
35
|
+
RuntimeRef,
|
|
36
|
+
)
|
|
37
|
+
from ksadk.studio.errors import StudioError
|
|
38
|
+
from ksadk.studio.workspace import Workspace
|
|
39
|
+
|
|
40
|
+
_SLUG = re.compile(r"[^a-z0-9]+")
|
|
41
|
+
_MAX_IMPORT_BYTES = 100 * 1024 * 1024
|
|
42
|
+
_MAX_IMPORT_FILES = 2000
|
|
43
|
+
_SUPPORTED_RUNTIMES = frozenset({"codex", "adk", "langgraph"})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ConversationProposal(BaseModel):
|
|
47
|
+
model_config = ConfigDict(extra="forbid")
|
|
48
|
+
|
|
49
|
+
name: str = Field(min_length=1, max_length=128)
|
|
50
|
+
slug: str = Field(min_length=1, max_length=63)
|
|
51
|
+
runtimeType: Literal["codex", "adk", "langgraph"]
|
|
52
|
+
description: str = Field(default="", max_length=1024)
|
|
53
|
+
spec: AgentSpec
|
|
54
|
+
|
|
55
|
+
@model_validator(mode="before")
|
|
56
|
+
@classmethod
|
|
57
|
+
def migrate_prompt_only_proposal(cls, value: Any) -> Any:
|
|
58
|
+
"""Accept one release of old model output without persisting its data loss.
|
|
59
|
+
|
|
60
|
+
Older authoring prompts asked the model for only ``instructions``. Turn
|
|
61
|
+
that response into a complete AgentSpec at the boundary so callers only
|
|
62
|
+
ever consume the lossless proposal shape.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
if not isinstance(value, dict) or "spec" in value:
|
|
66
|
+
return value
|
|
67
|
+
payload = dict(value)
|
|
68
|
+
instructions = payload.pop("instructions", None)
|
|
69
|
+
payload["spec"] = {
|
|
70
|
+
"description": str(payload.get("description") or ""),
|
|
71
|
+
"instructions": instructions or {},
|
|
72
|
+
}
|
|
73
|
+
return payload
|
|
74
|
+
|
|
75
|
+
@model_validator(mode="after")
|
|
76
|
+
def validate_runtime_type(self) -> "ConversationProposal":
|
|
77
|
+
if self.spec.runtime is not None and self.spec.runtime.type != self.runtimeType:
|
|
78
|
+
raise ValueError("spec.runtime.type 必须与 runtimeType 一致")
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class ImportInspection:
|
|
84
|
+
token: str
|
|
85
|
+
kind: Literal["agent-draft", "codex-manifest"]
|
|
86
|
+
payload: dict[str, Any]
|
|
87
|
+
runtime_type: str
|
|
88
|
+
display_name: str
|
|
89
|
+
source_digest: str
|
|
90
|
+
files: tuple[str, ...]
|
|
91
|
+
staging_directory: Path
|
|
92
|
+
manifest_directory: PurePosixPath
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class AgentAuthoringService:
|
|
96
|
+
"""Pure inspection and staging support; StudioService owns final commits."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, workspace: Workspace) -> None:
|
|
99
|
+
self.workspace = workspace
|
|
100
|
+
self.root = workspace.resolve(".agentkit/authoring")
|
|
101
|
+
self.import_root = workspace.resolve(".agentkit/authoring/imports")
|
|
102
|
+
self.project_root = workspace.resolve(".agentkit/authoring/projects")
|
|
103
|
+
self.import_root.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
self.project_root.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def normalize_slug(value: str) -> str:
|
|
108
|
+
normalized = _SLUG.sub("-", str(value or "").strip().lower()).strip("-")
|
|
109
|
+
if not normalized or not normalized[0].isalpha():
|
|
110
|
+
normalized = f"agent-{normalized}" if normalized else "agent"
|
|
111
|
+
return normalized[:48].rstrip("-")
|
|
112
|
+
|
|
113
|
+
def allocate_agent_id(self, slug: str) -> str:
|
|
114
|
+
base = self.normalize_slug(slug)
|
|
115
|
+
for _attempt in range(100):
|
|
116
|
+
candidate = f"{base}-{uuid4().hex[:12]}"
|
|
117
|
+
if not self.workspace.resolve(Path("agents") / candidate).exists():
|
|
118
|
+
return candidate
|
|
119
|
+
raise StudioError(
|
|
120
|
+
"AGENT_ID_ALLOCATION_FAILED",
|
|
121
|
+
"无法分配唯一 Agent ID",
|
|
122
|
+
status_code=500,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def runtime_ref(agent_id: str, runtime_type: str) -> RuntimeRef:
|
|
127
|
+
normalized = str(runtime_type or "").strip().lower()
|
|
128
|
+
if normalized not in _SUPPORTED_RUNTIMES:
|
|
129
|
+
raise StudioError(
|
|
130
|
+
"RUNTIME_NOT_SUPPORTED",
|
|
131
|
+
"创建方式仅支持 Codex、ADK 和 LangGraph",
|
|
132
|
+
status_code=422,
|
|
133
|
+
field="runtimeType",
|
|
134
|
+
details={"runtimeType": normalized},
|
|
135
|
+
)
|
|
136
|
+
if normalized == "codex":
|
|
137
|
+
# A new YAML Agent must lock the CLI actually installed on this
|
|
138
|
+
# Studio host. Cloud admission resolves that explicit version via
|
|
139
|
+
# the Server-owned catalog instead of accepting a client image.
|
|
140
|
+
return RuntimeRef(
|
|
141
|
+
type="codex",
|
|
142
|
+
version=installed_runtime_version("codex") or "0.144.4",
|
|
143
|
+
)
|
|
144
|
+
return RuntimeRef(
|
|
145
|
+
type=cast(Any, normalized),
|
|
146
|
+
project_path=f"agents/{agent_id}/source",
|
|
147
|
+
entry_point="agent.py",
|
|
148
|
+
agent_variable="graph" if normalized == "langgraph" else "root_agent",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def inspect_import(self, content: bytes, *, filename: str) -> dict[str, Any]:
|
|
152
|
+
if not content:
|
|
153
|
+
raise StudioError(
|
|
154
|
+
"AGENT_IMPORT_EMPTY",
|
|
155
|
+
"导入文件不能为空",
|
|
156
|
+
status_code=422,
|
|
157
|
+
)
|
|
158
|
+
if len(content) > _MAX_IMPORT_BYTES:
|
|
159
|
+
raise StudioError(
|
|
160
|
+
"AGENT_IMPORT_TOO_LARGE",
|
|
161
|
+
"Agent 导入包不能超过 100 MiB",
|
|
162
|
+
status_code=413,
|
|
163
|
+
)
|
|
164
|
+
token = f"imp_{uuid4().hex}"
|
|
165
|
+
staging = self.workspace.resolve(self.import_root / token)
|
|
166
|
+
staging.mkdir(parents=True, exist_ok=False)
|
|
167
|
+
try:
|
|
168
|
+
files, manifest_path = self._stage_import(staging, content, filename=filename)
|
|
169
|
+
payload = self._load_yaml_object(manifest_path)
|
|
170
|
+
kind, runtime_type, display_name = self._classify_import(payload)
|
|
171
|
+
manifest_relative = manifest_path.relative_to(staging)
|
|
172
|
+
record = {
|
|
173
|
+
"format": "agentkit.authoring-import/v1",
|
|
174
|
+
"token": token,
|
|
175
|
+
"kind": kind,
|
|
176
|
+
"runtimeType": runtime_type,
|
|
177
|
+
"displayName": display_name,
|
|
178
|
+
"sourceDigest": self._tree_digest(staging, exclude={"inspection.json"}),
|
|
179
|
+
"files": files,
|
|
180
|
+
"manifestPath": manifest_relative.as_posix(),
|
|
181
|
+
"manifestDirectory": manifest_relative.parent.as_posix(),
|
|
182
|
+
"payload": payload,
|
|
183
|
+
}
|
|
184
|
+
self.workspace.atomic_write_text(
|
|
185
|
+
staging / "inspection.json",
|
|
186
|
+
json.dumps(record, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
|
|
187
|
+
)
|
|
188
|
+
return self._public_import_record(record)
|
|
189
|
+
except BaseException:
|
|
190
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
191
|
+
raise
|
|
192
|
+
|
|
193
|
+
def load_import(self, token: str) -> ImportInspection:
|
|
194
|
+
staging = self._token_directory(self.import_root, token, prefix="imp_")
|
|
195
|
+
record_path = staging / "inspection.json"
|
|
196
|
+
try:
|
|
197
|
+
record = json.loads(record_path.read_text(encoding="utf-8"))
|
|
198
|
+
except (OSError, ValueError) as exc:
|
|
199
|
+
raise StudioError(
|
|
200
|
+
"AGENT_IMPORT_INSPECTION_NOT_FOUND",
|
|
201
|
+
"导入检查不存在或已失效",
|
|
202
|
+
status_code=404,
|
|
203
|
+
) from exc
|
|
204
|
+
expected = str(record.get("sourceDigest") or "")
|
|
205
|
+
actual = self._tree_digest(staging, exclude={"inspection.json"})
|
|
206
|
+
if not expected or expected != actual:
|
|
207
|
+
raise StudioError(
|
|
208
|
+
"AGENT_IMPORT_CHANGED",
|
|
209
|
+
"导入暂存内容在确认前发生变化,请重新检查",
|
|
210
|
+
status_code=409,
|
|
211
|
+
)
|
|
212
|
+
manifest_directory = PurePosixPath(str(record.get("manifestDirectory") or "."))
|
|
213
|
+
return ImportInspection(
|
|
214
|
+
token=token,
|
|
215
|
+
kind=cast(Any, record["kind"]),
|
|
216
|
+
payload=cast(dict[str, Any], record["payload"]),
|
|
217
|
+
runtime_type=str(record["runtimeType"]),
|
|
218
|
+
display_name=str(record["displayName"]),
|
|
219
|
+
source_digest=expected,
|
|
220
|
+
files=tuple(str(item) for item in record.get("files") or []),
|
|
221
|
+
staging_directory=staging,
|
|
222
|
+
manifest_directory=manifest_directory,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def consume_import(self, token: str) -> None:
|
|
226
|
+
staging = self._token_directory(self.import_root, token, prefix="imp_")
|
|
227
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
228
|
+
|
|
229
|
+
def source_directory(self, inspection: ImportInspection) -> Path | None:
|
|
230
|
+
if inspection.kind != "agent-draft":
|
|
231
|
+
return None
|
|
232
|
+
draft = AgentDraft.model_validate(inspection.payload)
|
|
233
|
+
runtime = draft.spec.runtime
|
|
234
|
+
if runtime is None or not runtime.project_path:
|
|
235
|
+
return None
|
|
236
|
+
candidates = [
|
|
237
|
+
inspection.staging_directory / inspection.manifest_directory / runtime.project_path,
|
|
238
|
+
inspection.staging_directory / runtime.project_path,
|
|
239
|
+
inspection.staging_directory / inspection.manifest_directory / "source",
|
|
240
|
+
]
|
|
241
|
+
for candidate in candidates:
|
|
242
|
+
resolved = candidate.resolve()
|
|
243
|
+
try:
|
|
244
|
+
resolved.relative_to(inspection.staging_directory.resolve())
|
|
245
|
+
except ValueError:
|
|
246
|
+
continue
|
|
247
|
+
if resolved.is_dir():
|
|
248
|
+
return resolved
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
def inspect_project(self, project_path: str) -> dict[str, Any]:
|
|
252
|
+
project = self.workspace.resolve(project_path, must_exist=True)
|
|
253
|
+
if not project.is_dir():
|
|
254
|
+
raise StudioError(
|
|
255
|
+
"PROJECT_PATH_INVALID",
|
|
256
|
+
"项目识别路径必须是工作区内的目录",
|
|
257
|
+
status_code=422,
|
|
258
|
+
)
|
|
259
|
+
detection = FrameworkDetector(str(project)).detect()
|
|
260
|
+
runtime_type = detection.type.value
|
|
261
|
+
if not detection.is_valid or runtime_type not in _SUPPORTED_RUNTIMES:
|
|
262
|
+
raise StudioError(
|
|
263
|
+
"PROJECT_RUNTIME_UNSUPPORTED",
|
|
264
|
+
"没有识别到可由 Studio 管理的 Codex、ADK 或 LangGraph Runtime",
|
|
265
|
+
status_code=422,
|
|
266
|
+
details={"detected": runtime_type},
|
|
267
|
+
)
|
|
268
|
+
token = f"prj_{uuid4().hex}"
|
|
269
|
+
relative = self.workspace.relative(project)
|
|
270
|
+
record = {
|
|
271
|
+
"format": "agentkit.authoring-project/v1",
|
|
272
|
+
"token": token,
|
|
273
|
+
"projectPath": relative,
|
|
274
|
+
"runtimeType": runtime_type,
|
|
275
|
+
"name": detection.name or project.name,
|
|
276
|
+
"entryPoint": detection.entry_point or None,
|
|
277
|
+
"agentVariable": detection.agent_variable or None,
|
|
278
|
+
"confidence": float(detection.confidence),
|
|
279
|
+
"evidence": {
|
|
280
|
+
"config": dict(detection.raw_config or {}),
|
|
281
|
+
"packagePath": detection.package_path,
|
|
282
|
+
},
|
|
283
|
+
"sourceDigest": self._tree_digest(project),
|
|
284
|
+
"requiresConfirmation": True,
|
|
285
|
+
}
|
|
286
|
+
self.workspace.atomic_write_text(
|
|
287
|
+
self.project_root / f"{token}.json",
|
|
288
|
+
json.dumps(record, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
|
|
289
|
+
)
|
|
290
|
+
return {**record, "inspectionToken": token}
|
|
291
|
+
|
|
292
|
+
def load_project(self, token: str) -> dict[str, Any]:
|
|
293
|
+
if not re.fullmatch(r"prj_[0-9a-f]{32}", token):
|
|
294
|
+
raise StudioError(
|
|
295
|
+
"PROJECT_INSPECTION_NOT_FOUND",
|
|
296
|
+
"项目识别检查不存在或已失效",
|
|
297
|
+
status_code=404,
|
|
298
|
+
)
|
|
299
|
+
path = self.workspace.resolve(self.project_root / f"{token}.json")
|
|
300
|
+
try:
|
|
301
|
+
record = json.loads(path.read_text(encoding="utf-8"))
|
|
302
|
+
except (OSError, ValueError) as exc:
|
|
303
|
+
raise StudioError(
|
|
304
|
+
"PROJECT_INSPECTION_NOT_FOUND",
|
|
305
|
+
"项目识别检查不存在或已失效",
|
|
306
|
+
status_code=404,
|
|
307
|
+
) from exc
|
|
308
|
+
project = self.workspace.resolve(str(record["projectPath"]), must_exist=True)
|
|
309
|
+
if self._tree_digest(project) != record.get("sourceDigest"):
|
|
310
|
+
raise StudioError(
|
|
311
|
+
"PROJECT_CHANGED_AFTER_INSPECTION",
|
|
312
|
+
"项目在确认前发生变化,请重新识别",
|
|
313
|
+
status_code=409,
|
|
314
|
+
)
|
|
315
|
+
return cast(dict[str, Any], record)
|
|
316
|
+
|
|
317
|
+
def consume_project(self, token: str) -> None:
|
|
318
|
+
path = self.workspace.resolve(self.project_root / f"{token}.json")
|
|
319
|
+
if path.is_file():
|
|
320
|
+
path.unlink()
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def _sanitize_model_block(payload: dict[str, Any]) -> None:
|
|
324
|
+
"""清掉模型照抄示例或凭空编造的 spec.model 字段。
|
|
325
|
+
|
|
326
|
+
- baseUrl/endpointUrl 写着 example.com/placeholder 等占位域名的直接删掉
|
|
327
|
+
(Studio 会按选中的模型 Profile 注入真实 endpoint)
|
|
328
|
+
- parameters 只保留用户对话明确要求时模型写出的值;模型自行编造的
|
|
329
|
+
常见值(temperature 0.x + maxTokens 2048/4096 这类组合)无法与
|
|
330
|
+
用户意图区分时一并删除,交平台默认值兜底
|
|
331
|
+
"""
|
|
332
|
+
spec = payload.get("spec")
|
|
333
|
+
if not isinstance(spec, dict):
|
|
334
|
+
return
|
|
335
|
+
model = spec.get("model")
|
|
336
|
+
if not isinstance(model, dict):
|
|
337
|
+
return
|
|
338
|
+
for key in ("baseUrl", "endpointUrl"):
|
|
339
|
+
value = model.get(key)
|
|
340
|
+
if not isinstance(value, str):
|
|
341
|
+
continue
|
|
342
|
+
try:
|
|
343
|
+
hostname = (urlparse(value).hostname or "").lower().rstrip(".")
|
|
344
|
+
except ValueError:
|
|
345
|
+
hostname = ""
|
|
346
|
+
labels = hostname.split(".")
|
|
347
|
+
is_example_host = hostname == "example.com" or hostname.endswith(".example.com")
|
|
348
|
+
is_placeholder_host = any(label == "placeholder" for label in labels)
|
|
349
|
+
if is_example_host or is_placeholder_host:
|
|
350
|
+
model.pop(key, None)
|
|
351
|
+
# ModelSpec 校验要求 baseUrl/endpointUrl 二选一;模型没写或写了占位被删时,
|
|
352
|
+
# 置一个显式标记值,coordinator 会用选中模型 Profile 的真实 endpoint 覆写。
|
|
353
|
+
if not model.get("baseUrl") and not model.get("endpointUrl"):
|
|
354
|
+
model["baseUrl"] = "https://model-profile.invalid/placeholder"
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def _coerce_model_credential_ref(payload: dict[str, Any]) -> None:
|
|
358
|
+
"""容忍模型把 spec.model.credentialRef 写成对象/空值的常见错误形态。
|
|
359
|
+
|
|
360
|
+
模型偶尔会把字符串引用字段写成 {} 或 {"ref": ...};在进入 Pydantic 校验前
|
|
361
|
+
收敛为默认 env 引用,避免浪费唯一一次纠错重试。
|
|
362
|
+
"""
|
|
363
|
+
spec = payload.get("spec")
|
|
364
|
+
if not isinstance(spec, dict):
|
|
365
|
+
return
|
|
366
|
+
model = spec.get("model")
|
|
367
|
+
if not isinstance(model, dict):
|
|
368
|
+
return
|
|
369
|
+
ref = model.get("credentialRef")
|
|
370
|
+
if isinstance(ref, str) and ref.strip().startswith(("env://", "keychain://", "secret-manager://")):
|
|
371
|
+
return
|
|
372
|
+
if isinstance(ref, dict):
|
|
373
|
+
nested = ref.get("ref") or ref.get("credentialRef") or ref.get("value")
|
|
374
|
+
if isinstance(nested, str) and nested.strip().startswith(("env://", "keychain://", "secret-manager://")):
|
|
375
|
+
model["credentialRef"] = nested.strip()
|
|
376
|
+
return
|
|
377
|
+
model["credentialRef"] = "env://AGENTKIT_MODEL_API_KEY"
|
|
378
|
+
|
|
379
|
+
@staticmethod
|
|
380
|
+
def _coerce_runtime_type(payload: dict[str, Any]) -> None:
|
|
381
|
+
"""容忍模型把 spec.runtime.type 写成 provider 的常见错误形态。"""
|
|
382
|
+
spec = payload.get("spec")
|
|
383
|
+
if not isinstance(spec, dict):
|
|
384
|
+
return
|
|
385
|
+
runtime = spec.get("runtime")
|
|
386
|
+
if not isinstance(runtime, dict):
|
|
387
|
+
return
|
|
388
|
+
if not runtime.get("type") and runtime.get("provider"):
|
|
389
|
+
runtime["type"] = runtime.pop("provider")
|
|
390
|
+
elif runtime.get("provider") and runtime.get("type"):
|
|
391
|
+
runtime.pop("provider")
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def _conversation_json_object(content: str) -> dict[str, Any]:
|
|
395
|
+
"""Extract one JSON object without trusting surrounding model prose."""
|
|
396
|
+
|
|
397
|
+
text = str(content or "").strip()
|
|
398
|
+
candidates = [text]
|
|
399
|
+
candidates.extend(
|
|
400
|
+
match.group(1).strip()
|
|
401
|
+
for match in re.finditer(r"```(?:json)?\s*([\s\S]*?)```", text, re.IGNORECASE)
|
|
402
|
+
)
|
|
403
|
+
decoder = json.JSONDecoder()
|
|
404
|
+
for candidate in candidates:
|
|
405
|
+
try:
|
|
406
|
+
payload = json.loads(candidate)
|
|
407
|
+
except ValueError:
|
|
408
|
+
payload = None
|
|
409
|
+
if isinstance(payload, dict):
|
|
410
|
+
return cast(dict[str, Any], payload)
|
|
411
|
+
for start, character in enumerate(text):
|
|
412
|
+
if character != "{":
|
|
413
|
+
continue
|
|
414
|
+
try:
|
|
415
|
+
payload, _end = decoder.raw_decode(text, start)
|
|
416
|
+
except ValueError:
|
|
417
|
+
continue
|
|
418
|
+
if isinstance(payload, dict):
|
|
419
|
+
return cast(dict[str, Any], payload)
|
|
420
|
+
raise ValueError("model output does not contain a JSON object")
|
|
421
|
+
|
|
422
|
+
@staticmethod
|
|
423
|
+
def _merge_conversation_patch(
|
|
424
|
+
base: dict[str, Any], patch: dict[str, Any]
|
|
425
|
+
) -> dict[str, Any]:
|
|
426
|
+
merged = copy.deepcopy(base)
|
|
427
|
+
for key, value in patch.items():
|
|
428
|
+
if isinstance(value, dict) and isinstance(merged.get(key), dict):
|
|
429
|
+
merged[key] = AgentAuthoringService._merge_conversation_patch(
|
|
430
|
+
cast(dict[str, Any], merged[key]), value
|
|
431
|
+
)
|
|
432
|
+
else:
|
|
433
|
+
merged[key] = copy.deepcopy(value)
|
|
434
|
+
return merged
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def _studio_owned_conversation_patch(
|
|
438
|
+
payload: dict[str, Any],
|
|
439
|
+
*,
|
|
440
|
+
runtime_type: str,
|
|
441
|
+
) -> dict[str, Any]:
|
|
442
|
+
"""Keep an authoring-model response to semantic fields only.
|
|
443
|
+
|
|
444
|
+
A conversation can describe an Agent, but it cannot produce a valid
|
|
445
|
+
local ADK/LangGraph project, resource identity, credential or runtime
|
|
446
|
+
binding. Treating its complete AgentSpec as deployable made a simple
|
|
447
|
+
conversation depend on it guessing every evolving Studio contract.
|
|
448
|
+
Studio owns those fields and injects them after this parser returns.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
normalized = str(runtime_type or "").strip().lower()
|
|
452
|
+
if normalized not in _SUPPORTED_RUNTIMES:
|
|
453
|
+
raise ValueError("runtimeType is not supported")
|
|
454
|
+
raw_spec = payload.get("spec")
|
|
455
|
+
if not isinstance(raw_spec, dict):
|
|
456
|
+
raw_spec = {}
|
|
457
|
+
raw_instructions = raw_spec.get("instructions", payload.get("instructions"))
|
|
458
|
+
instructions = raw_instructions if isinstance(raw_instructions, dict) else {}
|
|
459
|
+
spec: dict[str, Any] = {
|
|
460
|
+
"instructions": {
|
|
461
|
+
key: str(value).strip()
|
|
462
|
+
for key in ("system", "task")
|
|
463
|
+
if isinstance((value := instructions.get(key)), str) and value.strip()
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if isinstance(raw_spec.get("description"), str):
|
|
467
|
+
spec["description"] = raw_spec["description"].strip()
|
|
468
|
+
return {
|
|
469
|
+
key: copy.deepcopy(payload[key])
|
|
470
|
+
for key in ("name", "slug", "description")
|
|
471
|
+
if key in payload
|
|
472
|
+
} | {
|
|
473
|
+
"runtimeType": normalized,
|
|
474
|
+
"spec": spec,
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
@staticmethod
|
|
478
|
+
def parse_conversation_proposal(
|
|
479
|
+
content: str,
|
|
480
|
+
*,
|
|
481
|
+
base: ConversationProposal | dict[str, Any] | None = None,
|
|
482
|
+
runtime_type: str | None = None,
|
|
483
|
+
) -> ConversationProposal:
|
|
484
|
+
try:
|
|
485
|
+
payload = AgentAuthoringService._conversation_json_object(content)
|
|
486
|
+
for wrapper in ("proposal", "patch"):
|
|
487
|
+
wrapped = payload.get(wrapper)
|
|
488
|
+
if isinstance(wrapped, dict) and len(payload) == 1:
|
|
489
|
+
payload = cast(dict[str, Any], wrapped)
|
|
490
|
+
break
|
|
491
|
+
if runtime_type is not None:
|
|
492
|
+
payload = AgentAuthoringService._studio_owned_conversation_patch(
|
|
493
|
+
payload,
|
|
494
|
+
runtime_type=runtime_type,
|
|
495
|
+
)
|
|
496
|
+
AgentAuthoringService._coerce_model_credential_ref(payload)
|
|
497
|
+
AgentAuthoringService._sanitize_model_block(payload)
|
|
498
|
+
AgentAuthoringService._coerce_runtime_type(payload)
|
|
499
|
+
if base is not None:
|
|
500
|
+
base_payload = (
|
|
501
|
+
base.model_dump(by_alias=True, mode="json")
|
|
502
|
+
if isinstance(base, ConversationProposal)
|
|
503
|
+
else base
|
|
504
|
+
)
|
|
505
|
+
payload = AgentAuthoringService._merge_conversation_patch(
|
|
506
|
+
base_payload, payload
|
|
507
|
+
)
|
|
508
|
+
if runtime_type is not None:
|
|
509
|
+
# The merge can reintroduce an old runtimeType from a previous
|
|
510
|
+
# Draft Patch; the live Studio selector remains authoritative.
|
|
511
|
+
payload["runtimeType"] = str(runtime_type).strip().lower()
|
|
512
|
+
if isinstance(payload.get("spec"), dict):
|
|
513
|
+
payload["spec"].pop("runtime", None)
|
|
514
|
+
proposal = ConversationProposal.model_validate(payload)
|
|
515
|
+
except (ValueError, ValidationError) as exc:
|
|
516
|
+
raise StudioError(
|
|
517
|
+
"AUTHORING_MODEL_OUTPUT_INVALID",
|
|
518
|
+
"对话构建模型没有返回合法的 Agent Draft Patch",
|
|
519
|
+
status_code=502,
|
|
520
|
+
details={"reason": str(exc)},
|
|
521
|
+
) from exc
|
|
522
|
+
normalized_slug = AgentAuthoringService.normalize_slug(proposal.slug)
|
|
523
|
+
return proposal.model_copy(update={"slug": normalized_slug})
|
|
524
|
+
|
|
525
|
+
@staticmethod
|
|
526
|
+
def conversation_messages(
|
|
527
|
+
messages: list[dict[str, str]],
|
|
528
|
+
*,
|
|
529
|
+
runtime_type: str = "codex",
|
|
530
|
+
) -> list[dict[str, str]]:
|
|
531
|
+
if not messages:
|
|
532
|
+
raise StudioError(
|
|
533
|
+
"AUTHORING_CONVERSATION_EMPTY",
|
|
534
|
+
"对话构建至少需要一条消息",
|
|
535
|
+
status_code=422,
|
|
536
|
+
)
|
|
537
|
+
normalized: list[dict[str, str]] = [
|
|
538
|
+
{
|
|
539
|
+
"role": "system",
|
|
540
|
+
"content": (
|
|
541
|
+
"你是 AgentKit Studio 的 Agent 设计助手。根据对话生成一个 JSON Draft Patch,"
|
|
542
|
+
"不得输出 Markdown。只返回一个最小 JSON Draft Patch:首轮只包含 name、"
|
|
543
|
+
"slug、description、spec;spec 只包含 instructions,instructions 只允许"
|
|
544
|
+
"system 和 task。后续轮次只返回要更新的上述字段,由 Studio 与上一版 Patch"
|
|
545
|
+
"合并。当前 Runtime 已由 Studio 选择为 "
|
|
546
|
+
f"{runtime_type},不得输出 runtimeType、spec.runtime、execution、context、"
|
|
547
|
+
"memory、security、evaluation、model、bindings、capabilities 或任何资源 ID。"
|
|
548
|
+
"模型 Profile、运行 Runtime、模型参数、Tool、MCP、Skill、凭证、端点与"
|
|
549
|
+
"资源 ID 都由 Studio 按用户选择注入。只提出配置,不写文件、不宣称已经创建。"
|
|
550
|
+
),
|
|
551
|
+
}
|
|
552
|
+
]
|
|
553
|
+
for item in messages:
|
|
554
|
+
role = str(item.get("role") or "").strip()
|
|
555
|
+
content = str(item.get("content") or "").strip()
|
|
556
|
+
if role not in {"user", "assistant"} or not content:
|
|
557
|
+
raise StudioError(
|
|
558
|
+
"AUTHORING_CONVERSATION_INVALID",
|
|
559
|
+
"对话消息只允许非空 user/assistant 文本",
|
|
560
|
+
status_code=422,
|
|
561
|
+
)
|
|
562
|
+
normalized.append({"role": role, "content": content})
|
|
563
|
+
return normalized
|
|
564
|
+
|
|
565
|
+
@staticmethod
|
|
566
|
+
def authoring_network_policy(endpoint_url: str) -> NetworkPolicy:
|
|
567
|
+
host = (urlparse(endpoint_url).hostname or "").lower().rstrip(".")
|
|
568
|
+
return NetworkPolicy(
|
|
569
|
+
mode="restricted",
|
|
570
|
+
allowed_hosts=[host] if host else [],
|
|
571
|
+
allow_private_network=False,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
def _stage_import(
|
|
575
|
+
self,
|
|
576
|
+
staging: Path,
|
|
577
|
+
content: bytes,
|
|
578
|
+
*,
|
|
579
|
+
filename: str,
|
|
580
|
+
) -> tuple[list[str], Path]:
|
|
581
|
+
if filename.lower().endswith(".zip"):
|
|
582
|
+
return self._extract_zip(staging, content)
|
|
583
|
+
if not filename.lower().endswith((".yaml", ".yml")):
|
|
584
|
+
raise StudioError(
|
|
585
|
+
"AGENT_IMPORT_FORMAT_UNSUPPORTED",
|
|
586
|
+
"仅支持 Agent YAML 或 ZIP",
|
|
587
|
+
status_code=422,
|
|
588
|
+
)
|
|
589
|
+
path = staging / "agent.yaml"
|
|
590
|
+
path.write_bytes(content)
|
|
591
|
+
return ["agent.yaml"], path
|
|
592
|
+
|
|
593
|
+
def _extract_zip(self, staging: Path, content: bytes) -> tuple[list[str], Path]:
|
|
594
|
+
try:
|
|
595
|
+
archive = zipfile.ZipFile(io.BytesIO(content))
|
|
596
|
+
except zipfile.BadZipFile as exc:
|
|
597
|
+
raise StudioError(
|
|
598
|
+
"AGENT_IMPORT_ARCHIVE_INVALID",
|
|
599
|
+
"Agent ZIP 无法解析",
|
|
600
|
+
status_code=422,
|
|
601
|
+
) from exc
|
|
602
|
+
with archive:
|
|
603
|
+
entries = [item for item in archive.infolist() if not item.is_dir()]
|
|
604
|
+
if not entries or len(entries) > _MAX_IMPORT_FILES:
|
|
605
|
+
raise StudioError(
|
|
606
|
+
"AGENT_IMPORT_ARCHIVE_INVALID",
|
|
607
|
+
"Agent ZIP 文件数量不合法",
|
|
608
|
+
status_code=422,
|
|
609
|
+
)
|
|
610
|
+
expanded = sum(item.file_size for item in entries)
|
|
611
|
+
if expanded > _MAX_IMPORT_BYTES:
|
|
612
|
+
raise StudioError(
|
|
613
|
+
"AGENT_IMPORT_TOO_LARGE",
|
|
614
|
+
"Agent ZIP 解压后不能超过 100 MiB",
|
|
615
|
+
status_code=413,
|
|
616
|
+
)
|
|
617
|
+
files: list[str] = []
|
|
618
|
+
manifests: list[Path] = []
|
|
619
|
+
for item in entries:
|
|
620
|
+
path = PurePosixPath(item.filename)
|
|
621
|
+
mode = (item.external_attr >> 16) & 0o170000
|
|
622
|
+
if (
|
|
623
|
+
path.is_absolute()
|
|
624
|
+
or ".." in path.parts
|
|
625
|
+
or mode in {stat.S_IFLNK, stat.S_IFCHR, stat.S_IFBLK, stat.S_IFIFO}
|
|
626
|
+
or (item.compress_size == 0 and item.file_size > 0)
|
|
627
|
+
or (
|
|
628
|
+
item.compress_size > 0
|
|
629
|
+
and item.file_size / item.compress_size > 200
|
|
630
|
+
)
|
|
631
|
+
):
|
|
632
|
+
raise StudioError(
|
|
633
|
+
"AGENT_IMPORT_ARCHIVE_UNSAFE",
|
|
634
|
+
"Agent ZIP 包含不安全路径、链接或压缩条目",
|
|
635
|
+
status_code=422,
|
|
636
|
+
details={"entry": item.filename},
|
|
637
|
+
)
|
|
638
|
+
target = staging / Path(*path.parts)
|
|
639
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
640
|
+
target.write_bytes(archive.read(item))
|
|
641
|
+
files.append(path.as_posix())
|
|
642
|
+
if path.name in {"agent.yaml", "agentengine.yaml"}:
|
|
643
|
+
manifests.append(target)
|
|
644
|
+
if len(manifests) != 1:
|
|
645
|
+
raise StudioError(
|
|
646
|
+
"AGENT_IMPORT_MANIFEST_REQUIRED",
|
|
647
|
+
"Agent ZIP 必须且只能包含一个 agent.yaml 或 agentengine.yaml",
|
|
648
|
+
status_code=422,
|
|
649
|
+
)
|
|
650
|
+
return sorted(files), manifests[0]
|
|
651
|
+
|
|
652
|
+
@staticmethod
|
|
653
|
+
def _load_yaml_object(path: Path) -> dict[str, Any]:
|
|
654
|
+
try:
|
|
655
|
+
payload = yaml.safe_load(path.read_text(encoding="utf-8-sig"))
|
|
656
|
+
except (OSError, UnicodeDecodeError, yaml.YAMLError) as exc:
|
|
657
|
+
raise StudioError(
|
|
658
|
+
"AGENT_IMPORT_MANIFEST_INVALID",
|
|
659
|
+
"Agent YAML 无法解析",
|
|
660
|
+
status_code=422,
|
|
661
|
+
) from exc
|
|
662
|
+
if not isinstance(payload, dict):
|
|
663
|
+
raise StudioError(
|
|
664
|
+
"AGENT_IMPORT_MANIFEST_INVALID",
|
|
665
|
+
"Agent YAML 根节点必须是对象",
|
|
666
|
+
status_code=422,
|
|
667
|
+
)
|
|
668
|
+
return cast(dict[str, Any], payload)
|
|
669
|
+
|
|
670
|
+
@staticmethod
|
|
671
|
+
def _classify_import(payload: dict[str, Any]) -> tuple[str, str, str]:
|
|
672
|
+
try:
|
|
673
|
+
if payload.get("kind") == "Agent":
|
|
674
|
+
draft = AgentDraft.model_validate(payload)
|
|
675
|
+
if draft.spec.runtime is None:
|
|
676
|
+
raise ValueError("Agent Draft 缺少 spec.runtime")
|
|
677
|
+
return "agent-draft", draft.spec.runtime.type, draft.metadata.name
|
|
678
|
+
manifest = CodexAgentManifest.model_validate(payload)
|
|
679
|
+
return "codex-manifest", "codex", manifest.name
|
|
680
|
+
except (ValueError, ValidationError) as exc:
|
|
681
|
+
raise StudioError(
|
|
682
|
+
"AGENT_IMPORT_CONTRACT_INVALID",
|
|
683
|
+
"导入内容不符合 Agent Draft 或 Codex agentengine.yaml 合同",
|
|
684
|
+
status_code=422,
|
|
685
|
+
details={"reason": str(exc)},
|
|
686
|
+
) from exc
|
|
687
|
+
|
|
688
|
+
@staticmethod
|
|
689
|
+
def _tree_digest(root: Path, *, exclude: set[str] | None = None) -> str:
|
|
690
|
+
# 默认排除 .agentkit(Studio 自身状态)与常见忽略目录,避免 inspect 后写 token json
|
|
691
|
+
# 改变 digest 导致 commit 时 PROJECT_CHANGED_AFTER_INSPECTION 误报(方案 §6.1)。
|
|
692
|
+
ignored = set(exclude or [])
|
|
693
|
+
ignored.add(".agentkit")
|
|
694
|
+
ignored.add(".git")
|
|
695
|
+
entries: list[dict[str, Any]] = []
|
|
696
|
+
for path in sorted(root.rglob("*"), key=lambda item: item.as_posix()):
|
|
697
|
+
if path.is_symlink():
|
|
698
|
+
raise StudioError(
|
|
699
|
+
"AUTHORING_SOURCE_UNSAFE",
|
|
700
|
+
"待检查内容包含软链接",
|
|
701
|
+
status_code=422,
|
|
702
|
+
details={"path": str(path)},
|
|
703
|
+
)
|
|
704
|
+
if not path.is_file() or path.name in ignored:
|
|
705
|
+
continue
|
|
706
|
+
# 跳过 .agentkit / .git 目录下的文件
|
|
707
|
+
relative = path.relative_to(root).as_posix()
|
|
708
|
+
if any(relative.startswith(ignored_dir + "/") for ignored_dir in (".agentkit", ".git")):
|
|
709
|
+
continue
|
|
710
|
+
content = path.read_bytes()
|
|
711
|
+
entries.append(
|
|
712
|
+
{
|
|
713
|
+
"path": relative,
|
|
714
|
+
"sha256": hashlib.sha256(content).hexdigest(),
|
|
715
|
+
"size": len(content),
|
|
716
|
+
}
|
|
717
|
+
)
|
|
718
|
+
return sha256_digest(canonical_json(entries))
|
|
719
|
+
|
|
720
|
+
def _token_directory(self, root: Path, token: str, *, prefix: str) -> Path:
|
|
721
|
+
if not re.fullmatch(rf"{re.escape(prefix)}[0-9a-f]{{32}}", token):
|
|
722
|
+
raise StudioError(
|
|
723
|
+
"AGENT_IMPORT_INSPECTION_NOT_FOUND",
|
|
724
|
+
"导入检查不存在或已失效",
|
|
725
|
+
status_code=404,
|
|
726
|
+
)
|
|
727
|
+
path = self.workspace.resolve(root / token)
|
|
728
|
+
if not path.is_dir():
|
|
729
|
+
raise StudioError(
|
|
730
|
+
"AGENT_IMPORT_INSPECTION_NOT_FOUND",
|
|
731
|
+
"导入检查不存在或已失效",
|
|
732
|
+
status_code=404,
|
|
733
|
+
)
|
|
734
|
+
return path
|
|
735
|
+
|
|
736
|
+
@staticmethod
|
|
737
|
+
def _public_import_record(record: dict[str, Any]) -> dict[str, Any]:
|
|
738
|
+
return {
|
|
739
|
+
"inspectionToken": record["token"],
|
|
740
|
+
"kind": record["kind"],
|
|
741
|
+
"runtimeType": record["runtimeType"],
|
|
742
|
+
"displayName": record["displayName"],
|
|
743
|
+
"sourceDigest": record["sourceDigest"],
|
|
744
|
+
"files": record["files"],
|
|
745
|
+
"requiresConfirmation": True,
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
__all__ = ["AgentAuthoringService", "ConversationProposal", "ImportInspection"]
|