agentengine-sdk-python 0.8.1__py3-none-any.whl → 0.8.3__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.3.dist-info}/METADATA +25 -12
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +421 -232
- ksadk/_build_provenance.json +1 -0
- 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 +247 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +443 -259
- ksadk/api/client.py +528 -27
- ksadk/builders/code_builder.py +284 -8
- ksadk/builders/container_builder.py +8 -1
- ksadk/builders/managed_runtime_builder.py +42 -35
- ksadk/cli/__init__.py +23 -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_plugin.py +657 -0
- 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 +475 -331
- ksadk/configs/env_registry.py +94 -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/configs/settings.py +9 -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 +409 -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 +19 -0
- ksadk/conversations/context.py +230 -30
- ksadk/conversations/contracts.py +266 -0
- ksadk/conversations/message_projection.py +308 -26
- ksadk/conversations/model_context.py +49 -3
- ksadk/conversations/projector.py +616 -0
- ksadk/conversations/reducer.py +98 -0
- 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 +755 -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 +907 -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/__init__.py +45 -35
- ksadk/harness/runtime.py +220 -58
- 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 +62 -0
- ksadk/kernel/contracts.py +370 -0
- ksadk/kernel/control.py +220 -0
- ksadk/kernel/errors.py +79 -0
- ksadk/kernel/ingress.py +1123 -0
- ksadk/kernel/mapping.py +111 -0
- ksadk/kernel/memory_store.py +1134 -0
- ksadk/kernel/postgres_store.py +1757 -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 +1411 -0
- ksadk/kernel/state.py +145 -0
- ksadk/kernel/store.py +232 -0
- ksadk/kernel/worker.py +964 -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 +497 -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/plugins/__init__.py +167 -0
- ksadk/plugins/bridges/__init__.py +45 -0
- ksadk/plugins/bridges/codex.py +552 -0
- ksadk/plugins/bridges/dsh.py +1116 -0
- ksadk/plugins/builtins.py +747 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +167 -0
- ksadk/plugins/composition.py +529 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +451 -0
- ksadk/plugins/dsh_toolchain.py +935 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +724 -0
- ksadk/plugins/providers/__init__.py +85 -0
- ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
- ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
- ksadk/plugins/providers/codex.py +722 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/harness.py +627 -0
- ksadk/plugins/providers/harness_dsh.py +91 -0
- ksadk/plugins/providers/legacy.py +215 -0
- ksadk/plugins/providers/legacy_catalog.py +100 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +331 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -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 +797 -0
- ksadk/runtime/adapter.py +119 -2
- ksadk/runtime/conversation_execution.py +316 -182
- ksadk/runtime/executor.py +144 -3
- ksadk/runtime/factory.py +268 -8
- ksadk/runtime/framework_adapters.py +18 -0
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +14 -1
- ksadk/runtime/preprocessing.py +25 -3
- ksadk/runtime/runner_adapter.py +293 -508
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/__init__.py +2 -1
- ksadk/sandbox/backends/e2b.py +35 -2
- ksadk/sandbox/factory.py +72 -0
- ksadk/scheduler/__init__.py +18 -0
- ksadk/scheduler/calendar.py +102 -0
- ksadk/scheduler/contracts.py +254 -0
- ksadk/scheduler/dispatcher.py +129 -0
- ksadk/scheduler/engine.py +416 -0
- ksadk/scheduler/sqlite_store.py +563 -0
- 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-10GPXIJ6.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-BXk1l-G1.js} +4 -4
- ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
- ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-D9uB55Oh.js} +4 -4
- ksadk/server/static/assets/MermaidBlock--OEYoXIJ.js +312 -0
- ksadk/server/static/assets/NativeTerminalPanel-DrEe1syq.js +1 -0
- ksadk/server/static/assets/abnfDiagram-VCTEODGH-eeTFJLvo.js +1 -0
- ksadk/server/static/assets/arc-S3ZNbIAd.js +1 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
- ksadk/server/static/assets/architectureDiagram-5GKGNRK7-CRjgEJDX.js +36 -0
- ksadk/server/static/assets/blockDiagram-NRAW4CY4-C7bsBoNH.js +129 -0
- ksadk/server/static/assets/c4Diagram-UCG6FXSJ-BX3c33Ob.js +38 -0
- ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
- ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-CZwF3oUP.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
- ksadk/server/static/assets/chunk-LCL6LL3I-CnorGV5q.js +206 -0
- ksadk/server/static/assets/chunk-POPQ4Y6H-CexntQA-.js +1 -0
- ksadk/server/static/assets/chunk-RHFEMEQ7-CHTC7UxA.js +168 -0
- ksadk/server/static/assets/chunk-SVP7TREG-BC1EWt_-.js +88 -0
- ksadk/server/static/assets/chunk-XXDRQBXY-BTG24xN0.js +1 -0
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
- ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-DEOCBe15.js +62 -0
- ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
- ksadk/server/static/assets/dagre-3AP2YEHR-DBThPuLa.js +4 -0
- ksadk/server/static/assets/dagre-ClTPrTht.js +1 -0
- ksadk/server/static/assets/diagram-S7CK7UJ4-DsZHrnpJ.js +30 -0
- ksadk/server/static/assets/diagram-UQ7AKVKN-BJR4nhVr.js +41 -0
- ksadk/server/static/assets/diagram-VSXAHHWV-xzcERX0a.js +3 -0
- ksadk/server/static/assets/diagram-VX7I27RA-tn2xM1xu.js +24 -0
- ksadk/server/static/assets/diagram-Z3DM3KII-BZPGBkw5.js +24 -0
- ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-4zN1_tq5.js} +1 -1
- ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-8VWI-2bZ.js} +1 -1
- ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-B-46q3Hk.js} +1 -1
- ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-B0gdk4rl.js} +1 -1
- ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-B1oWRmrH.js} +1 -1
- ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-BI1qQH4n.js} +1 -1
- ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-BJLUwH6W.js} +1 -1
- ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Bbwqa8TG.js} +1 -1
- ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-Btyo4vX4.js} +1 -1
- ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-C71CsujO.js} +1 -1
- ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-CXpH_Rdd.js} +1 -1
- ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-Cq4OL6nO.js} +1 -1
- ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-D1V3f1B4.js} +1 -1
- ksadk/server/static/assets/{dist-fhOuWds5.js → dist-DJmCPRVB.js} +1 -1
- ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DQAB0qn_.js} +2 -2
- ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DUwKa_l6.js} +1 -1
- ksadk/server/static/assets/dist-DdRBIceA.js +1 -0
- ksadk/server/static/assets/{dist-DWKByxWV.js → dist-DfGFrb0v.js} +1 -1
- ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-DjhwGUgZ.js} +1 -1
- ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-DyqV4fL4.js} +1 -1
- ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-M67Hwu0n.js} +1 -1
- ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-u8tVH5Ak.js} +1 -1
- ksadk/server/static/assets/ebnfDiagram-PWID7BFC-DVfMsLge.js +1 -0
- ksadk/server/static/assets/erDiagram-SSCWMZ5O-CWEZSYaw.js +99 -0
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
- ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-TWIxoyYL.js +292 -0
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-DDh2hd2S.js +106 -0
- ksadk/server/static/assets/index-B2k_urY8.js +241 -0
- ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
- ksadk/server/static/assets/infoDiagram-RXCK75RN-CgoyWiDR.js +2 -0
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-CNP3ui66.js} +10 -10
- ksadk/server/static/assets/katex-DWEzQy1d.js +257 -0
- ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-C-qHp7d2.js} +1 -1
- ksadk/server/static/assets/mermaid-parser.core-Cl-K943T.js +166 -0
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +29 -29
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
- ksadk/server/static/assets/pegDiagram-XKGWAZYB-BQVc9ia_.js +1 -0
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
- ksadk/server/static/assets/pieDiagram-E7YTZNPT-B6UCAvtY.js +39 -0
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +6 -6
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
- ksadk/server/static/assets/railroadDiagram-O6MQD6OU-Dt3FFDmK.js +1 -0
- ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-Cz_vQtgF.js +84 -0
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +10 -10
- ksadk/server/static/assets/sizeCapture-X5ZJPWSS-DXX54iyO.js +1 -0
- ksadk/server/static/assets/stateDiagram-HBIQ2CUA-Dp_lw9II.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
- ksadk/server/static/assets/swimlanes-XN3QIQJK-D8O5UlLG.js +1 -0
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +7 -7
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
- ksadk/server/static/assets/vennDiagram-4TSXK5OY-EnNl0DE7.js +34 -0
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DBu5fbYr.js +78 -0
- ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-BSVXs4IY.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/service_client.py +48 -0
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +1063 -69
- ksadk/studio/api_catalog_routes.py +5 -0
- ksadk/studio/api_contracts.py +148 -6
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/api_plugin_routes.py +480 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +259 -28
- ksadk/studio/authoring_coordinator.py +809 -67
- ksadk/studio/builder.py +197 -40
- ksadk/studio/capabilities.py +17 -0
- ksadk/studio/cloud.py +1920 -95
- ksadk/studio/codex_agent_service.py +137 -15
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +177 -25
- ksadk/studio/codex_manifest.py +71 -22
- ksadk/studio/codex_run.py +59 -4
- ksadk/studio/compatibility_report.py +445 -0
- ksadk/studio/compiler.py +7 -1
- ksadk/studio/contracts.py +234 -4
- ksadk/studio/dsh_provider_registration.py +768 -0
- ksadk/studio/event_store.py +79 -101
- ksadk/studio/framework_run.py +264 -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 +47 -5
- ksadk/studio/otel_trace.py +348 -42
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/plugin_composition.py +301 -0
- ksadk/studio/plugin_kernel_adapter.py +128 -0
- ksadk/studio/plugin_runtime.py +652 -0
- ksadk/studio/repository.py +23 -2
- ksadk/studio/resource_catalog.py +49 -1
- ksadk/studio/run_service.py +1266 -202
- ksadk/studio/runtime_source.py +51 -17
- ksadk/studio/scheduler_runtime.py +374 -0
- ksadk/studio/scheduler_service.py +211 -0
- ksadk/studio/service.py +1815 -117
- ksadk/studio/shared_web.py +199 -50
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
- ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/templates.py +100 -8
- ksadk/studio/validator.py +20 -1
- ksadk/studio/workspace.py +31 -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_runtime_common/workspace_files/path_utils.py +41 -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.3.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
ksadk/studio/api.py
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import base64
|
|
6
7
|
import hmac
|
|
7
8
|
import json
|
|
8
9
|
import os
|
|
9
10
|
import secrets
|
|
11
|
+
import time
|
|
10
12
|
from collections.abc import AsyncIterator
|
|
11
13
|
from contextlib import asynccontextmanager
|
|
12
14
|
from pathlib import Path
|
|
@@ -16,16 +18,33 @@ from fastapi import FastAPI, File, Header, Query, Request, Response, UploadFile
|
|
|
16
18
|
from fastapi.exceptions import RequestValidationError
|
|
17
19
|
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
|
18
20
|
from fastapi.staticfiles import StaticFiles
|
|
21
|
+
from starlette.background import BackgroundTask
|
|
19
22
|
|
|
23
|
+
from ksadk.api.client import AgentEngineAPIError
|
|
24
|
+
from ksadk.conversations.contracts import (
|
|
25
|
+
ConversationAttachmentPart,
|
|
26
|
+
ConversationTextPart,
|
|
27
|
+
validate_conversation_input,
|
|
28
|
+
)
|
|
29
|
+
from ksadk.conversations.projector import project_conversation_item
|
|
30
|
+
from ksadk.events.canonical import parse_runtime_event_lenient
|
|
31
|
+
from ksadk.scheduler.contracts import ScheduledTask
|
|
20
32
|
from ksadk.studio.api_catalog_routes import register_catalog_routes
|
|
21
33
|
from ksadk.studio.api_contracts import (
|
|
34
|
+
AgentScheduleRequest,
|
|
22
35
|
AuthoringCommitRequest,
|
|
23
36
|
BuildRequest,
|
|
37
|
+
CloudAgentVersionRollbackRequest,
|
|
38
|
+
CloudChatInteractionSubmitRequest,
|
|
39
|
+
CloudChatMessageRequest,
|
|
40
|
+
ContextPreviewRequest,
|
|
24
41
|
ConversationAuthoringRequest,
|
|
42
|
+
ConversationTurnRequest,
|
|
25
43
|
CreateAgentRequest,
|
|
26
|
-
|
|
44
|
+
ImportRootRequest,
|
|
27
45
|
InteractionSubmitRequest,
|
|
28
46
|
ProjectInspectRequest,
|
|
47
|
+
PromptCompileRequest,
|
|
29
48
|
QuickAuthoringRequest,
|
|
30
49
|
RollbackRequest,
|
|
31
50
|
RunRequest,
|
|
@@ -58,6 +77,8 @@ from ksadk.studio.api_helpers import (
|
|
|
58
77
|
from ksadk.studio.api_helpers import (
|
|
59
78
|
sse as _sse,
|
|
60
79
|
)
|
|
80
|
+
from ksadk.studio.api_memory_routes import register_memory_routes
|
|
81
|
+
from ksadk.studio.api_plugin_routes import register_plugin_routes
|
|
61
82
|
from ksadk.studio.codex_manifest import CodexAgentManifest
|
|
62
83
|
from ksadk.studio.contracts import (
|
|
63
84
|
AgentAppearance,
|
|
@@ -78,6 +99,117 @@ _PUBLIC_API_PATHS = {
|
|
|
78
99
|
}
|
|
79
100
|
_LOCAL_HOSTS = {"127.0.0.1", "::1", "localhost", "testserver"}
|
|
80
101
|
|
|
102
|
+
# RuntimeEvent/v2 itself is snake_case. Some pre-existing cloud SessionEvent
|
|
103
|
+
# projections, however, serialized the *envelope* with the REST camelCase
|
|
104
|
+
# convention. This is a narrow transport normalizer, not a provider payload
|
|
105
|
+
# rewrite: only fields owned by the frozen RuntimeEvent envelope/content
|
|
106
|
+
# contracts are translated before strict parsing.
|
|
107
|
+
_RUNTIME_EVENT_WIRE_ALIASES = {
|
|
108
|
+
"schemaVersion": "schema_version",
|
|
109
|
+
"eventId": "event_id",
|
|
110
|
+
"runId": "run_id",
|
|
111
|
+
"runSeq": "run_seq",
|
|
112
|
+
"scopeId": "scope_id",
|
|
113
|
+
"parentScopeId": "parent_scope_id",
|
|
114
|
+
"eventType": "event_type",
|
|
115
|
+
"itemId": "item_id",
|
|
116
|
+
"itemKind": "item_kind",
|
|
117
|
+
"interactionId": "interaction_id",
|
|
118
|
+
"interactionKind": "interaction_kind",
|
|
119
|
+
"continuationId": "continuation_id",
|
|
120
|
+
"resumeAttemptId": "resume_attempt_id",
|
|
121
|
+
"outputRefs": "output_refs",
|
|
122
|
+
"inputTokens": "input_tokens",
|
|
123
|
+
"outputTokens": "output_tokens",
|
|
124
|
+
"totalTokens": "total_tokens",
|
|
125
|
+
"cachedTokens": "cached_tokens",
|
|
126
|
+
"reasoningTokens": "reasoning_tokens",
|
|
127
|
+
}
|
|
128
|
+
_SOURCE_WIRE_ALIASES = {
|
|
129
|
+
"nativeEventId": "native_event_id",
|
|
130
|
+
"nativeCursor": "native_cursor",
|
|
131
|
+
"nativeRunId": "native_run_id",
|
|
132
|
+
"nativeItemId": "native_item_id",
|
|
133
|
+
}
|
|
134
|
+
_CONTENT_WIRE_ALIASES = {
|
|
135
|
+
"contentType": "content_type",
|
|
136
|
+
"partId": "part_id",
|
|
137
|
+
"callId": "call_id",
|
|
138
|
+
"artifactId": "artifact_id",
|
|
139
|
+
"mimeType": "mime_type",
|
|
140
|
+
"isError": "is_error",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _normalize_cloud_runtime_event_wire(value: dict[str, Any]) -> dict[str, Any]:
|
|
145
|
+
"""Normalize only historic REST casing at the RuntimeEvent boundary."""
|
|
146
|
+
|
|
147
|
+
normalized = {
|
|
148
|
+
_RUNTIME_EVENT_WIRE_ALIASES.get(key, key): item for key, item in value.items()
|
|
149
|
+
}
|
|
150
|
+
source = normalized.get("source")
|
|
151
|
+
if isinstance(source, dict):
|
|
152
|
+
normalized["source"] = {
|
|
153
|
+
_SOURCE_WIRE_ALIASES.get(key, key): item for key, item in source.items()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def normalize_content(content: Any) -> Any:
|
|
157
|
+
if not isinstance(content, dict):
|
|
158
|
+
return content
|
|
159
|
+
return {_CONTENT_WIRE_ALIASES.get(key, key): item for key, item in content.items()}
|
|
160
|
+
|
|
161
|
+
normalized["update"] = normalize_content(normalized.get("update"))
|
|
162
|
+
for key in ("initial", "snapshot"):
|
|
163
|
+
snapshot = normalized.get(key)
|
|
164
|
+
if not isinstance(snapshot, dict):
|
|
165
|
+
continue
|
|
166
|
+
parts = snapshot.get("parts")
|
|
167
|
+
if isinstance(parts, list):
|
|
168
|
+
normalized[key] = {
|
|
169
|
+
**snapshot,
|
|
170
|
+
"parts": [normalize_content(part) for part in parts],
|
|
171
|
+
}
|
|
172
|
+
return normalized
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _cloud_event_conversation_item(
|
|
176
|
+
event: dict[str, Any], *, session_id: str
|
|
177
|
+
) -> dict[str, Any] | None:
|
|
178
|
+
"""Project a complete cloud RuntimeEvent without inventing a browser item.
|
|
179
|
+
|
|
180
|
+
Cloud SessionEvent history has a compatibility envelope around the runtime
|
|
181
|
+
payload. Keep the source event untouched, but add the same typed
|
|
182
|
+
ConversationItem that local Studio streams use whenever the nested event
|
|
183
|
+
validates against RuntimeEvent/v2. A malformed or older envelope is still
|
|
184
|
+
observable to diagnostics, never guessed into an actionable chat card.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
payload = event.get("payload") if isinstance(event.get("payload"), dict) else event
|
|
188
|
+
content = payload.get("content") if isinstance(payload.get("content"), dict) else {}
|
|
189
|
+
candidates = (
|
|
190
|
+
content.get("runtime_event"),
|
|
191
|
+
content.get("runtimeEvent"),
|
|
192
|
+
payload.get("runtime_event"),
|
|
193
|
+
payload.get("runtimeEvent"),
|
|
194
|
+
event.get("runtime_event"),
|
|
195
|
+
event.get("runtimeEvent"),
|
|
196
|
+
)
|
|
197
|
+
raw_event = next((candidate for candidate in candidates if isinstance(candidate, dict)), None)
|
|
198
|
+
if raw_event is None:
|
|
199
|
+
return None
|
|
200
|
+
try:
|
|
201
|
+
runtime_event = parse_runtime_event_lenient(
|
|
202
|
+
_normalize_cloud_runtime_event_wire(raw_event)
|
|
203
|
+
)
|
|
204
|
+
item = project_conversation_item(
|
|
205
|
+
runtime_event, # type: ignore[arg-type]
|
|
206
|
+
session_id=session_id,
|
|
207
|
+
run_id=runtime_event.run_id,
|
|
208
|
+
)
|
|
209
|
+
except (TypeError, ValueError):
|
|
210
|
+
return None
|
|
211
|
+
return item.model_dump(by_alias=True, exclude_none=True, mode="json")
|
|
212
|
+
|
|
81
213
|
|
|
82
214
|
def create_studio_app(
|
|
83
215
|
root: Path | str,
|
|
@@ -94,8 +226,13 @@ def create_studio_app(
|
|
|
94
226
|
@asynccontextmanager
|
|
95
227
|
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
|
96
228
|
try:
|
|
229
|
+
await studio.start()
|
|
230
|
+
await studio.run_service.recover_interrupted()
|
|
231
|
+
await studio.scheduler.start_if_available()
|
|
97
232
|
yield
|
|
98
233
|
finally:
|
|
234
|
+
await studio.scheduler.stop()
|
|
235
|
+
await studio.aclose()
|
|
99
236
|
studio.credentials.clear_session()
|
|
100
237
|
|
|
101
238
|
app = FastAPI(
|
|
@@ -109,7 +246,82 @@ def create_studio_app(
|
|
|
109
246
|
app.state.studio_service = studio
|
|
110
247
|
app.state.session_token = session_secret
|
|
111
248
|
app.state.csrf_token = csrf_secret
|
|
249
|
+
|
|
250
|
+
def _stream_studio_run(
|
|
251
|
+
build_id: str,
|
|
252
|
+
*,
|
|
253
|
+
user_input: str,
|
|
254
|
+
session_id: str | None,
|
|
255
|
+
model: str | None,
|
|
256
|
+
sandbox: str | None,
|
|
257
|
+
reasoning_effort: str | None = None,
|
|
258
|
+
approval_mode: str | None = None,
|
|
259
|
+
collaboration_mode: str | None = None,
|
|
260
|
+
goal_objective: str | None = None,
|
|
261
|
+
runtime_input: Any = None,
|
|
262
|
+
idempotency_key: str,
|
|
263
|
+
) -> StreamingResponse:
|
|
264
|
+
"""Create an observer-only SSE response for one durable Studio run."""
|
|
265
|
+
|
|
266
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
267
|
+
|
|
268
|
+
def observe(event):
|
|
269
|
+
queue.put_nowait(event)
|
|
270
|
+
|
|
271
|
+
# The Operation owns the runtime task. The SSE response is only one
|
|
272
|
+
# observer, so refreshing or switching chats cannot cancel the Run.
|
|
273
|
+
operation = studio.submit_studio_run(
|
|
274
|
+
build_id,
|
|
275
|
+
user_input,
|
|
276
|
+
session_id=session_id,
|
|
277
|
+
model=model,
|
|
278
|
+
sandbox=sandbox,
|
|
279
|
+
reasoning_effort=reasoning_effort,
|
|
280
|
+
approval_mode=approval_mode,
|
|
281
|
+
collaboration_mode=collaboration_mode,
|
|
282
|
+
goal_objective=goal_objective,
|
|
283
|
+
runtime_input=runtime_input,
|
|
284
|
+
idempotency_key=idempotency_key,
|
|
285
|
+
on_event=observe,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
async def render():
|
|
289
|
+
while True:
|
|
290
|
+
try:
|
|
291
|
+
event = await asyncio.wait_for(queue.get(), timeout=0.1)
|
|
292
|
+
except TimeoutError:
|
|
293
|
+
event = None
|
|
294
|
+
if event is not None:
|
|
295
|
+
data = json.dumps(
|
|
296
|
+
event.data,
|
|
297
|
+
ensure_ascii=False,
|
|
298
|
+
separators=(",", ":"),
|
|
299
|
+
)
|
|
300
|
+
yield f"id: {event.id}\nevent: {event.type}\ndata: {data}\n\n"
|
|
301
|
+
current = studio.operations.get(operation.id)
|
|
302
|
+
if (
|
|
303
|
+
current.status
|
|
304
|
+
in {
|
|
305
|
+
OperationStatus.SUCCEEDED,
|
|
306
|
+
OperationStatus.FAILED,
|
|
307
|
+
OperationStatus.CANCELLED,
|
|
308
|
+
OperationStatus.INTERRUPTED,
|
|
309
|
+
}
|
|
310
|
+
and queue.empty()
|
|
311
|
+
):
|
|
312
|
+
if current.status == OperationStatus.FAILED:
|
|
313
|
+
data = json.dumps(current.error or {}, ensure_ascii=False)
|
|
314
|
+
yield f"event: run.failed\ndata: {data}\n\n"
|
|
315
|
+
break
|
|
316
|
+
|
|
317
|
+
return StreamingResponse(
|
|
318
|
+
render(),
|
|
319
|
+
media_type="text/event-stream",
|
|
320
|
+
headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
|
|
321
|
+
)
|
|
322
|
+
|
|
112
323
|
static_root = Path(__file__).with_name("static")
|
|
324
|
+
_studio_startup_epoch = str(int(time.time()))
|
|
113
325
|
shared_web = StudioSharedWebBridge(studio)
|
|
114
326
|
app.state.shared_web_bridge = shared_web
|
|
115
327
|
app.mount("/static", StaticFiles(directory=static_root), name="studio-static")
|
|
@@ -143,6 +355,7 @@ def create_studio_app(
|
|
|
143
355
|
large_upload_paths = {
|
|
144
356
|
"/api/v1/catalog/skills:import": 52 * 1024 * 1024,
|
|
145
357
|
"/api/v1/authoring/imports:inspect": 102 * 1024 * 1024,
|
|
358
|
+
"/api/v1/evaluation-files": 2 * 1024 * 1024 + 64 * 1024,
|
|
146
359
|
}
|
|
147
360
|
request_limit = large_upload_paths.get(request.url.path, 2 * 1024 * 1024)
|
|
148
361
|
if content_length and int(content_length) > request_limit:
|
|
@@ -203,6 +416,39 @@ def create_studio_app(
|
|
|
203
416
|
async def studio_error_handler(request: Request, exc: StudioError):
|
|
204
417
|
return _error_response(exc, request)
|
|
205
418
|
|
|
419
|
+
@app.exception_handler(AgentEngineAPIError)
|
|
420
|
+
async def agentengine_api_error_handler(request: Request, exc: AgentEngineAPIError):
|
|
421
|
+
http_status = exc.details.get("http_status", 502)
|
|
422
|
+
status_code = (
|
|
423
|
+
http_status if isinstance(http_status, int) and 400 <= http_status < 600 else 502
|
|
424
|
+
)
|
|
425
|
+
return _error_response(
|
|
426
|
+
StudioError(
|
|
427
|
+
"CLOUD_API_ERROR",
|
|
428
|
+
exc.message or "云端服务返回错误",
|
|
429
|
+
status_code=status_code,
|
|
430
|
+
details={"api_code": exc.code, "raw_code": exc.raw_code},
|
|
431
|
+
),
|
|
432
|
+
request,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
@app.exception_handler(Exception)
|
|
436
|
+
async def generic_error_handler(request: Request, exc: Exception):
|
|
437
|
+
# Catch-all for bare Exception raises from the API client (e.g. HTTP
|
|
438
|
+
# errors, JSON parse failures) so they surface as structured JSON
|
|
439
|
+
# instead of opaque 500s. StudioError and AgentEngineAPIError are
|
|
440
|
+
# handled by their own dedicated handlers above.
|
|
441
|
+
if isinstance(exc, StudioError):
|
|
442
|
+
return _error_response(exc, request)
|
|
443
|
+
return _error_response(
|
|
444
|
+
StudioError(
|
|
445
|
+
"INTERNAL_ERROR",
|
|
446
|
+
"Studio 内部错误,请根据请求 ID 查看本地诊断日志。",
|
|
447
|
+
status_code=500,
|
|
448
|
+
),
|
|
449
|
+
request,
|
|
450
|
+
)
|
|
451
|
+
|
|
206
452
|
@app.exception_handler(RequestValidationError)
|
|
207
453
|
async def validation_error_handler(request: Request, exc: RequestValidationError):
|
|
208
454
|
first = exc.errors()[0] if exc.errors() else {}
|
|
@@ -220,7 +466,24 @@ def create_studio_app(
|
|
|
220
466
|
@app.get("/")
|
|
221
467
|
async def index():
|
|
222
468
|
path = static_root / "index.html"
|
|
223
|
-
|
|
469
|
+
html = path.read_text(encoding="utf-8")
|
|
470
|
+
# Inject a startup-scoped version so a restarted Studio with a rebuilt
|
|
471
|
+
# bundle always wins over a stale browser tab. The bundle filenames
|
|
472
|
+
# are already content-hashed; this only defeats cached index.html.
|
|
473
|
+
if "?v=" not in html:
|
|
474
|
+
import re
|
|
475
|
+
|
|
476
|
+
def _add_version(match: "re.Match[str]") -> str:
|
|
477
|
+
attr, path_part = match.group(1), match.group(2)
|
|
478
|
+
return f'{attr}="/static/assets/{path_part}?v={_studio_startup_epoch}"'
|
|
479
|
+
|
|
480
|
+
html = re.sub(
|
|
481
|
+
r'(src|href)="/static/assets/([^"]+)"',
|
|
482
|
+
_add_version,
|
|
483
|
+
html,
|
|
484
|
+
)
|
|
485
|
+
response = Response(content=html, media_type="text/html")
|
|
486
|
+
response.headers["Cache-Control"] = "no-store"
|
|
224
487
|
if security_enabled:
|
|
225
488
|
response.set_cookie(
|
|
226
489
|
"agentkit_studio_session",
|
|
@@ -289,6 +552,16 @@ def create_studio_app(
|
|
|
289
552
|
goal_objective = str(
|
|
290
553
|
metadata.get("goal_objective") or metadata.get("goalObjective") or ""
|
|
291
554
|
).strip()
|
|
555
|
+
reasoning = payload.get("reasoning")
|
|
556
|
+
reasoning = reasoning if isinstance(reasoning, dict) else {}
|
|
557
|
+
reasoning_effort = str(reasoning.get("effort") or "").strip().lower()
|
|
558
|
+
if reasoning_effort and reasoning_effort not in {"low", "medium", "high"}:
|
|
559
|
+
raise StudioError(
|
|
560
|
+
"REASONING_EFFORT_INVALID",
|
|
561
|
+
"推理强度必须是 low、medium 或 high",
|
|
562
|
+
status_code=422,
|
|
563
|
+
field="reasoning.effort",
|
|
564
|
+
)
|
|
292
565
|
session_id = _responses_session_id(payload, bridge=shared_web)
|
|
293
566
|
response_id = str(
|
|
294
567
|
metadata.get("invocation_id")
|
|
@@ -304,6 +577,8 @@ def create_studio_app(
|
|
|
304
577
|
"ApprovalMode": requested_approval_mode,
|
|
305
578
|
"CollaborationMode": collaboration_mode,
|
|
306
579
|
"GoalObjective": goal_objective,
|
|
580
|
+
"ReasoningEffort": reasoning_effort,
|
|
581
|
+
"ModelExplicit": bool(str(payload.get("model") or "").strip()),
|
|
307
582
|
}
|
|
308
583
|
bridge_payload["Model"] = shared_web.select_model(
|
|
309
584
|
bridge_payload["AgentId"],
|
|
@@ -364,14 +639,14 @@ def create_studio_app(
|
|
|
364
639
|
elif action == "DeleteSession":
|
|
365
640
|
data = shared_web.delete_session(str(payload.get("SessionId") or ""))
|
|
366
641
|
elif action == "ListSessionMessages":
|
|
367
|
-
data = shared_web.list_messages(
|
|
642
|
+
data = await shared_web.list_messages(
|
|
368
643
|
str(payload.get("SessionId") or ""),
|
|
369
644
|
after_seq_id=_optional_int(payload.get("AfterSeqId")),
|
|
370
645
|
before_seq_id=_optional_int(payload.get("BeforeSeqId")),
|
|
371
646
|
limit=int(payload.get("Limit") or 50),
|
|
372
647
|
)
|
|
373
648
|
elif action == "ListSessionEvents":
|
|
374
|
-
data = shared_web.list_session_events(str(payload.get("SessionId") or ""))
|
|
649
|
+
data = await shared_web.list_session_events(str(payload.get("SessionId") or ""))
|
|
375
650
|
elif action == "RunAgent":
|
|
376
651
|
return StreamingResponse(
|
|
377
652
|
shared_web.stream_run(payload),
|
|
@@ -461,6 +736,7 @@ def create_studio_app(
|
|
|
461
736
|
"name": studio.workspace.root.name,
|
|
462
737
|
"path": str(studio.workspace.root),
|
|
463
738
|
},
|
|
739
|
+
"operationScope": studio.deployment_operation_scope(),
|
|
464
740
|
"features": {
|
|
465
741
|
"build": True,
|
|
466
742
|
"run": True,
|
|
@@ -470,8 +746,10 @@ def create_studio_app(
|
|
|
470
746
|
"deployment": True,
|
|
471
747
|
"cloudRebuild": False,
|
|
472
748
|
"reactChat": True,
|
|
749
|
+
"scheduler": studio.scheduler.availability(),
|
|
473
750
|
},
|
|
474
751
|
"runtimes": studio.runtime_catalog(),
|
|
752
|
+
"importableProject": studio.detect_importable_project(),
|
|
475
753
|
}
|
|
476
754
|
|
|
477
755
|
@app.get("/api/v1/system/settings")
|
|
@@ -482,9 +760,118 @@ def create_studio_app(
|
|
|
482
760
|
async def update_settings(payload: dict[str, Any]):
|
|
483
761
|
return studio.update_settings(payload)
|
|
484
762
|
|
|
763
|
+
@app.get("/api/v1/schedules")
|
|
764
|
+
async def list_schedules():
|
|
765
|
+
return {
|
|
766
|
+
"items": studio.scheduler.list_tasks(),
|
|
767
|
+
"availability": studio.scheduler.availability(),
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
@app.post("/api/v1/schedules", status_code=201)
|
|
771
|
+
async def create_schedule(payload: ScheduledTask):
|
|
772
|
+
studio.validate_schedule_build(
|
|
773
|
+
payload.target.agent_version_ref,
|
|
774
|
+
agent_id=payload.target.agent_id,
|
|
775
|
+
)
|
|
776
|
+
return studio.scheduler.create_task(payload)
|
|
777
|
+
|
|
778
|
+
@app.get("/api/v1/schedules/{task_id}")
|
|
779
|
+
async def get_schedule(task_id: str):
|
|
780
|
+
return studio.scheduler.get_task(task_id)
|
|
781
|
+
|
|
782
|
+
@app.put("/api/v1/schedules/{task_id}")
|
|
783
|
+
async def update_schedule(task_id: str, payload: ScheduledTask):
|
|
784
|
+
studio.validate_schedule_build(
|
|
785
|
+
payload.target.agent_version_ref,
|
|
786
|
+
agent_id=payload.target.agent_id,
|
|
787
|
+
)
|
|
788
|
+
return studio.scheduler.update_task(task_id, payload)
|
|
789
|
+
|
|
790
|
+
@app.delete("/api/v1/schedules/{task_id}", status_code=204)
|
|
791
|
+
async def delete_schedule(task_id: str):
|
|
792
|
+
studio.scheduler.delete_task(task_id)
|
|
793
|
+
return Response(status_code=204)
|
|
794
|
+
|
|
795
|
+
@app.post("/api/v1/schedules/{task_id}:run", status_code=202)
|
|
796
|
+
async def run_schedule_now(task_id: str):
|
|
797
|
+
return await studio.scheduler.run_now(task_id)
|
|
798
|
+
|
|
799
|
+
@app.get("/api/v1/schedules/{task_id}/occurrences")
|
|
800
|
+
async def list_schedule_occurrences(task_id: str, limit: int = Query(default=50, ge=1, le=200)):
|
|
801
|
+
return {"items": studio.scheduler.list_occurrences(task_id, limit=limit)}
|
|
802
|
+
|
|
803
|
+
@app.get("/api/v1/schedule-occurrences")
|
|
804
|
+
async def list_all_schedule_occurrences(
|
|
805
|
+
limit: int = Query(default=200, ge=1, le=500),
|
|
806
|
+
):
|
|
807
|
+
return {"items": studio.scheduler.list_all_occurrences(limit=limit)}
|
|
808
|
+
|
|
809
|
+
@app.get("/api/v1/agents/{agent_id}/schedules")
|
|
810
|
+
async def list_agent_schedules(agent_id: str):
|
|
811
|
+
return {
|
|
812
|
+
"items": studio.list_agent_schedules(agent_id),
|
|
813
|
+
"availability": studio.scheduler.availability(),
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
@app.post("/api/v1/agents/{agent_id}/schedules", status_code=201)
|
|
817
|
+
async def create_agent_schedule(agent_id: str, payload: AgentScheduleRequest):
|
|
818
|
+
return await studio.create_agent_schedule(
|
|
819
|
+
agent_id,
|
|
820
|
+
display_name=payload.display_name,
|
|
821
|
+
prompt=payload.prompt,
|
|
822
|
+
schedule=payload.schedule,
|
|
823
|
+
enabled=payload.enabled,
|
|
824
|
+
continuity=payload.continuity,
|
|
825
|
+
session_id=payload.session_id,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
@app.get("/api/v1/agents/{agent_id}/schedules/{task_id}")
|
|
829
|
+
async def get_agent_schedule(agent_id: str, task_id: str):
|
|
830
|
+
return studio.get_agent_schedule(agent_id, task_id)
|
|
831
|
+
|
|
832
|
+
@app.put("/api/v1/agents/{agent_id}/schedules/{task_id}")
|
|
833
|
+
async def update_agent_schedule(
|
|
834
|
+
agent_id: str,
|
|
835
|
+
task_id: str,
|
|
836
|
+
payload: AgentScheduleRequest,
|
|
837
|
+
):
|
|
838
|
+
return studio.update_agent_schedule(
|
|
839
|
+
agent_id,
|
|
840
|
+
task_id,
|
|
841
|
+
display_name=payload.display_name,
|
|
842
|
+
prompt=payload.prompt,
|
|
843
|
+
schedule=payload.schedule,
|
|
844
|
+
enabled=payload.enabled,
|
|
845
|
+
continuity=payload.continuity,
|
|
846
|
+
session_id=payload.session_id,
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
@app.delete("/api/v1/agents/{agent_id}/schedules/{task_id}", status_code=204)
|
|
850
|
+
async def delete_agent_schedule(agent_id: str, task_id: str):
|
|
851
|
+
studio.get_agent_schedule(agent_id, task_id)
|
|
852
|
+
studio.scheduler.delete_task(task_id)
|
|
853
|
+
return Response(status_code=204)
|
|
854
|
+
|
|
855
|
+
@app.post("/api/v1/agents/{agent_id}/schedules/{task_id}:run", status_code=202)
|
|
856
|
+
async def run_agent_schedule_now(agent_id: str, task_id: str):
|
|
857
|
+
return await studio.run_agent_schedule_now(agent_id, task_id)
|
|
858
|
+
|
|
859
|
+
@app.get("/api/v1/agents/{agent_id}/schedules/{task_id}/occurrences")
|
|
860
|
+
async def list_agent_schedule_occurrences(
|
|
861
|
+
agent_id: str,
|
|
862
|
+
task_id: str,
|
|
863
|
+
limit: int = Query(default=50, ge=1, le=200),
|
|
864
|
+
):
|
|
865
|
+
studio.get_agent_schedule(agent_id, task_id)
|
|
866
|
+
return {"items": studio.scheduler.list_occurrences(task_id, limit=limit)}
|
|
867
|
+
|
|
485
868
|
@app.post("/api/v1/workspaces:open")
|
|
486
869
|
async def open_workspace(payload: WorkspaceOpenRequest):
|
|
487
|
-
|
|
870
|
+
# This endpoint only reconnects to the daemon's already-bound root. Do
|
|
871
|
+
# not resolve or otherwise touch a caller-provided filesystem path.
|
|
872
|
+
requested = os.path.normcase(os.path.abspath(os.path.expanduser(payload.path)))
|
|
873
|
+
bound_root = os.path.normcase(str(studio.workspace.root))
|
|
874
|
+
if requested != bound_root:
|
|
488
875
|
raise StudioError(
|
|
489
876
|
"WORKSPACE_PATH_FORBIDDEN",
|
|
490
877
|
"当前 Daemon 不允许切换到启动 root 之外的工作区",
|
|
@@ -554,57 +941,117 @@ def create_studio_app(
|
|
|
554
941
|
"Codex Studio 只支持只读本地运行",
|
|
555
942
|
status_code=422,
|
|
556
943
|
)
|
|
557
|
-
|
|
558
|
-
queue: asyncio.Queue = asyncio.Queue()
|
|
559
|
-
|
|
560
|
-
def observe(event):
|
|
561
|
-
queue.put_nowait(event)
|
|
562
|
-
|
|
563
|
-
# The Operation owns the runtime task. The SSE response is only one
|
|
564
|
-
# observer, so refreshing or switching chats cannot cancel the Run.
|
|
565
|
-
operation = studio.submit_studio_run(
|
|
944
|
+
return _stream_studio_run(
|
|
566
945
|
build_id,
|
|
567
|
-
payload.input.content,
|
|
946
|
+
user_input=payload.input.content,
|
|
568
947
|
session_id=payload.session_id,
|
|
569
948
|
model=payload.model,
|
|
570
949
|
sandbox=payload.sandbox,
|
|
571
950
|
idempotency_key=key,
|
|
572
|
-
on_event=observe,
|
|
573
951
|
)
|
|
574
952
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if event is not None:
|
|
582
|
-
data = json.dumps(
|
|
583
|
-
event.data,
|
|
584
|
-
ensure_ascii=False,
|
|
585
|
-
separators=(",", ":"),
|
|
586
|
-
)
|
|
587
|
-
yield f"id: {event.id}\nevent: {event.type}\ndata: {data}\n\n"
|
|
588
|
-
current = studio.operations.get(operation.id)
|
|
589
|
-
if (
|
|
590
|
-
current.status
|
|
591
|
-
in {
|
|
592
|
-
OperationStatus.SUCCEEDED,
|
|
593
|
-
OperationStatus.FAILED,
|
|
594
|
-
OperationStatus.CANCELLED,
|
|
595
|
-
OperationStatus.INTERRUPTED,
|
|
596
|
-
}
|
|
597
|
-
and queue.empty()
|
|
598
|
-
):
|
|
599
|
-
if current.status == OperationStatus.FAILED:
|
|
600
|
-
data = json.dumps(current.error or {}, ensure_ascii=False)
|
|
601
|
-
yield f"event: run.failed\ndata: {data}\n\n"
|
|
602
|
-
break
|
|
953
|
+
@app.get("/api/v1/builds/{build_id}/conversation-surface")
|
|
954
|
+
async def get_conversation_surface(
|
|
955
|
+
build_id: str,
|
|
956
|
+
session_id: str = Query(alias="sessionId", min_length=1, max_length=256),
|
|
957
|
+
):
|
|
958
|
+
return studio.conversation_surface(build_id, session_id=session_id)
|
|
603
959
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
960
|
+
@app.get("/api/v1/agents/{agent_id}/conversation-surface")
|
|
961
|
+
async def get_agent_conversation_surface(
|
|
962
|
+
agent_id: str,
|
|
963
|
+
session_id: str = Query(alias="sessionId", min_length=1, max_length=256),
|
|
964
|
+
):
|
|
965
|
+
"""Resolve the immutable Build and its composer contract atomically.
|
|
966
|
+
|
|
967
|
+
The browser selects an Agent, not a mutable Draft or a Build id. This
|
|
968
|
+
route gives it the same current-Build decision used by chat and local
|
|
969
|
+
Scheduler authoring, so controls cannot be rendered from a stale or
|
|
970
|
+
unrelated Build.
|
|
971
|
+
"""
|
|
972
|
+
|
|
973
|
+
build = await studio.ensure_current_build(agent_id)
|
|
974
|
+
return {
|
|
975
|
+
"buildId": build.id,
|
|
976
|
+
"surface": studio.conversation_surface(
|
|
977
|
+
build.id,
|
|
978
|
+
session_id=session_id,
|
|
979
|
+
),
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
@app.post("/api/v1/builds/{build_id}/conversation:stream")
|
|
983
|
+
async def stream_conversation_turn(
|
|
984
|
+
build_id: str,
|
|
985
|
+
payload: ConversationTurnRequest,
|
|
986
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
987
|
+
):
|
|
988
|
+
key = _require_idempotency_key(idempotency_key)
|
|
989
|
+
conversation_input = payload.input
|
|
990
|
+
if conversation_input.idempotency_key != key:
|
|
991
|
+
raise StudioError(
|
|
992
|
+
"CONVERSATION_IDEMPOTENCY_MISMATCH",
|
|
993
|
+
"ConversationInput 的幂等键必须与请求头一致",
|
|
994
|
+
status_code=422,
|
|
995
|
+
)
|
|
996
|
+
surface = studio.conversation_surface(
|
|
997
|
+
build_id,
|
|
998
|
+
session_id=conversation_input.session_id,
|
|
999
|
+
)
|
|
1000
|
+
try:
|
|
1001
|
+
validate_conversation_input(surface, conversation_input)
|
|
1002
|
+
except ValueError as exc:
|
|
1003
|
+
raise StudioError(
|
|
1004
|
+
"CONVERSATION_INPUT_UNSUPPORTED",
|
|
1005
|
+
"当前 Agent 不支持此会话输入",
|
|
1006
|
+
status_code=422,
|
|
1007
|
+
details={"reason": str(exc), "surfaceId": surface.surface_id},
|
|
1008
|
+
) from exc
|
|
1009
|
+
text_parts = [
|
|
1010
|
+
part.text for part in conversation_input.parts if isinstance(part, ConversationTextPart)
|
|
1011
|
+
]
|
|
1012
|
+
runtime_parts: list[dict[str, str]] = [
|
|
1013
|
+
{"type": "text", "text": value} for value in text_parts
|
|
1014
|
+
]
|
|
1015
|
+
attachment_names: list[str] = []
|
|
1016
|
+
for part in conversation_input.parts:
|
|
1017
|
+
if not isinstance(part, ConversationAttachmentPart):
|
|
1018
|
+
continue
|
|
1019
|
+
stored = studio.conversation_attachments.resolve(part.attachment_ref)
|
|
1020
|
+
if part.media_type.lower() != stored.media_type.lower():
|
|
1021
|
+
raise StudioError(
|
|
1022
|
+
"CONVERSATION_ATTACHMENT_METADATA_MISMATCH",
|
|
1023
|
+
"会话附件类型与已上传内容不一致",
|
|
1024
|
+
status_code=422,
|
|
1025
|
+
details={"attachmentRef": part.attachment_ref},
|
|
1026
|
+
)
|
|
1027
|
+
encoded = base64.b64encode(stored.path.read_bytes()).decode("ascii")
|
|
1028
|
+
data_url = f"data:{stored.media_type};base64,{encoded}"
|
|
1029
|
+
attachment_names.append(part.name or stored.name)
|
|
1030
|
+
if stored.media_type.startswith("image/"):
|
|
1031
|
+
runtime_parts.append({"type": "image", "url": data_url})
|
|
1032
|
+
else:
|
|
1033
|
+
runtime_parts.append(
|
|
1034
|
+
{
|
|
1035
|
+
"type": "input_file",
|
|
1036
|
+
"filename": part.name or stored.name,
|
|
1037
|
+
"file_data": data_url,
|
|
1038
|
+
}
|
|
1039
|
+
)
|
|
1040
|
+
display_input = "\n".join(text_parts).strip()
|
|
1041
|
+
if not display_input:
|
|
1042
|
+
display_input = "请处理本轮附件:" + "、".join(attachment_names)
|
|
1043
|
+
return _stream_studio_run(
|
|
1044
|
+
build_id,
|
|
1045
|
+
user_input=display_input,
|
|
1046
|
+
session_id=conversation_input.session_id,
|
|
1047
|
+
model=conversation_input.model_ref,
|
|
1048
|
+
sandbox=payload.sandbox,
|
|
1049
|
+
reasoning_effort=conversation_input.reasoning,
|
|
1050
|
+
approval_mode=conversation_input.approval_mode,
|
|
1051
|
+
collaboration_mode=conversation_input.collaboration_mode,
|
|
1052
|
+
goal_objective=conversation_input.goal_objective,
|
|
1053
|
+
runtime_input=runtime_parts if attachment_names else None,
|
|
1054
|
+
idempotency_key=key,
|
|
608
1055
|
)
|
|
609
1056
|
|
|
610
1057
|
@app.post("/api/v1/agents", status_code=201)
|
|
@@ -618,6 +1065,15 @@ def create_studio_app(
|
|
|
618
1065
|
runtime=payload.runtime,
|
|
619
1066
|
)
|
|
620
1067
|
|
|
1068
|
+
@app.post("/api/v1/conversation-attachments", status_code=201)
|
|
1069
|
+
async def upload_conversation_attachment(file: UploadFile = File(...)):
|
|
1070
|
+
content = await file.read(studio.conversation_attachments.MAX_BYTES + 1)
|
|
1071
|
+
return studio.conversation_attachments.store(
|
|
1072
|
+
content,
|
|
1073
|
+
filename=file.filename or "attachment",
|
|
1074
|
+
media_type=file.content_type or "application/octet-stream",
|
|
1075
|
+
)
|
|
1076
|
+
|
|
621
1077
|
@app.post("/api/v1/assets/agent-avatars", status_code=201)
|
|
622
1078
|
async def upload_agent_avatar(request: Request):
|
|
623
1079
|
declared_size = request.headers.get("Content-Length")
|
|
@@ -674,8 +1130,26 @@ def create_studio_app(
|
|
|
674
1130
|
return await studio.compose_agent_conversation(
|
|
675
1131
|
messages=[item.model_dump(mode="json") for item in payload.messages],
|
|
676
1132
|
model_profile_id=payload.model_profile_id,
|
|
1133
|
+
runtime_type=payload.runtime_type,
|
|
1134
|
+
agent_model_profile_ids=payload.agent_model_profile_ids,
|
|
1135
|
+
agent_default_model_profile_id=payload.agent_default_model_profile_id,
|
|
1136
|
+
tool_resource_ids=payload.tool_resource_ids,
|
|
1137
|
+
mcp_resource_ids=payload.mcp_resource_ids,
|
|
1138
|
+
skill_resource_ids=payload.skill_resource_ids,
|
|
1139
|
+
request_id=payload.request_id,
|
|
677
1140
|
)
|
|
678
1141
|
|
|
1142
|
+
@app.get("/api/v1/authoring/conversations:status/{request_id}")
|
|
1143
|
+
async def get_authoring_conversation_status(request_id: str):
|
|
1144
|
+
status = studio.conversation_authoring_status(request_id)
|
|
1145
|
+
if status is None:
|
|
1146
|
+
raise StudioError(
|
|
1147
|
+
"AUTHORING_STATUS_NOT_FOUND",
|
|
1148
|
+
"未找到该构建请求的阶段记录",
|
|
1149
|
+
status_code=404,
|
|
1150
|
+
)
|
|
1151
|
+
return status
|
|
1152
|
+
|
|
679
1153
|
@app.post("/api/v1/authoring/imports:inspect")
|
|
680
1154
|
async def inspect_agent_import(file: UploadFile = File(...)):
|
|
681
1155
|
content = await file.read(100 * 1024 * 1024 + 1)
|
|
@@ -740,6 +1214,7 @@ def create_studio_app(
|
|
|
740
1214
|
agent_id: str,
|
|
741
1215
|
spec: AgentSpec,
|
|
742
1216
|
if_match: str | None = Header(default=None, alias="If-Match"),
|
|
1217
|
+
name: str | None = Query(default=None, min_length=1, max_length=128),
|
|
743
1218
|
):
|
|
744
1219
|
if not if_match:
|
|
745
1220
|
raise StudioError(
|
|
@@ -759,6 +1234,7 @@ def create_studio_app(
|
|
|
759
1234
|
agent_id,
|
|
760
1235
|
spec,
|
|
761
1236
|
expected_revision=revision,
|
|
1237
|
+
name=name,
|
|
762
1238
|
)
|
|
763
1239
|
|
|
764
1240
|
@app.put("/api/v1/agents/{agent_id}/bindings")
|
|
@@ -799,6 +1275,33 @@ def create_studio_app(
|
|
|
799
1275
|
level=payload.level,
|
|
800
1276
|
)
|
|
801
1277
|
|
|
1278
|
+
@app.post("/api/v1/workspace:import-root", status_code=201)
|
|
1279
|
+
async def import_root_project(payload: ImportRootRequest):
|
|
1280
|
+
"""PR-S6:一键导入根 Framework 项目(方案 §6.1)。"""
|
|
1281
|
+
return studio.import_root_project(name=payload.name, slug=payload.slug)
|
|
1282
|
+
|
|
1283
|
+
@app.post("/api/v1/agents/{agent_id}/prompt:compile")
|
|
1284
|
+
async def compile_prompt(agent_id: str, payload: PromptCompileRequest):
|
|
1285
|
+
"""PR-S2:Prompt 编译预览(方案 §6.2)。只读,不写 Session/Trace/Build。"""
|
|
1286
|
+
return studio.compile_prompt_preview(
|
|
1287
|
+
agent_id,
|
|
1288
|
+
request_instructions=payload.request_instructions,
|
|
1289
|
+
include_content=payload.include_content,
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
@app.post("/api/v1/agents/{agent_id}/context:preview")
|
|
1293
|
+
async def preview_context(agent_id: str, payload: ContextPreviewRequest):
|
|
1294
|
+
"""PR-S2:Context 预览(方案 §6.2)。复用真实 Planner,不调模型。"""
|
|
1295
|
+
return await studio.preview_context(
|
|
1296
|
+
agent_id,
|
|
1297
|
+
user_input=payload.user_input,
|
|
1298
|
+
request_instructions=payload.request_instructions,
|
|
1299
|
+
simulated_history=[
|
|
1300
|
+
{"role": m.role, "content": m.content} for m in payload.simulated_history
|
|
1301
|
+
],
|
|
1302
|
+
include_content=payload.include_content,
|
|
1303
|
+
)
|
|
1304
|
+
|
|
802
1305
|
@app.post("/api/v1/agents/{agent_id}/builds", status_code=202)
|
|
803
1306
|
async def create_build(
|
|
804
1307
|
agent_id: str,
|
|
@@ -882,13 +1385,161 @@ def create_studio_app(
|
|
|
882
1385
|
interaction_id,
|
|
883
1386
|
name=payload.name,
|
|
884
1387
|
data=payload.data,
|
|
1388
|
+
expected_revision=payload.expected_revision,
|
|
1389
|
+
idempotency_key=payload.idempotency_key,
|
|
885
1390
|
)
|
|
886
1391
|
|
|
1392
|
+
@app.get("/api/v1/runs/{run_id}/context")
|
|
1393
|
+
async def get_run_context(run_id: str):
|
|
1394
|
+
"""Runtime Context Evidence:planned/projected/actual + 精度 + ownership。"""
|
|
1395
|
+
record = studio.event_store.get(run_id)
|
|
1396
|
+
plan = record.context_plan or {}
|
|
1397
|
+
evidence = record.prompt_evidence or {}
|
|
1398
|
+
return {
|
|
1399
|
+
"planId": plan.get("plan_id"),
|
|
1400
|
+
"accuracy": evidence.get("accountingAccuracy")
|
|
1401
|
+
or plan.get("accounting_accuracy", "opaque"),
|
|
1402
|
+
"policyVersion": plan.get("policy_version"),
|
|
1403
|
+
"tokensByKind": plan.get("tokens_by_kind", {}),
|
|
1404
|
+
"plannedInputTokens": plan.get("planned_input_tokens"),
|
|
1405
|
+
"projectedInputTokens": plan.get("projected_input_tokens"),
|
|
1406
|
+
"runtimeReportedInputTokens": plan.get("runtime_reported_input_tokens"),
|
|
1407
|
+
"selected": plan.get("selected", []),
|
|
1408
|
+
"decisions": plan.get("decisions", []),
|
|
1409
|
+
"ownership": {
|
|
1410
|
+
"promptOwner": evidence.get("promptOwner"),
|
|
1411
|
+
"historyOwner": (plan.get("history_owner") if isinstance(plan, dict) else None),
|
|
1412
|
+
"integrationMode": evidence.get("integrationMode"),
|
|
1413
|
+
"runtimeType": evidence.get("runtimeType"),
|
|
1414
|
+
"deploymentMode": evidence.get("deploymentMode"),
|
|
1415
|
+
"capabilityHash": evidence.get("capabilityHash"),
|
|
1416
|
+
},
|
|
1417
|
+
"warnings": [],
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
@app.get("/api/v1/runs/{run_id}/prompt")
|
|
1421
|
+
async def get_run_prompt(run_id: str, include_content: bool = Query(default=False)):
|
|
1422
|
+
"""PR-S4:Prompt evidence(方案 §6.3 / §7.3)。section hash/版本,默认不返回正文。"""
|
|
1423
|
+
record = studio.event_store.get(run_id)
|
|
1424
|
+
evidence = record.prompt_evidence or {}
|
|
1425
|
+
result = {
|
|
1426
|
+
"contentHash": evidence.get("contentHash"),
|
|
1427
|
+
"stablePrefixHash": evidence.get("stablePrefixHash"),
|
|
1428
|
+
"sectionHashes": evidence.get("sectionHashes", {}),
|
|
1429
|
+
"tokensBySection": evidence.get("tokensBySection", {}),
|
|
1430
|
+
"estimatedTokens": evidence.get("estimatedTokens"),
|
|
1431
|
+
"sectionCount": evidence.get("sectionCount"),
|
|
1432
|
+
"plannedInputTokens": evidence.get("plannedInputTokens"),
|
|
1433
|
+
"accountingAccuracy": evidence.get("accountingAccuracy"),
|
|
1434
|
+
"runtimeType": evidence.get("runtimeType"),
|
|
1435
|
+
"integrationMode": evidence.get("integrationMode"),
|
|
1436
|
+
}
|
|
1437
|
+
if include_content:
|
|
1438
|
+
result["reveal"] = studio.reveal_run_prompt(run_id)
|
|
1439
|
+
return result
|
|
1440
|
+
|
|
1441
|
+
@app.get("/api/v1/runs/{run_id}/working-state")
|
|
1442
|
+
async def get_run_working_state(run_id: str):
|
|
1443
|
+
"""PR-S4:Working State evidence(方案 §6.5)。从 checkpoint/read record 读取。"""
|
|
1444
|
+
record = studio.event_store.get(run_id)
|
|
1445
|
+
return {"workingState": record.working_state}
|
|
1446
|
+
|
|
887
1447
|
@app.delete("/api/v1/sessions/{session_id}", status_code=204)
|
|
888
1448
|
async def delete_studio_session(session_id: str):
|
|
889
|
-
studio.delete_session(session_id)
|
|
1449
|
+
await studio.delete_session(session_id)
|
|
890
1450
|
return Response(status_code=204)
|
|
891
1451
|
|
|
1452
|
+
@app.get("/api/v1/sessions/{session_id}/events")
|
|
1453
|
+
async def session_events(
|
|
1454
|
+
session_id: str,
|
|
1455
|
+
before_seq_id: int | None = Query(default=None, ge=1, alias="beforeSeqId"),
|
|
1456
|
+
invocation_id: str | None = Query(default=None, alias="invocationId"),
|
|
1457
|
+
limit: int = Query(default=100, ge=1, le=500),
|
|
1458
|
+
):
|
|
1459
|
+
return await studio.trajectory_page(
|
|
1460
|
+
session_id,
|
|
1461
|
+
before_seq_id=before_seq_id,
|
|
1462
|
+
invocation_id=invocation_id,
|
|
1463
|
+
limit=limit,
|
|
1464
|
+
)
|
|
1465
|
+
|
|
1466
|
+
@app.get("/api/v1/sessions/{session_id}/events/stream")
|
|
1467
|
+
async def session_event_stream(
|
|
1468
|
+
session_id: str,
|
|
1469
|
+
request: Request,
|
|
1470
|
+
after_seq_id: int = Query(default=0, ge=0, alias="afterSeqId"),
|
|
1471
|
+
invocation_id: str | None = Query(default=None, alias="invocationId"),
|
|
1472
|
+
):
|
|
1473
|
+
await studio._require_runtime_session(session_id)
|
|
1474
|
+
last = request.headers.get("Last-Event-ID")
|
|
1475
|
+
cursor = int(last) if last and last.isdigit() else after_seq_id
|
|
1476
|
+
stream = studio.stream_trajectory(
|
|
1477
|
+
session_id,
|
|
1478
|
+
cursor,
|
|
1479
|
+
invocation_id=invocation_id,
|
|
1480
|
+
)
|
|
1481
|
+
|
|
1482
|
+
async def frames():
|
|
1483
|
+
try:
|
|
1484
|
+
async for frame in stream:
|
|
1485
|
+
if await request.is_disconnected():
|
|
1486
|
+
return
|
|
1487
|
+
yield frame
|
|
1488
|
+
finally:
|
|
1489
|
+
await stream.aclose()
|
|
1490
|
+
|
|
1491
|
+
return StreamingResponse(
|
|
1492
|
+
frames(),
|
|
1493
|
+
media_type="text/event-stream",
|
|
1494
|
+
headers={
|
|
1495
|
+
"Cache-Control": "no-store",
|
|
1496
|
+
"X-Accel-Buffering": "no",
|
|
1497
|
+
},
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
@app.post("/api/v1/sessions/{session_id}:export")
|
|
1501
|
+
async def export_session(session_id: str, payload: dict[str, Any]):
|
|
1502
|
+
filename = payload.get("filename")
|
|
1503
|
+
invocation_id = payload.get("invocationId")
|
|
1504
|
+
download = payload.get("download", False)
|
|
1505
|
+
if not isinstance(filename, str):
|
|
1506
|
+
raise StudioError(
|
|
1507
|
+
"SESSION_EXPORT_FILENAME_INVALID",
|
|
1508
|
+
"filename 必须是字符串",
|
|
1509
|
+
status_code=422,
|
|
1510
|
+
field="filename",
|
|
1511
|
+
)
|
|
1512
|
+
if invocation_id is not None and not isinstance(invocation_id, str):
|
|
1513
|
+
raise StudioError(
|
|
1514
|
+
"SESSION_EXPORT_INVOCATION_INVALID",
|
|
1515
|
+
"invocationId 必须是字符串",
|
|
1516
|
+
status_code=422,
|
|
1517
|
+
field="invocationId",
|
|
1518
|
+
)
|
|
1519
|
+
if not isinstance(download, bool):
|
|
1520
|
+
raise StudioError(
|
|
1521
|
+
"SESSION_EXPORT_DOWNLOAD_INVALID",
|
|
1522
|
+
"download 必须是布尔值",
|
|
1523
|
+
status_code=422,
|
|
1524
|
+
field="download",
|
|
1525
|
+
)
|
|
1526
|
+
result = await studio.export_runtime_session(
|
|
1527
|
+
session_id,
|
|
1528
|
+
filename=filename,
|
|
1529
|
+
invocation_id=invocation_id,
|
|
1530
|
+
)
|
|
1531
|
+
if not download:
|
|
1532
|
+
return result
|
|
1533
|
+
|
|
1534
|
+
path = studio.workspace.resolve(result["path"])
|
|
1535
|
+
return FileResponse(
|
|
1536
|
+
path,
|
|
1537
|
+
filename=filename,
|
|
1538
|
+
media_type="application/x-ndjson",
|
|
1539
|
+
headers={"X-Session-Event-Count": str(result["eventCount"])},
|
|
1540
|
+
background=BackgroundTask(path.unlink, missing_ok=True),
|
|
1541
|
+
)
|
|
1542
|
+
|
|
892
1543
|
@app.get("/api/v1/runs")
|
|
893
1544
|
async def list_runs(session_id: str | None = Query(default=None, alias="sessionId")):
|
|
894
1545
|
return {"items": studio.event_store.list_runs(session_id=session_id)}
|
|
@@ -901,7 +1552,7 @@ def create_studio_app(
|
|
|
901
1552
|
):
|
|
902
1553
|
last = request.headers.get("Last-Event-ID")
|
|
903
1554
|
cursor = int(last) if last and last.isdigit() else after
|
|
904
|
-
events = studio.
|
|
1555
|
+
events = await studio.run_service.events(run_id, after=cursor)
|
|
905
1556
|
return _sse(events)
|
|
906
1557
|
|
|
907
1558
|
@app.get("/api/v1/traces/overview")
|
|
@@ -942,19 +1593,6 @@ def create_studio_app(
|
|
|
942
1593
|
async def get_trace_otlp(trace_id: str):
|
|
943
1594
|
return studio.event_store.trace_otlp(trace_id)
|
|
944
1595
|
|
|
945
|
-
@app.post("/api/v1/builds/{build_id}/evaluations", status_code=202)
|
|
946
|
-
async def create_evaluation(
|
|
947
|
-
build_id: str,
|
|
948
|
-
payload: EvaluationRequest,
|
|
949
|
-
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
950
|
-
):
|
|
951
|
-
return studio.submit_evaluation(
|
|
952
|
-
build_id,
|
|
953
|
-
payload.suite_refs,
|
|
954
|
-
fail_fast=payload.fail_fast,
|
|
955
|
-
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
956
|
-
)
|
|
957
|
-
|
|
958
1596
|
@app.post("/api/v1/evaluations", status_code=202)
|
|
959
1597
|
async def create_public_evaluation(
|
|
960
1598
|
payload: StudioEvaluationCreate,
|
|
@@ -967,16 +1605,39 @@ def create_studio_app(
|
|
|
967
1605
|
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
968
1606
|
)
|
|
969
1607
|
|
|
1608
|
+
@app.post("/api/v1/evaluation-files", status_code=201)
|
|
1609
|
+
async def import_evaluation_file(file: UploadFile = File(...)):
|
|
1610
|
+
return studio.import_evaluation_file(
|
|
1611
|
+
await file.read(2 * 1024 * 1024 + 1),
|
|
1612
|
+
filename=file.filename or "evalset.yaml",
|
|
1613
|
+
)
|
|
1614
|
+
|
|
970
1615
|
@app.get("/api/v1/evaluations")
|
|
971
1616
|
async def list_public_evaluations():
|
|
972
1617
|
return {"items": studio.list_public_evaluations()}
|
|
973
1618
|
|
|
1619
|
+
@app.get("/api/v1/evaluation-runs")
|
|
1620
|
+
async def list_public_evaluation_runs():
|
|
1621
|
+
return {"items": studio.list_public_evaluation_runs()}
|
|
1622
|
+
|
|
1623
|
+
@app.get("/api/v1/evaluation-runs/{evaluation_id}")
|
|
1624
|
+
async def get_public_evaluation_run(evaluation_id: str):
|
|
1625
|
+
return studio.get_public_evaluation_run(evaluation_id)
|
|
1626
|
+
|
|
1627
|
+
@app.get("/api/v1/evaluation-targets")
|
|
1628
|
+
async def list_evaluation_targets():
|
|
1629
|
+
return studio.evaluation_catalog()
|
|
1630
|
+
|
|
1631
|
+
@app.get("/api/v1/evaluation-cloud/catalog")
|
|
1632
|
+
async def list_evaluation_cloud_catalog(
|
|
1633
|
+
project_id: str | None = Query(default=None, alias="projectId"),
|
|
1634
|
+
):
|
|
1635
|
+
items = await studio.evaluation_cloud_catalog(project_id=project_id)
|
|
1636
|
+
return {"items": items}
|
|
1637
|
+
|
|
974
1638
|
@app.get("/api/v1/evaluations/{evaluation_id}")
|
|
975
1639
|
async def get_evaluation(evaluation_id: str):
|
|
976
|
-
|
|
977
|
-
if report_path.is_file():
|
|
978
|
-
return studio.get_public_evaluation(evaluation_id)
|
|
979
|
-
return studio.evaluations.get(evaluation_id)
|
|
1640
|
+
return studio.get_public_evaluation(evaluation_id)
|
|
980
1641
|
|
|
981
1642
|
@app.get("/api/v1/evaluations/{evaluation_id}/cases/{case_id}")
|
|
982
1643
|
async def get_public_evaluation_case(evaluation_id: str, case_id: str):
|
|
@@ -1003,9 +1664,337 @@ def create_studio_app(
|
|
|
1003
1664
|
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
1004
1665
|
)
|
|
1005
1666
|
|
|
1667
|
+
@app.get("/api/v1/deployments")
|
|
1668
|
+
async def list_deployments():
|
|
1669
|
+
"""Read local deployment receipts without implicit cloud refreshes."""
|
|
1670
|
+
|
|
1671
|
+
return {"items": studio.cloud.list()}
|
|
1672
|
+
|
|
1673
|
+
@app.get("/api/v1/cloud-agents")
|
|
1674
|
+
async def list_account_cloud_agents(
|
|
1675
|
+
page: int = Query(default=1, ge=1),
|
|
1676
|
+
size: int = Query(default=100, ge=1, le=100),
|
|
1677
|
+
):
|
|
1678
|
+
"""List Agents visible to Studio's configured signed cloud account."""
|
|
1679
|
+
|
|
1680
|
+
return await studio.cloud.list_account_agents(page=page, size=size)
|
|
1681
|
+
|
|
1682
|
+
@app.get("/api/v1/cloud-agents/{agent_id}")
|
|
1683
|
+
async def get_account_cloud_agent(agent_id: str):
|
|
1684
|
+
return await studio.cloud.get_account_agent(agent_id)
|
|
1685
|
+
|
|
1686
|
+
@app.get("/api/v1/cloud-agents/{agent_id}/versions")
|
|
1687
|
+
async def list_account_cloud_agent_versions(
|
|
1688
|
+
agent_id: str,
|
|
1689
|
+
page: int = Query(default=1, ge=1),
|
|
1690
|
+
size: int = Query(default=100, ge=1, le=100),
|
|
1691
|
+
):
|
|
1692
|
+
"""List the Server-owned version history and rollback eligibility."""
|
|
1693
|
+
|
|
1694
|
+
return await studio.cloud.list_account_agent_versions(
|
|
1695
|
+
agent_id,
|
|
1696
|
+
page=page,
|
|
1697
|
+
size=size,
|
|
1698
|
+
)
|
|
1699
|
+
|
|
1700
|
+
@app.post(
|
|
1701
|
+
"/api/v1/cloud-agents/{agent_id}:rollback-version",
|
|
1702
|
+
status_code=202,
|
|
1703
|
+
)
|
|
1704
|
+
async def rollback_account_cloud_agent_version(
|
|
1705
|
+
agent_id: str,
|
|
1706
|
+
payload: CloudAgentVersionRollbackRequest,
|
|
1707
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
1708
|
+
):
|
|
1709
|
+
"""Submit Server RollbackVersion through Studio's process-only AK/SK."""
|
|
1710
|
+
|
|
1711
|
+
return studio.submit_account_agent_version_rollback(
|
|
1712
|
+
agent_id,
|
|
1713
|
+
version_id=payload.version_id,
|
|
1714
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
1715
|
+
)
|
|
1716
|
+
|
|
1717
|
+
@app.post("/api/v1/cloud-agents/{agent_id}:dashboard")
|
|
1718
|
+
async def open_account_cloud_agent_dashboard(agent_id: str):
|
|
1719
|
+
return await studio.cloud.account_agent_dashboard_access(agent_id)
|
|
1720
|
+
|
|
1721
|
+
@app.delete("/api/v1/cloud-agents/{agent_id}")
|
|
1722
|
+
async def delete_account_cloud_agent(agent_id: str):
|
|
1723
|
+
return await studio.cloud.delete_account_agent(agent_id)
|
|
1724
|
+
|
|
1006
1725
|
@app.get("/api/v1/deployments/{deployment_id}")
|
|
1007
1726
|
async def get_deployment(deployment_id: str):
|
|
1008
|
-
return studio.cloud.
|
|
1727
|
+
return await studio.cloud.refresh(deployment_id)
|
|
1728
|
+
|
|
1729
|
+
@app.post("/api/v1/deployments/{deployment_id}:dashboard")
|
|
1730
|
+
async def open_deployment_dashboard(deployment_id: str):
|
|
1731
|
+
return await studio.deployment_dashboard_access(deployment_id)
|
|
1732
|
+
|
|
1733
|
+
@app.delete("/api/v1/deployments/{deployment_id}")
|
|
1734
|
+
async def delete_deployment(deployment_id: str):
|
|
1735
|
+
"""Delete the receipt-bound cloud Agent and its superseded local receipts."""
|
|
1736
|
+
|
|
1737
|
+
return await studio.cloud.delete(deployment_id)
|
|
1738
|
+
|
|
1739
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions")
|
|
1740
|
+
async def list_cloud_chat_sessions(
|
|
1741
|
+
deployment_id: str,
|
|
1742
|
+
page: int = Query(default=1, ge=1),
|
|
1743
|
+
size: int = Query(default=50, ge=1, le=100),
|
|
1744
|
+
):
|
|
1745
|
+
"""List Server-owned sessions for this local deployment receipt only."""
|
|
1746
|
+
|
|
1747
|
+
return await studio.cloud.list_cloud_chat_sessions(deployment_id, page=page, size=size)
|
|
1748
|
+
|
|
1749
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/models")
|
|
1750
|
+
async def list_cloud_chat_models(deployment_id: str):
|
|
1751
|
+
"""List models through Studio's signed Server client."""
|
|
1752
|
+
|
|
1753
|
+
return await studio.cloud.list_cloud_chat_models(deployment_id)
|
|
1754
|
+
|
|
1755
|
+
@app.post(
|
|
1756
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions",
|
|
1757
|
+
status_code=201,
|
|
1758
|
+
)
|
|
1759
|
+
async def create_cloud_chat_session(deployment_id: str):
|
|
1760
|
+
"""Create a cloud session via loopback-held AK/SK; no secret reaches JS."""
|
|
1761
|
+
|
|
1762
|
+
return await studio.cloud.create_cloud_chat_session(deployment_id)
|
|
1763
|
+
|
|
1764
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages")
|
|
1765
|
+
async def list_cloud_chat_messages(
|
|
1766
|
+
deployment_id: str,
|
|
1767
|
+
session_id: str,
|
|
1768
|
+
after_seq_id: int | None = Query(default=None, alias="afterSeqId", ge=0),
|
|
1769
|
+
limit: int = Query(default=100, ge=1, le=200),
|
|
1770
|
+
):
|
|
1771
|
+
return await studio.cloud.list_cloud_chat_messages(
|
|
1772
|
+
deployment_id,
|
|
1773
|
+
session_id=session_id,
|
|
1774
|
+
after_seq_id=after_seq_id,
|
|
1775
|
+
limit=limit,
|
|
1776
|
+
)
|
|
1777
|
+
|
|
1778
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events")
|
|
1779
|
+
async def list_cloud_chat_events(
|
|
1780
|
+
deployment_id: str,
|
|
1781
|
+
session_id: str,
|
|
1782
|
+
after_seq_id: int | None = Query(default=None, alias="afterSeqId", ge=0),
|
|
1783
|
+
offset: int | None = Query(default=None, ge=0),
|
|
1784
|
+
limit: int = Query(default=200, ge=1, le=1000),
|
|
1785
|
+
):
|
|
1786
|
+
return await studio.cloud.list_cloud_chat_events(
|
|
1787
|
+
deployment_id,
|
|
1788
|
+
session_id=session_id,
|
|
1789
|
+
after_seq_id=after_seq_id,
|
|
1790
|
+
offset=offset,
|
|
1791
|
+
limit=limit,
|
|
1792
|
+
)
|
|
1793
|
+
|
|
1794
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events/stream")
|
|
1795
|
+
async def stream_cloud_chat_events(
|
|
1796
|
+
request: Request,
|
|
1797
|
+
deployment_id: str,
|
|
1798
|
+
session_id: str,
|
|
1799
|
+
after_seq_id: int = Query(default=0, alias="afterSeqId", ge=0),
|
|
1800
|
+
):
|
|
1801
|
+
"""Stream canonical cloud events to the loopback browser.
|
|
1802
|
+
|
|
1803
|
+
The public cloud control plane currently exposes cursor reads for this
|
|
1804
|
+
surface. Keep that cursor in the Studio backend and present one SSE
|
|
1805
|
+
response to the browser, so assistant deltas arrive before the durable
|
|
1806
|
+
terminal message projection without exposing cloud credentials to JS.
|
|
1807
|
+
"""
|
|
1808
|
+
|
|
1809
|
+
async def event_stream() -> AsyncIterator[str]:
|
|
1810
|
+
cursor = after_seq_id
|
|
1811
|
+
idle_polls = 0
|
|
1812
|
+
while not await request.is_disconnected():
|
|
1813
|
+
payload = await studio.cloud.list_cloud_chat_events(
|
|
1814
|
+
deployment_id,
|
|
1815
|
+
session_id=session_id,
|
|
1816
|
+
after_seq_id=cursor,
|
|
1817
|
+
limit=200,
|
|
1818
|
+
)
|
|
1819
|
+
events = payload.get("events") or []
|
|
1820
|
+
if not isinstance(events, list):
|
|
1821
|
+
events = []
|
|
1822
|
+
terminal = False
|
|
1823
|
+
emitted = False
|
|
1824
|
+
for event in events:
|
|
1825
|
+
if not isinstance(event, dict):
|
|
1826
|
+
continue
|
|
1827
|
+
event_payload = (
|
|
1828
|
+
event.get("payload") if isinstance(event.get("payload"), dict) else event
|
|
1829
|
+
)
|
|
1830
|
+
seq = int(
|
|
1831
|
+
event_payload.get("seq")
|
|
1832
|
+
or event_payload.get("seq_id")
|
|
1833
|
+
or event_payload.get("source_session_seq")
|
|
1834
|
+
or event.get("seq")
|
|
1835
|
+
or event.get("seq_id")
|
|
1836
|
+
or 0
|
|
1837
|
+
)
|
|
1838
|
+
if seq and seq <= cursor:
|
|
1839
|
+
continue
|
|
1840
|
+
if seq:
|
|
1841
|
+
cursor = max(cursor, seq)
|
|
1842
|
+
event_type = str(
|
|
1843
|
+
event.get("event_type")
|
|
1844
|
+
or event.get("eventType")
|
|
1845
|
+
or event_payload.get("event_type")
|
|
1846
|
+
or event_payload.get("eventType")
|
|
1847
|
+
or ""
|
|
1848
|
+
).lower()
|
|
1849
|
+
content = (
|
|
1850
|
+
event_payload.get("content")
|
|
1851
|
+
if isinstance(event_payload.get("content"), dict)
|
|
1852
|
+
else {}
|
|
1853
|
+
)
|
|
1854
|
+
status = str(event_payload.get("status") or content.get("status") or "").lower()
|
|
1855
|
+
event_is_terminal = event_type in {
|
|
1856
|
+
"run.completed",
|
|
1857
|
+
"run.complete",
|
|
1858
|
+
"run.succeeded",
|
|
1859
|
+
"run.failed",
|
|
1860
|
+
"run.cancelled",
|
|
1861
|
+
"run.expired",
|
|
1862
|
+
"run.error",
|
|
1863
|
+
} or (
|
|
1864
|
+
event_type in {"run_status", "run.status"}
|
|
1865
|
+
and status
|
|
1866
|
+
in {
|
|
1867
|
+
"completed",
|
|
1868
|
+
"complete",
|
|
1869
|
+
"succeeded",
|
|
1870
|
+
"success",
|
|
1871
|
+
"failed",
|
|
1872
|
+
"cancelled",
|
|
1873
|
+
"canceled",
|
|
1874
|
+
"expired",
|
|
1875
|
+
"error",
|
|
1876
|
+
"aborted",
|
|
1877
|
+
}
|
|
1878
|
+
)
|
|
1879
|
+
terminal = terminal or event_is_terminal
|
|
1880
|
+
emitted = True
|
|
1881
|
+
projected_event = dict(event)
|
|
1882
|
+
conversation_item = _cloud_event_conversation_item(
|
|
1883
|
+
event,
|
|
1884
|
+
session_id=session_id,
|
|
1885
|
+
)
|
|
1886
|
+
if conversation_item is not None:
|
|
1887
|
+
projected_event["conversationItem"] = conversation_item
|
|
1888
|
+
yield (
|
|
1889
|
+
(f"id: {seq}\n" if seq else "")
|
|
1890
|
+
+ "event: session.event\n"
|
|
1891
|
+
+ f"data: {json.dumps(projected_event, ensure_ascii=False)}\n\n"
|
|
1892
|
+
)
|
|
1893
|
+
if terminal or payload.get("session_deleted"):
|
|
1894
|
+
break
|
|
1895
|
+
idle_polls = 0 if emitted else idle_polls + 1
|
|
1896
|
+
if idle_polls and idle_polls % 20 == 0:
|
|
1897
|
+
yield ": keepalive\n\n"
|
|
1898
|
+
await asyncio.sleep(0.25)
|
|
1899
|
+
|
|
1900
|
+
return StreamingResponse(
|
|
1901
|
+
event_stream(),
|
|
1902
|
+
media_type="text/event-stream",
|
|
1903
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
|
1904
|
+
)
|
|
1905
|
+
|
|
1906
|
+
@app.delete(
|
|
1907
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}",
|
|
1908
|
+
status_code=204,
|
|
1909
|
+
)
|
|
1910
|
+
async def delete_cloud_chat_session(deployment_id: str, session_id: str):
|
|
1911
|
+
await studio.cloud.delete_cloud_chat_session(deployment_id, session_id=session_id)
|
|
1912
|
+
return Response(status_code=204)
|
|
1913
|
+
|
|
1914
|
+
@app.post(
|
|
1915
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages",
|
|
1916
|
+
status_code=202,
|
|
1917
|
+
)
|
|
1918
|
+
async def send_cloud_chat_message(
|
|
1919
|
+
deployment_id: str,
|
|
1920
|
+
session_id: str,
|
|
1921
|
+
payload: CloudChatMessageRequest,
|
|
1922
|
+
):
|
|
1923
|
+
"""Admit one cloud message through Server; response is a durable receipt."""
|
|
1924
|
+
|
|
1925
|
+
return await studio.cloud.send_cloud_chat_message(
|
|
1926
|
+
deployment_id,
|
|
1927
|
+
session_id=session_id,
|
|
1928
|
+
content=payload.content,
|
|
1929
|
+
model=payload.model,
|
|
1930
|
+
model_options=payload.model_options,
|
|
1931
|
+
tool_approval_mode=payload.tool_approval_mode,
|
|
1932
|
+
collaboration_mode=payload.collaboration_mode,
|
|
1933
|
+
goal_objective=payload.goal_objective,
|
|
1934
|
+
)
|
|
1935
|
+
|
|
1936
|
+
@app.post(
|
|
1937
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages/stream"
|
|
1938
|
+
)
|
|
1939
|
+
async def stream_cloud_chat_message(
|
|
1940
|
+
request: Request,
|
|
1941
|
+
deployment_id: str,
|
|
1942
|
+
session_id: str,
|
|
1943
|
+
payload: CloudChatMessageRequest,
|
|
1944
|
+
):
|
|
1945
|
+
"""Proxy one signed foreground RunAgent SSE response to loopback UI."""
|
|
1946
|
+
|
|
1947
|
+
upstream = await studio.cloud.stream_cloud_chat_message(
|
|
1948
|
+
deployment_id,
|
|
1949
|
+
session_id=session_id,
|
|
1950
|
+
content=payload.content,
|
|
1951
|
+
model=payload.model,
|
|
1952
|
+
model_options=payload.model_options,
|
|
1953
|
+
tool_approval_mode=payload.tool_approval_mode,
|
|
1954
|
+
collaboration_mode=payload.collaboration_mode,
|
|
1955
|
+
goal_objective=payload.goal_objective,
|
|
1956
|
+
)
|
|
1957
|
+
|
|
1958
|
+
async def proxy_stream() -> AsyncIterator[bytes]:
|
|
1959
|
+
try:
|
|
1960
|
+
while not await request.is_disconnected():
|
|
1961
|
+
try:
|
|
1962
|
+
chunk = await anext(upstream)
|
|
1963
|
+
except StopAsyncIteration:
|
|
1964
|
+
break
|
|
1965
|
+
if await request.is_disconnected():
|
|
1966
|
+
break
|
|
1967
|
+
yield chunk
|
|
1968
|
+
finally:
|
|
1969
|
+
close = getattr(upstream, "aclose", None)
|
|
1970
|
+
if close is not None:
|
|
1971
|
+
await close()
|
|
1972
|
+
|
|
1973
|
+
return StreamingResponse(
|
|
1974
|
+
proxy_stream(),
|
|
1975
|
+
media_type="text/event-stream",
|
|
1976
|
+
headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
|
|
1977
|
+
)
|
|
1978
|
+
|
|
1979
|
+
@app.post(
|
|
1980
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/interactions",
|
|
1981
|
+
status_code=202,
|
|
1982
|
+
)
|
|
1983
|
+
async def submit_cloud_chat_interaction(
|
|
1984
|
+
deployment_id: str,
|
|
1985
|
+
session_id: str,
|
|
1986
|
+
payload: CloudChatInteractionSubmitRequest,
|
|
1987
|
+
):
|
|
1988
|
+
return await studio.cloud.submit_cloud_chat_interaction(
|
|
1989
|
+
deployment_id,
|
|
1990
|
+
session_id=session_id,
|
|
1991
|
+
run_id=payload.run_id,
|
|
1992
|
+
interaction_id=payload.interaction_id,
|
|
1993
|
+
expected_revision=payload.expected_revision,
|
|
1994
|
+
action=payload.action,
|
|
1995
|
+
response=payload.response,
|
|
1996
|
+
idempotency_key=payload.idempotency_key,
|
|
1997
|
+
)
|
|
1009
1998
|
|
|
1010
1999
|
@app.post("/api/v1/deployments/{deployment_id}:rollback", status_code=202)
|
|
1011
2000
|
async def rollback_deployment(
|
|
@@ -1035,12 +2024,17 @@ def create_studio_app(
|
|
|
1035
2024
|
):
|
|
1036
2025
|
last = request.headers.get("Last-Event-ID")
|
|
1037
2026
|
cursor = int(last) if last and last.isdigit() else after
|
|
1038
|
-
|
|
2027
|
+
events = studio.operations.events(operation_id, after=cursor)
|
|
2028
|
+
if "application/json" in request.headers.get("Accept", ""):
|
|
2029
|
+
return {"items": events}
|
|
2030
|
+
return _sse(events)
|
|
1039
2031
|
|
|
1040
2032
|
register_catalog_routes(
|
|
1041
2033
|
app,
|
|
1042
2034
|
studio,
|
|
1043
2035
|
runtime_model_catalog=runtime_model_catalog,
|
|
1044
2036
|
)
|
|
2037
|
+
register_memory_routes(app, studio)
|
|
2038
|
+
register_plugin_routes(app, studio)
|
|
1045
2039
|
|
|
1046
2040
|
return app
|