agentengine-sdk-python 0.8.1__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.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +179 -63
- ksadk/a2a/space_client.py +6 -333
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +443 -259
- ksadk/api/client.py +525 -27
- ksadk/builders/code_builder.py +284 -8
- ksadk/builders/container_builder.py +8 -1
- ksadk/builders/managed_runtime_builder.py +39 -35
- ksadk/cli/__init__.py +17 -0
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +10 -2
- ksadk/cli/cmd_eval.py +66 -9
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +1 -126
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +1 -435
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_studio.py +99 -8
- ksadk/cli/cmd_web.py +10 -2
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/codex/client.py +180 -30
- ksadk/codex/runtime.py +428 -326
- ksadk/configs/env_registry.py +66 -12
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +5 -0
- 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/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 +184 -2
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +141 -0
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +272 -2
- ksadk/conversations/runtime_resume.py +20 -1
- ksadk/conversations/runtime_stream_events.py +94 -9
- 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 +5 -0
- ksadk/deployment/providers/serverless.py +143 -69
- ksadk/evaluation/__init__.py +32 -0
- ksadk/evaluation/a2a_adapter.py +9 -1
- ksadk/evaluation/adapters.py +18 -5
- 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 +73 -4
- ksadk/evaluation/evaluators.py +183 -17
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +31 -6
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +15 -6
- 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 -265
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/runtime.py +160 -57
- 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 +7 -0
- ksadk/knowledge_base/service.py +32 -5
- 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 +384 -2
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- 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 +188 -6
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +39 -15
- ksadk/model_proxy/server.py +24 -5
- ksadk/model_proxy/transform.py +60 -2
- 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 +5 -5
- ksadk/runners/base_runner.py +16 -0
- ksadk/runners/factory.py +1 -2
- ksadk/runners/langgraph_runner.py +198 -437
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +118 -2
- ksadk/runtime/conversation_execution.py +316 -182
- ksadk/runtime/executor.py +144 -3
- ksadk/runtime/factory.py +193 -7
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +14 -1
- ksadk/runtime/preprocessing.py +25 -3
- ksadk/runtime/runner_adapter.py +288 -508
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +35 -2
- ksadk/server/composition.py +8 -0
- ksadk/server/factory.py +69 -6
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/openai_compat.py +88 -8
- ksadk/server/routes/projection.py +35 -20
- ksadk/server/routes/run.py +94 -0
- ksadk/server/routes/sessions.py +2 -0
- ksadk/server/routes/streaming.py +5 -2
- ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.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-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.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-ChNs_Mdk.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-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-CmZHzlwZ.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-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-hdBcYzM8.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-Bjy_2v4_.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-BmkRbwCg.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-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.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-DEz24vV0.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/studio/api.py +577 -25
- ksadk/studio/api_contracts.py +114 -5
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +252 -27
- ksadk/studio/authoring_coordinator.py +799 -67
- ksadk/studio/builder.py +88 -11
- ksadk/studio/capabilities.py +17 -0
- ksadk/studio/cloud.py +1916 -95
- ksadk/studio/codex_agent_service.py +125 -14
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +138 -25
- ksadk/studio/codex_manifest.py +51 -22
- ksadk/studio/codex_run.py +46 -3
- ksadk/studio/compiler.py +1 -0
- ksadk/studio/contracts.py +114 -3
- ksadk/studio/event_store.py +25 -95
- ksadk/studio/framework_run.py +231 -3
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/model_client.py +95 -9
- ksadk/studio/model_profile_service.py +1 -1
- ksadk/studio/operations.py +37 -5
- ksadk/studio/otel_trace.py +348 -42
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/repository.py +23 -2
- ksadk/studio/resource_catalog.py +43 -0
- ksadk/studio/run_service.py +563 -137
- ksadk/studio/runtime_source.py +42 -16
- ksadk/studio/service.py +1180 -72
- ksadk/studio/shared_web.py +32 -22
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/templates.py +100 -8
- ksadk/studio/validator.py +19 -0
- ksadk/studio/workspace.py +30 -32
- ksadk/tracing/setup.py +304 -3
- ksadk/version.py +1 -1
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk/events/parser.py +0 -191
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
- ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
- ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
- ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-R52pJJvg.js +0 -177
- ksadk/server/static/assets/index-jJr3_955.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
- ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
- ksadk/studio/evaluation.py +0 -192
- ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
- ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Content-addressed preflight for Studio Code bundles hosted by Agent Kernel.
|
|
2
|
+
|
|
3
|
+
This module deliberately proves only properties available in the local bundle:
|
|
4
|
+
the frozen Agent Kernel wire-contract digest and the launch layout generated by
|
|
5
|
+
Studio. Whether the target Server-selected image implements that digest remains
|
|
6
|
+
a separate control-plane/readiness check.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import io
|
|
12
|
+
import json
|
|
13
|
+
import zipfile
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import PurePosixPath
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from ksadk.studio.capabilities import canonical_json, sha256_digest
|
|
19
|
+
from ksadk.studio.errors import StudioError
|
|
20
|
+
|
|
21
|
+
AGENT_KERNEL_V1_CONTRACT_SET = "agent-kernel/v1"
|
|
22
|
+
# This mirrors contracts/agent-kernel/v1/manifest.json. A Studio test compares
|
|
23
|
+
# the two, so a frozen-contract update cannot leave packaged preflight stale.
|
|
24
|
+
AGENT_KERNEL_V1_CONTRACT_DIGEST = "47e1003e03d97abeba232cc3e03a14b9cbcf78b1109870ccd2ce371f073b6211"
|
|
25
|
+
HOSTED_KERNEL_REQUIREMENTS_PATH = "hosted-kernel-requirements.json"
|
|
26
|
+
HOSTED_KERNEL_REQUIREMENTS_FORMAT = "agentkit.hosted-kernel-requirements/v1"
|
|
27
|
+
HOSTED_KERNEL_RUNTIME_CONTRACT = "agentkit.runtime/v1"
|
|
28
|
+
HOSTED_KERNEL_BUNDLE_FORMAT = "agentkit.bundle/v2"
|
|
29
|
+
_RUNTIME_TYPES = frozenset({"adk", "codex", "langgraph"})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class HostedKernelBundle:
|
|
34
|
+
"""Facts proved from the exact ZIP bytes that will be uploaded."""
|
|
35
|
+
|
|
36
|
+
manifest: dict[str, Any]
|
|
37
|
+
provenance: dict[str, Any]
|
|
38
|
+
requirement: dict[str, Any]
|
|
39
|
+
requirement_digest: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_hosted_kernel_requirement(
|
|
43
|
+
*,
|
|
44
|
+
runtime_type: str,
|
|
45
|
+
entry_point: str | None,
|
|
46
|
+
agent_variable: str | None,
|
|
47
|
+
launch_config: bytes | None,
|
|
48
|
+
) -> dict[str, Any]:
|
|
49
|
+
"""Build the immutable local requirement embedded into every Studio ZIP."""
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
"format": HOSTED_KERNEL_REQUIREMENTS_FORMAT,
|
|
53
|
+
"kernelContract": {
|
|
54
|
+
"set": AGENT_KERNEL_V1_CONTRACT_SET,
|
|
55
|
+
"digest": AGENT_KERNEL_V1_CONTRACT_DIGEST,
|
|
56
|
+
},
|
|
57
|
+
"bundleLayout": HOSTED_KERNEL_BUNDLE_FORMAT,
|
|
58
|
+
"runtimeContract": HOSTED_KERNEL_RUNTIME_CONTRACT,
|
|
59
|
+
"runtime": {
|
|
60
|
+
"type": runtime_type,
|
|
61
|
+
"entryPoint": entry_point or "",
|
|
62
|
+
"agentVariable": agent_variable or "",
|
|
63
|
+
"launchConfig": "runtime/agentengine.yaml" if launch_config is not None else "",
|
|
64
|
+
"launchConfigSha256": sha256_digest(launch_config) if launch_config is not None else "",
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def hosted_kernel_requirement_digest(requirement: dict[str, Any]) -> str:
|
|
70
|
+
return sha256_digest(canonical_json(requirement))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def preflight_hosted_kernel_bundle(bundle: bytes) -> HostedKernelBundle:
|
|
74
|
+
"""Reject a ZIP that cannot be proved compatible before CreateAgent.
|
|
75
|
+
|
|
76
|
+
This validates the uploaded bytes, including their file manifest, instead
|
|
77
|
+
of trusting a writable sibling directory from a local Build.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
with zipfile.ZipFile(io.BytesIO(bundle)) as archive:
|
|
82
|
+
names = _validated_zip_names(archive)
|
|
83
|
+
entries = {name: archive.read(name) for name in names}
|
|
84
|
+
except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile) as error:
|
|
85
|
+
raise _integrity_error("Bundle 不是可验证的 ZIP 文件", reason="invalid_zip") from error
|
|
86
|
+
|
|
87
|
+
manifest = _json_object(entries, "manifest.json")
|
|
88
|
+
provenance = _json_object(entries, "provenance.json")
|
|
89
|
+
requirement = _json_object(entries, HOSTED_KERNEL_REQUIREMENTS_PATH)
|
|
90
|
+
requirement_digest = hosted_kernel_requirement_digest(requirement)
|
|
91
|
+
|
|
92
|
+
_validate_requirement(manifest, provenance, requirement, requirement_digest, entries)
|
|
93
|
+
_validate_content_manifest(manifest, entries)
|
|
94
|
+
_validate_declared_bundle_digest(manifest)
|
|
95
|
+
return HostedKernelBundle(
|
|
96
|
+
manifest=manifest,
|
|
97
|
+
provenance=provenance,
|
|
98
|
+
requirement=requirement,
|
|
99
|
+
requirement_digest=requirement_digest,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _validated_zip_names(archive: zipfile.ZipFile) -> list[str]:
|
|
104
|
+
names = archive.namelist()
|
|
105
|
+
if not names:
|
|
106
|
+
raise _integrity_error("Bundle 为空", reason="empty_zip")
|
|
107
|
+
if len(names) != len(set(names)):
|
|
108
|
+
raise _integrity_error("Bundle 含有重复文件名", reason="duplicate_path")
|
|
109
|
+
for name in names:
|
|
110
|
+
path = PurePosixPath(name)
|
|
111
|
+
if (
|
|
112
|
+
not name
|
|
113
|
+
or "\\" in name
|
|
114
|
+
or name.endswith("/")
|
|
115
|
+
or path.is_absolute()
|
|
116
|
+
or any(part in {"", ".", ".."} for part in path.parts)
|
|
117
|
+
):
|
|
118
|
+
raise _integrity_error("Bundle 含有不安全文件路径", reason="unsafe_path")
|
|
119
|
+
return names
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _json_object(entries: dict[str, bytes], path: str) -> dict[str, Any]:
|
|
123
|
+
raw = entries.get(path)
|
|
124
|
+
if raw is None:
|
|
125
|
+
raise _incompatible_error(f"Bundle 缺少 {path}", reason="missing_required_file")
|
|
126
|
+
try:
|
|
127
|
+
parsed = json.loads(raw.decode("utf-8"))
|
|
128
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
129
|
+
raise _incompatible_error(
|
|
130
|
+
f"Bundle 中的 {path} 不是 JSON 对象", reason="invalid_json"
|
|
131
|
+
) from error
|
|
132
|
+
if not isinstance(parsed, dict):
|
|
133
|
+
raise _incompatible_error(f"Bundle 中的 {path} 不是 JSON 对象", reason="invalid_json")
|
|
134
|
+
return parsed
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _validate_requirement(
|
|
138
|
+
manifest: dict[str, Any],
|
|
139
|
+
provenance: dict[str, Any],
|
|
140
|
+
requirement: dict[str, Any],
|
|
141
|
+
requirement_digest: str,
|
|
142
|
+
entries: dict[str, bytes],
|
|
143
|
+
) -> None:
|
|
144
|
+
if manifest.get("bundleFormat") != HOSTED_KERNEL_BUNDLE_FORMAT:
|
|
145
|
+
raise _incompatible_error(
|
|
146
|
+
"Bundle 不是受支持的 Studio Code bundle 格式", reason="bundle_format"
|
|
147
|
+
)
|
|
148
|
+
if manifest.get("runtimeContract") != HOSTED_KERNEL_RUNTIME_CONTRACT:
|
|
149
|
+
raise _incompatible_error(
|
|
150
|
+
"Bundle runtime contract 不受 Hosted Agent Kernel 支持", reason="runtime_contract"
|
|
151
|
+
)
|
|
152
|
+
if requirement.get("format") != HOSTED_KERNEL_REQUIREMENTS_FORMAT:
|
|
153
|
+
raise _incompatible_error(
|
|
154
|
+
"Bundle 缺少 Hosted Agent Kernel requirement 格式声明", reason="requirement_format"
|
|
155
|
+
)
|
|
156
|
+
if requirement.get("bundleLayout") != manifest.get("bundleFormat"):
|
|
157
|
+
raise _incompatible_error(
|
|
158
|
+
"Bundle requirement 与 bundle layout 不一致", reason="bundle_layout"
|
|
159
|
+
)
|
|
160
|
+
if requirement.get("runtimeContract") != manifest.get("runtimeContract"):
|
|
161
|
+
raise _incompatible_error(
|
|
162
|
+
"Bundle requirement 与 runtime contract 不一致", reason="runtime_contract"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
contract = requirement.get("kernelContract")
|
|
166
|
+
if not isinstance(contract, dict):
|
|
167
|
+
raise _incompatible_error(
|
|
168
|
+
"Bundle 缺少 Agent Kernel contract requirement", reason="missing_contract"
|
|
169
|
+
)
|
|
170
|
+
if contract.get("set") != AGENT_KERNEL_V1_CONTRACT_SET:
|
|
171
|
+
raise _incompatible_error(
|
|
172
|
+
"Bundle Agent Kernel contract set 不受支持", reason="contract_set"
|
|
173
|
+
)
|
|
174
|
+
if contract.get("digest") != AGENT_KERNEL_V1_CONTRACT_DIGEST:
|
|
175
|
+
raise _incompatible_error(
|
|
176
|
+
"Bundle Agent Kernel contract digest 与当前 KsADK 不一致,请重新 Build 后部署",
|
|
177
|
+
reason="contract_digest",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
hosted = provenance.get("hostedKernel")
|
|
181
|
+
if not isinstance(hosted, dict):
|
|
182
|
+
raise _incompatible_error(
|
|
183
|
+
"Bundle provenance 缺少 Hosted Agent Kernel requirement", reason="missing_provenance"
|
|
184
|
+
)
|
|
185
|
+
if hosted.get("requirementsPath") != HOSTED_KERNEL_REQUIREMENTS_PATH:
|
|
186
|
+
raise _incompatible_error(
|
|
187
|
+
"Bundle provenance 的 requirement 路径不一致", reason="provenance_path"
|
|
188
|
+
)
|
|
189
|
+
if hosted.get("requirementDigest") != requirement_digest:
|
|
190
|
+
raise _incompatible_error(
|
|
191
|
+
"Bundle provenance 的 requirement digest 不一致", reason="provenance_digest"
|
|
192
|
+
)
|
|
193
|
+
if hosted.get("contractSet") != contract.get("set") or hosted.get(
|
|
194
|
+
"contractDigest"
|
|
195
|
+
) != contract.get("digest"):
|
|
196
|
+
raise _incompatible_error(
|
|
197
|
+
"Bundle provenance 的 Agent Kernel contract 不一致", reason="provenance_contract"
|
|
198
|
+
)
|
|
199
|
+
if manifest.get("hostedKernelRequirementDigest") != requirement_digest:
|
|
200
|
+
raise _incompatible_error(
|
|
201
|
+
"Bundle manifest 的 requirement digest 不一致", reason="manifest_digest"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
runtime = requirement.get("runtime")
|
|
205
|
+
if not isinstance(runtime, dict):
|
|
206
|
+
raise _incompatible_error(
|
|
207
|
+
"Bundle 缺少受支持的 runtime 启动 requirement", reason="missing_runtime"
|
|
208
|
+
)
|
|
209
|
+
runtime_type = str(runtime.get("type") or "").strip().lower()
|
|
210
|
+
entry_point = str(runtime.get("entryPoint") or "").strip()
|
|
211
|
+
agent_variable = str(runtime.get("agentVariable") or "").strip()
|
|
212
|
+
launch_path = str(runtime.get("launchConfig") or "").strip()
|
|
213
|
+
launch_digest = str(runtime.get("launchConfigSha256") or "").strip()
|
|
214
|
+
if runtime_type not in _RUNTIME_TYPES:
|
|
215
|
+
raise _incompatible_error(
|
|
216
|
+
"Bundle runtime 类型不受 Hosted Agent Kernel 支持", reason="runtime_type"
|
|
217
|
+
)
|
|
218
|
+
if (
|
|
219
|
+
runtime_type != str(manifest.get("runtimeType") or "").strip().lower()
|
|
220
|
+
or not entry_point
|
|
221
|
+
or not agent_variable
|
|
222
|
+
or launch_path != "runtime/agentengine.yaml"
|
|
223
|
+
or not launch_digest
|
|
224
|
+
):
|
|
225
|
+
raise _incompatible_error("Bundle 缺少可验证的 runtime 启动配置", reason="runtime_launch")
|
|
226
|
+
if not _safe_relative_file(entry_point) or f"runtime/{entry_point}" not in entries:
|
|
227
|
+
raise _incompatible_error("Bundle runtime entryPoint 不存在或不安全", reason="entry_point")
|
|
228
|
+
launch_bytes = entries.get(launch_path)
|
|
229
|
+
if launch_bytes is None or sha256_digest(launch_bytes) != launch_digest:
|
|
230
|
+
raise _incompatible_error("Bundle runtime 启动配置 digest 不一致", reason="launch_digest")
|
|
231
|
+
launch = _json_object(entries, launch_path)
|
|
232
|
+
if (
|
|
233
|
+
str(launch.get("framework") or "").strip().lower() != runtime_type
|
|
234
|
+
or launch.get("entry_point") != entry_point
|
|
235
|
+
or launch.get("agent_variable") != agent_variable
|
|
236
|
+
or launch.get("package") != "."
|
|
237
|
+
):
|
|
238
|
+
raise _incompatible_error(
|
|
239
|
+
"Bundle runtime 启动配置与 requirement 不一致", reason="launch_config"
|
|
240
|
+
)
|
|
241
|
+
runtime_lock = _json_object(entries, "runtime-lock.json")
|
|
242
|
+
if (
|
|
243
|
+
str(runtime_lock.get("type") or "").strip().lower() != runtime_type
|
|
244
|
+
or runtime_lock.get("entryPoint") != entry_point
|
|
245
|
+
or runtime_lock.get("agentVariable") != agent_variable
|
|
246
|
+
):
|
|
247
|
+
raise _incompatible_error(
|
|
248
|
+
"Bundle runtime lock 与 requirement 不一致", reason="runtime_lock"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _validate_content_manifest(manifest: dict[str, Any], entries: dict[str, bytes]) -> None:
|
|
253
|
+
files = manifest.get("files")
|
|
254
|
+
if not isinstance(files, list):
|
|
255
|
+
raise _integrity_error("Bundle manifest 缺少文件清单", reason="missing_file_manifest")
|
|
256
|
+
recorded: dict[str, dict[str, Any]] = {}
|
|
257
|
+
for item in files:
|
|
258
|
+
if not isinstance(item, dict) or not isinstance(item.get("path"), str):
|
|
259
|
+
raise _integrity_error("Bundle manifest 的文件清单无效", reason="invalid_file_manifest")
|
|
260
|
+
path = item["path"]
|
|
261
|
+
if path in recorded or not _safe_relative_file(path) or path == "manifest.json":
|
|
262
|
+
raise _integrity_error("Bundle manifest 的文件路径无效", reason="invalid_file_manifest")
|
|
263
|
+
recorded[path] = item
|
|
264
|
+
if set(entries) != set(recorded) | {"manifest.json"}:
|
|
265
|
+
raise _integrity_error("Bundle ZIP 与 manifest 文件清单不一致", reason="file_membership")
|
|
266
|
+
for path, item in recorded.items():
|
|
267
|
+
content = entries[path]
|
|
268
|
+
if item.get("sha256") != sha256_digest(content) or item.get("size") != len(content):
|
|
269
|
+
raise _integrity_error("Bundle 文件摘要与 manifest 不一致", reason="file_digest")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _validate_declared_bundle_digest(manifest: dict[str, Any]) -> None:
|
|
273
|
+
declared = manifest.get("bundleDigest")
|
|
274
|
+
digest_payload = dict(manifest)
|
|
275
|
+
digest_payload.pop("bundleDigest", None)
|
|
276
|
+
if declared != sha256_digest(canonical_json(digest_payload)):
|
|
277
|
+
raise _integrity_error("Bundle manifest 的 bundle digest 不一致", reason="bundle_digest")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _safe_relative_file(value: str) -> bool:
|
|
281
|
+
path = PurePosixPath(value)
|
|
282
|
+
return (
|
|
283
|
+
bool(value)
|
|
284
|
+
and "\\" not in value
|
|
285
|
+
and not path.is_absolute()
|
|
286
|
+
and all(part not in {"", ".", ".."} for part in path.parts)
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _incompatible_error(message: str, *, reason: str) -> StudioError:
|
|
291
|
+
return StudioError(
|
|
292
|
+
"HOSTED_KERNEL_BUNDLE_INCOMPATIBLE",
|
|
293
|
+
message,
|
|
294
|
+
status_code=422,
|
|
295
|
+
details={"reason": reason},
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _integrity_error(message: str, *, reason: str) -> StudioError:
|
|
300
|
+
return StudioError(
|
|
301
|
+
"HOSTED_KERNEL_BUNDLE_INTEGRITY_INVALID",
|
|
302
|
+
message,
|
|
303
|
+
status_code=422,
|
|
304
|
+
details={"reason": reason},
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
__all__ = [
|
|
309
|
+
"AGENT_KERNEL_V1_CONTRACT_DIGEST",
|
|
310
|
+
"AGENT_KERNEL_V1_CONTRACT_SET",
|
|
311
|
+
"HOSTED_KERNEL_REQUIREMENTS_FORMAT",
|
|
312
|
+
"HOSTED_KERNEL_REQUIREMENTS_PATH",
|
|
313
|
+
"HostedKernelBundle",
|
|
314
|
+
"build_hosted_kernel_requirement",
|
|
315
|
+
"hosted_kernel_requirement_digest",
|
|
316
|
+
"preflight_hosted_kernel_bundle",
|
|
317
|
+
]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""ManifestResolver —— 统一识别工作区根 Manifest 的种类(方案 §6.1)。
|
|
2
|
+
|
|
3
|
+
当前问题(方案 §2.4 第 1 点):标准 LangGraph/ADK 项目作为 Studio workspace 启动后,根
|
|
4
|
+
``agentengine.yaml`` 可能先进入 Codex Manifest 解析,导致 ``CODEX_MANIFEST_INVALID``。
|
|
5
|
+
|
|
6
|
+
本模块提供统一入口:根 manifest 存在时先读 ``framework`` / ``runtime.type`` 字段决定 kind,
|
|
7
|
+
只有明确 ``framework: codex`` 才走 ``CodexAgentManifest`` 解析;明确为 ADK/LangGraph 等框架时
|
|
8
|
+
返回 framework kind(交给 ``FrameworkDetector`` 与标准 Code 项目导入);无法判定时返回
|
|
9
|
+
``MANIFEST_KIND_AMBIGUOUS``,列出候选,不猜测为 Codex。
|
|
10
|
+
|
|
11
|
+
解析顺序(方案 §6.1):
|
|
12
|
+
1. 根 manifest 不存在 → ``none``
|
|
13
|
+
2. 显式 ``framework: codex`` 或 ``runtime.name: codex`` → ``codex``
|
|
14
|
+
3. 显式 ``framework: adk|langgraph|...`` 或 ``runtime.type: adk|langgraph|...`` → ``framework``
|
|
15
|
+
4. 仍无法判定 → ``ambiguous``(列出已读到的关键字段,便于诊断)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Literal
|
|
23
|
+
|
|
24
|
+
import yaml
|
|
25
|
+
|
|
26
|
+
ManifestKind = Literal["none", "codex", "framework", "ambiguous"]
|
|
27
|
+
|
|
28
|
+
# framework/runtime 字段的已知 codex / framework 取值。
|
|
29
|
+
_CODEX_FRAMEWORK_VALUES = frozenset({"codex"})
|
|
30
|
+
_CODEX_RUNTIME_VALUES = frozenset({"codex"})
|
|
31
|
+
_FRAMEWORK_VALUES = frozenset({"adk", "langgraph", "langchain", "deepagents", "hermes", "openclaw"})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class ManifestKindResult:
|
|
36
|
+
"""根 manifest 识别结果。"""
|
|
37
|
+
|
|
38
|
+
kind: ManifestKind
|
|
39
|
+
path: Path
|
|
40
|
+
framework: str = ""
|
|
41
|
+
runtime_type: str = ""
|
|
42
|
+
artifact_type: str = ""
|
|
43
|
+
# ambiguous 时列出已读字段,供诊断与错误返回。
|
|
44
|
+
detected_fields: dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def is_codex(self) -> bool:
|
|
48
|
+
return self.kind == "codex"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _safe_yaml(path: Path) -> dict[str, Any]:
|
|
52
|
+
try:
|
|
53
|
+
with open(path, "r", encoding="utf-8-sig") as f:
|
|
54
|
+
payload = yaml.safe_load(f)
|
|
55
|
+
except (OSError, yaml.YAMLError):
|
|
56
|
+
return {}
|
|
57
|
+
if not isinstance(payload, dict):
|
|
58
|
+
return {}
|
|
59
|
+
return payload
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def detect_manifest_kind(workspace_root: Path | str) -> ManifestKindResult:
|
|
63
|
+
"""识别工作区根 manifest 的种类(方案 §6.1)。
|
|
64
|
+
|
|
65
|
+
``workspace_root`` 指工作区目录;本函数查其下的 ``agentengine.yaml``(不存在时返回 ``none``)。
|
|
66
|
+
"""
|
|
67
|
+
root = Path(workspace_root)
|
|
68
|
+
path = root / "agentengine.yaml"
|
|
69
|
+
if not path.is_file():
|
|
70
|
+
return ManifestKindResult(kind="none", path=path)
|
|
71
|
+
|
|
72
|
+
payload = _safe_yaml(path)
|
|
73
|
+
if not payload:
|
|
74
|
+
# 文件存在但无法解析为 dict → ambiguous(不猜测为 codex)
|
|
75
|
+
return ManifestKindResult(kind="ambiguous", path=path, detected_fields={"unparsable": True})
|
|
76
|
+
|
|
77
|
+
framework = str(payload.get("framework") or "").strip().lower()
|
|
78
|
+
runtime = payload.get("runtime") or {}
|
|
79
|
+
runtime_type = (
|
|
80
|
+
str(runtime.get("type") or runtime.get("name") or "").strip().lower()
|
|
81
|
+
if isinstance(runtime, dict)
|
|
82
|
+
else ""
|
|
83
|
+
)
|
|
84
|
+
artifact_type = str(payload.get("artifact_type") or "").strip().lower()
|
|
85
|
+
detected = {
|
|
86
|
+
"framework": framework,
|
|
87
|
+
"runtimeType": runtime_type,
|
|
88
|
+
"artifactType": artifact_type,
|
|
89
|
+
"topLevelKeys": sorted(payload.keys()),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# 2. 显式 codex
|
|
93
|
+
if framework in _CODEX_FRAMEWORK_VALUES or runtime_type in _CODEX_RUNTIME_VALUES:
|
|
94
|
+
return ManifestKindResult(
|
|
95
|
+
kind="codex",
|
|
96
|
+
path=path,
|
|
97
|
+
framework=framework,
|
|
98
|
+
runtime_type=runtime_type,
|
|
99
|
+
artifact_type=artifact_type,
|
|
100
|
+
)
|
|
101
|
+
# 3. 显式 framework
|
|
102
|
+
if framework in _FRAMEWORK_VALUES or runtime_type in _FRAMEWORK_VALUES:
|
|
103
|
+
return ManifestKindResult(
|
|
104
|
+
kind="framework",
|
|
105
|
+
path=path,
|
|
106
|
+
framework=framework or runtime_type,
|
|
107
|
+
runtime_type=runtime_type,
|
|
108
|
+
artifact_type=artifact_type,
|
|
109
|
+
)
|
|
110
|
+
# 4. 无法判定(例如只有 name/version 但无 framework/runtime)
|
|
111
|
+
return ManifestKindResult(
|
|
112
|
+
kind="ambiguous",
|
|
113
|
+
path=path,
|
|
114
|
+
framework=framework,
|
|
115
|
+
runtime_type=runtime_type,
|
|
116
|
+
artifact_type=artifact_type,
|
|
117
|
+
detected_fields=detected,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def root_manifest_is_codex(workspace_root: Path | str) -> bool:
|
|
122
|
+
"""便捷判定:根 manifest 是否应走 Codex 解析(方案 §6.1)。"""
|
|
123
|
+
return detect_manifest_kind(workspace_root).is_codex
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
__all__ = [
|
|
127
|
+
"ManifestKind",
|
|
128
|
+
"ManifestKindResult",
|
|
129
|
+
"detect_manifest_kind",
|
|
130
|
+
"root_manifest_is_codex",
|
|
131
|
+
]
|
ksadk/studio/model_client.py
CHANGED
|
@@ -23,6 +23,12 @@ _DENIED_METADATA_HOSTS = {
|
|
|
23
23
|
"fd00:ec2::254",
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
#: 表示输出被 token 上限截断的 finishReason 集合(chat 与 Responses 两种 wire)。
|
|
27
|
+
_LENGTH_FINISH_REASONS = {"length", "incomplete", "max_output_tokens"}
|
|
28
|
+
#: length 截断重试时把 max_tokens 提到的目标值;未配置 max_tokens 的 profile
|
|
29
|
+
#: 首次截断重试也用该值兜底(authoring 输出完整 JSON 需要宽松上限)。
|
|
30
|
+
_LENGTH_RETRY_TARGET_TOKENS = 16384
|
|
31
|
+
|
|
26
32
|
|
|
27
33
|
@dataclass(frozen=True)
|
|
28
34
|
class ToolCall:
|
|
@@ -325,36 +331,50 @@ class OpenAICompatibleModelClient:
|
|
|
325
331
|
backoff_seconds: float,
|
|
326
332
|
tools: list[dict[str, Any]] | None = None,
|
|
327
333
|
allow_empty: bool = False,
|
|
334
|
+
response_format: dict[str, Any] | None = None,
|
|
335
|
+
retry_on_length: bool = False,
|
|
328
336
|
) -> ModelResponse:
|
|
329
337
|
await self.network_guard.check(model.endpoint_url, network_policy)
|
|
330
338
|
credential = self.credential_resolver.resolve(model.credential_ref)
|
|
331
339
|
wire_api = (model.wire_api or "chat").strip().lower()
|
|
340
|
+
payload: dict[str, Any]
|
|
332
341
|
if wire_api == "responses":
|
|
333
342
|
payload = self._responses_payload(model, messages, tools)
|
|
334
343
|
else:
|
|
335
344
|
payload = {
|
|
336
345
|
"model": model.model,
|
|
337
346
|
"messages": messages,
|
|
338
|
-
"temperature": model.parameters.temperature,
|
|
339
|
-
"max_tokens": model.parameters.max_tokens,
|
|
340
347
|
"stream": False,
|
|
341
348
|
}
|
|
349
|
+
# 未显式配置的采样参数一律不携带字段,交给服务端默认,
|
|
350
|
+
# 避免触碰各模型族的硬约束(kimi 温度、各家 max_tokens 上限等)。
|
|
351
|
+
if model.parameters.temperature is not None:
|
|
352
|
+
payload["temperature"] = model.parameters.temperature
|
|
353
|
+
if model.parameters.max_tokens is not None:
|
|
354
|
+
payload["max_tokens"] = model.parameters.max_tokens
|
|
342
355
|
if model.parameters.top_p is not None:
|
|
343
356
|
payload["top_p"] = model.parameters.top_p
|
|
344
357
|
if tools:
|
|
345
358
|
payload["tools"] = tools
|
|
346
359
|
payload["tool_choice"] = "auto"
|
|
360
|
+
if response_format and model.parameters.allow_json_response_format:
|
|
361
|
+
payload["response_format"] = response_format
|
|
347
362
|
headers = {
|
|
348
363
|
"Authorization": f"Bearer {credential}",
|
|
349
364
|
"Content-Type": "application/json",
|
|
350
365
|
}
|
|
351
366
|
timeout = httpx.Timeout(timeout_seconds, connect=min(10, timeout_seconds))
|
|
367
|
+
dropped_response_format = False
|
|
368
|
+
length_retried = False
|
|
369
|
+
last_length_error: StudioError | None = None
|
|
352
370
|
async with httpx.AsyncClient(
|
|
353
371
|
transport=self.transport,
|
|
354
372
|
timeout=timeout,
|
|
355
373
|
follow_redirects=False,
|
|
356
374
|
) as client:
|
|
357
|
-
|
|
375
|
+
# 额外 1 次迭代仅用于 response_format 400 降级重发,
|
|
376
|
+
# 其余失败路径仍受 max_attempts 约束(会在原上限处 raise)。
|
|
377
|
+
for attempt in range(1, max_attempts + 2):
|
|
358
378
|
try:
|
|
359
379
|
response = await client.post(
|
|
360
380
|
model.endpoint_url,
|
|
@@ -382,18 +402,83 @@ class OpenAICompatibleModelClient:
|
|
|
382
402
|
if attempt < max_attempts:
|
|
383
403
|
await self.sleep(backoff_seconds * attempt)
|
|
384
404
|
continue
|
|
405
|
+
if (
|
|
406
|
+
response.status_code == 400
|
|
407
|
+
and "response_format" in payload
|
|
408
|
+
and not dropped_response_format
|
|
409
|
+
):
|
|
410
|
+
# 网关不支持 response_format:去掉该字段重发一次。
|
|
411
|
+
dropped_response_format = True
|
|
412
|
+
payload.pop("response_format")
|
|
413
|
+
continue
|
|
385
414
|
if response.status_code >= 400:
|
|
415
|
+
if response.status_code == 429:
|
|
416
|
+
# A selected authoring profile must not silently fall
|
|
417
|
+
# back to a different model. Preserve the actual
|
|
418
|
+
# upstream condition so Studio can offer the user a
|
|
419
|
+
# useful retry/switch decision instead of reporting a
|
|
420
|
+
# misleading generic 502.
|
|
421
|
+
raise StudioError(
|
|
422
|
+
"MODEL_RATE_LIMITED",
|
|
423
|
+
"所选生成模型当前限流,请稍后重试或切换模型 Profile",
|
|
424
|
+
status_code=429,
|
|
425
|
+
details={"upstreamStatus": response.status_code},
|
|
426
|
+
)
|
|
427
|
+
upstream_detail = ""
|
|
428
|
+
try:
|
|
429
|
+
upstream_detail = response.text[:200]
|
|
430
|
+
except Exception: # noqa: BLE001 - 诊断信息尽力而为
|
|
431
|
+
upstream_detail = ""
|
|
386
432
|
raise StudioError(
|
|
387
433
|
"MODEL_REQUEST_FAILED",
|
|
388
434
|
"模型服务返回错误",
|
|
389
435
|
status_code=502,
|
|
390
|
-
details={
|
|
436
|
+
details={
|
|
437
|
+
"upstreamStatus": response.status_code,
|
|
438
|
+
"upstreamError": upstream_detail,
|
|
439
|
+
},
|
|
391
440
|
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
441
|
+
try:
|
|
442
|
+
if wire_api == "responses":
|
|
443
|
+
parsed = self._parse_responses_response(response, allow_empty=allow_empty)
|
|
444
|
+
else:
|
|
445
|
+
parsed = self._parse_response(response, allow_empty=allow_empty)
|
|
446
|
+
except StudioError as exc:
|
|
447
|
+
# finishReason=length 的空响应(大 JSON 被 max_tokens 截断):
|
|
448
|
+
# 一次性扩容 max_tokens 重发;再次截断则按原错误上抛。
|
|
449
|
+
if retry_on_length and not length_retried and self._is_length_truncation(exc):
|
|
450
|
+
length_retried = True
|
|
451
|
+
last_length_error = exc
|
|
452
|
+
payload = self._expand_length_budget(payload)
|
|
453
|
+
continue
|
|
454
|
+
raise
|
|
455
|
+
if (
|
|
456
|
+
retry_on_length
|
|
457
|
+
and not length_retried
|
|
458
|
+
and parsed.finish_reason in _LENGTH_FINISH_REASONS
|
|
459
|
+
):
|
|
460
|
+
# 有内容但被 length 截断(结构化输出必然残缺),同样扩容重发一次。
|
|
461
|
+
length_retried = True
|
|
462
|
+
payload = self._expand_length_budget(payload)
|
|
463
|
+
continue
|
|
464
|
+
return parsed
|
|
465
|
+
if last_length_error is not None:
|
|
466
|
+
raise last_length_error
|
|
395
467
|
raise AssertionError("unreachable")
|
|
396
468
|
|
|
469
|
+
@staticmethod
|
|
470
|
+
def _is_length_truncation(exc: StudioError) -> bool:
|
|
471
|
+
if exc.code != "MODEL_EMPTY_RESPONSE":
|
|
472
|
+
return False
|
|
473
|
+
return str(exc.details.get("finishReason") or "") in _LENGTH_FINISH_REASONS
|
|
474
|
+
|
|
475
|
+
@staticmethod
|
|
476
|
+
def _expand_length_budget(payload: dict[str, Any]) -> dict[str, Any]:
|
|
477
|
+
key = "max_output_tokens" if "max_output_tokens" in payload else "max_tokens"
|
|
478
|
+
current = int(payload.get(key) or 0)
|
|
479
|
+
payload[key] = max(current, _LENGTH_RETRY_TARGET_TOKENS)
|
|
480
|
+
return payload
|
|
481
|
+
|
|
397
482
|
@staticmethod
|
|
398
483
|
def _responses_payload(
|
|
399
484
|
model: ResolvedModel,
|
|
@@ -420,8 +505,9 @@ class OpenAICompatibleModelClient:
|
|
|
420
505
|
payload: dict[str, Any] = {
|
|
421
506
|
"model": model.model,
|
|
422
507
|
"input": items,
|
|
423
|
-
"max_output_tokens": model.parameters.max_tokens,
|
|
424
508
|
}
|
|
509
|
+
if model.parameters.max_tokens is not None:
|
|
510
|
+
payload["max_output_tokens"] = model.parameters.max_tokens
|
|
425
511
|
if instructions:
|
|
426
512
|
payload["instructions"] = "\n\n".join(instructions)
|
|
427
513
|
return payload
|
|
@@ -460,7 +546,7 @@ class OpenAICompatibleModelClient:
|
|
|
460
546
|
"MODEL_EMPTY_RESPONSE",
|
|
461
547
|
"模型未返回可用内容",
|
|
462
548
|
status_code=502,
|
|
463
|
-
details={"status": status},
|
|
549
|
+
details={"status": status, "finishReason": status},
|
|
464
550
|
)
|
|
465
551
|
raw_usage = payload.get("usage") or {}
|
|
466
552
|
usage = Usage(
|
|
@@ -32,7 +32,7 @@ async def test_model_profile_connection(
|
|
|
32
32
|
resolved.parameters = resolved.parameters.model_copy(
|
|
33
33
|
update={
|
|
34
34
|
"temperature": 0,
|
|
35
|
-
"max_tokens": min(resolved.parameters.max_tokens, 64),
|
|
35
|
+
"max_tokens": min(resolved.parameters.max_tokens or 2048, 64),
|
|
36
36
|
}
|
|
37
37
|
)
|
|
38
38
|
host = (urlparse(resolved.endpoint_url).hostname or "").lower().rstrip(".")
|