agentengine-sdk-python 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
- agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
- ksadk/a2a/__init__.py +2 -0
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/bootstrap.py +0 -2
- ksadk/a2a/card.py +6 -111
- ksadk/a2a/control_plane.py +239 -1
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +195 -172
- ksadk/a2a/routes.py +12 -5
- ksadk/a2a/server.py +1 -8
- ksadk/a2a/service_env.py +33 -0
- ksadk/a2a/space_client.py +43 -349
- ksadk/a2a_card.py +101 -0
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +474 -275
- ksadk/agui/config.py +11 -5
- ksadk/agui/routes.py +7 -15
- ksadk/api/client.py +530 -40
- ksadk/api/environment.py +27 -0
- ksadk/builders/code_builder.py +352 -53
- ksadk/builders/container_builder.py +71 -44
- ksadk/builders/framework_requirements.py +2 -1
- ksadk/builders/ks3_uploader.py +2 -2
- ksadk/builders/managed_runtime_builder.py +60 -28
- ksadk/cli/__init__.py +25 -0
- ksadk/cli/cmd_a2a.py +18 -31
- ksadk/cli/cmd_create.py +13 -26
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +28 -3
- ksadk/cli/cmd_eval.py +443 -0
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +137 -163
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_launch.py +12 -0
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +85 -479
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_run.py +29 -67
- ksadk/cli/cmd_studio.py +201 -0
- ksadk/cli/cmd_web.py +15 -6
- ksadk/cli/dry_run.py +2 -1
- ksadk/cli/env_options.py +83 -0
- ksadk/cli/global_options.py +3 -1
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/network_options.py +7 -5
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/cli/runtime_bootstrap.py +51 -0
- ksadk/cli/ui.py +6 -0
- ksadk/cli/workflow_common.py +5 -5
- ksadk/codex/__init__.py +3 -3
- ksadk/codex/client.py +833 -49
- ksadk/codex/phase.py +1 -1
- ksadk/codex/runtime.py +720 -127
- ksadk/common/kop_client.py +232 -0
- ksadk/configs/__init__.py +1 -3
- ksadk/configs/env_registry.py +244 -45
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +6 -1
- ksadk/configs/settings.py +5 -108
- ksadk/context_engine/__init__.py +98 -0
- ksadk/context_engine/assembler.py +176 -0
- ksadk/context_engine/baseline.py +419 -0
- ksadk/context_engine/cache_observability.py +228 -0
- ksadk/context_engine/capabilities.py +443 -0
- ksadk/context_engine/contributors.py +344 -0
- ksadk/context_engine/hosted_pipeline.py +417 -0
- ksadk/context_engine/models.py +117 -0
- ksadk/context_engine/planner.py +698 -0
- ksadk/context_engine/policies.py +319 -0
- ksadk/context_engine/projection.py +31 -0
- ksadk/context_engine/shadow_plan.py +266 -0
- ksadk/context_engine/tokenizer.py +164 -0
- ksadk/conversations/__init__.py +85 -94
- ksadk/conversations/context.py +126 -6
- ksadk/conversations/message_projection.py +292 -28
- ksadk/conversations/model_context.py +49 -3
- ksadk/conversations/runtime_compaction.py +207 -5
- ksadk/conversations/runtime_input.py +193 -3
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +157 -4
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +280 -2
- ksadk/conversations/runtime_resume.py +71 -32
- ksadk/conversations/runtime_stream_events.py +94 -9
- ksadk/conversations/runtime_streaming.py +175 -22
- ksadk/conversations/semantic_summary.py +311 -1
- ksadk/conversations/session_lock.py +51 -0
- ksadk/deployment/env_forward.py +72 -0
- ksadk/deployment/managed_runtime.py +38 -0
- ksadk/deployment/manager.py +24 -8
- ksadk/deployment/providers/serverless.py +228 -92
- ksadk/evaluation/__init__.py +116 -0
- ksadk/evaluation/a2a_adapter.py +452 -0
- ksadk/evaluation/adapters.py +67 -0
- ksadk/evaluation/agent_eval_client.py +395 -0
- ksadk/evaluation/cloud_binding.py +80 -0
- ksadk/evaluation/cloud_converter.py +194 -0
- ksadk/evaluation/cloud_service.py +199 -0
- ksadk/evaluation/contracts.py +478 -0
- ksadk/evaluation/evalset.py +351 -0
- ksadk/evaluation/evaluators.py +627 -0
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +143 -0
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/storage.py +68 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +82 -0
- ksadk/events/__init__.py +13 -13
- ksadk/events/_v1_compat/__init__.py +1 -0
- ksadk/events/_v1_compat/models.py +299 -0
- ksadk/events/_v1_compat/parser.py +247 -0
- ksadk/events/_v1_compat/projection.py +753 -0
- ksadk/events/adapters/__init__.py +34 -0
- ksadk/events/adapters/_a2a_snapshot.py +622 -0
- ksadk/events/adapters/_a2a_support.py +275 -0
- ksadk/events/adapters/_codex_interactions.py +375 -0
- ksadk/events/adapters/_codex_items.py +801 -0
- ksadk/events/adapters/_codex_validators.py +311 -0
- ksadk/events/adapters/_langgraph_support.py +785 -0
- ksadk/events/adapters/a2a.py +841 -0
- ksadk/events/adapters/adk.py +503 -0
- ksadk/events/adapters/codex.py +717 -0
- ksadk/events/adapters/langgraph.py +745 -0
- ksadk/events/canonical.py +440 -0
- ksadk/events/canonical_replay.py +497 -0
- ksadk/events/canonical_store.py +468 -0
- ksadk/events/cold_recovery.py +257 -0
- ksadk/events/content.py +89 -0
- ksadk/events/identity.py +86 -0
- ksadk/events/pipeline.py +455 -0
- ksadk/events/projections.py +66 -0
- ksadk/events/reducer.py +661 -0
- ksadk/events/replay.py +3 -36
- ksadk/events/runtime_event.py +20 -256
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/__init__.py +3 -2
- ksadk/harness/app.py +114 -123
- ksadk/harness/config.py +1 -1
- ksadk/harness/runtime.py +467 -0
- ksadk/hermes_terminal.py +5 -0
- ksadk/identity/resolver.py +4 -3
- ksadk/interaction/__init__.py +28 -0
- ksadk/interaction/contracts.py +136 -0
- ksadk/interaction/ledger.py +189 -0
- ksadk/interaction/provider.py +122 -0
- ksadk/interaction/providers/__init__.py +52 -0
- ksadk/interaction/providers/adk.py +40 -0
- ksadk/interaction/providers/codex.py +94 -0
- ksadk/interaction/providers/langgraph.py +58 -0
- ksadk/kernel/__init__.py +117 -0
- ksadk/kernel/authorization.py +258 -0
- ksadk/kernel/bootstrap.py +1074 -0
- ksadk/kernel/contract_fingerprints.py +61 -0
- ksadk/kernel/contracts.py +362 -0
- ksadk/kernel/control.py +219 -0
- ksadk/kernel/errors.py +79 -0
- ksadk/kernel/ingress.py +1102 -0
- ksadk/kernel/mapping.py +111 -0
- ksadk/kernel/memory_store.py +1133 -0
- ksadk/kernel/postgres_store.py +1756 -0
- ksadk/kernel/recovery.py +452 -0
- ksadk/kernel/runtime_identity.py +103 -0
- ksadk/kernel/sql/001_agent_kernel.sql +120 -0
- ksadk/kernel/sqlite_store.py +1410 -0
- ksadk/kernel/state.py +145 -0
- ksadk/kernel/store.py +232 -0
- ksadk/kernel/worker.py +953 -0
- ksadk/knowledge_base/client.py +22 -4
- ksadk/knowledge_base/service.py +32 -5
- ksadk/managed_a2a_card.py +160 -0
- ksadk/managed_runtime.py +16 -9
- ksadk/memory/__init__.py +61 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
- ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
- ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- ksadk/memory/adk/long_term_memory.py +3 -2
- ksadk/memory/coordinator.py +461 -0
- ksadk/memory/events.py +202 -0
- ksadk/memory/extraction.py +228 -0
- ksadk/memory/ltm_backend_factory.py +16 -3
- ksadk/memory/models.py +316 -0
- ksadk/memory/policy.py +215 -0
- ksadk/memory/provider.py +47 -0
- ksadk/memory/provider_adapter.py +159 -0
- ksadk/memory/provider_resolver.py +71 -0
- ksadk/memory/providers/__init__.py +5 -0
- ksadk/memory/providers/local_sqlite.py +490 -0
- ksadk/memory/resolved_policy.py +145 -0
- ksadk/memory/service.py +198 -8
- ksadk/memory/tool.py +4 -0
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/config.py +22 -1
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +67 -15
- ksadk/model_proxy/server.py +168 -6
- ksadk/model_proxy/transform.py +105 -3
- ksadk/observability/__init__.py +25 -0
- ksadk/observability/session_log.py +420 -0
- ksadk/observability/trajectory.py +105 -0
- ksadk/prompts/__init__.py +71 -0
- ksadk/prompts/compiler.py +215 -0
- ksadk/prompts/models.py +82 -0
- ksadk/prompts/projection.py +89 -0
- ksadk/prompts/resolved.py +137 -0
- ksadk/prompts/sources.py +233 -0
- ksadk/runners/_langgraph_runner_streams.py +822 -0
- ksadk/runners/adk_runner.py +14 -31
- ksadk/runners/base_runner.py +44 -3
- ksadk/runners/factory.py +3 -3
- ksadk/runners/langgraph_runner.py +252 -433
- ksadk/runners/utils/__init__.py +0 -10
- ksadk/runners/utils/loader.py +16 -0
- ksadk/runtime/__init__.py +67 -41
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +199 -16
- ksadk/runtime/conversation_execution.py +687 -0
- ksadk/runtime/executor.py +392 -0
- ksadk/runtime/factory.py +342 -0
- ksadk/runtime/framework_adapters.py +6 -12
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +61 -0
- ksadk/runtime/preprocessing.py +49 -14
- ksadk/runtime/runner_adapter.py +330 -391
- ksadk/runtime/runner_loading.py +36 -0
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +36 -3
- ksadk/server/__init__.py +39 -4
- ksadk/server/app.py +18 -492
- ksadk/server/composition.py +36 -2
- ksadk/server/factory.py +210 -149
- ksadk/server/routes/common.py +15 -46
- ksadk/server/routes/control.py +13 -9
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/misc.py +10 -33
- ksadk/server/routes/models.py +14 -6
- ksadk/server/routes/openai_compat.py +129 -27
- ksadk/server/routes/projection.py +84 -16
- ksadk/server/routes/run.py +347 -460
- ksadk/server/routes/sessions.py +37 -40
- ksadk/server/routes/streaming.py +52 -7
- ksadk/server/routes/workspace.py +54 -21
- ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
- ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
- ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
- ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
- ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
- ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
- ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
- ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
- ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
- ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
- ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
- ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
- ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
- ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
- ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
- ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
- ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
- ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
- ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
- ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
- ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
- ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
- ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
- ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
- ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
- ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
- ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
- ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
- ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
- ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
- ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
- ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
- ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
- ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
- ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
- ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
- ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
- ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
- ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
- ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
- ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
- ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
- ksadk/server/static/index.html +2 -2
- ksadk/sessions/__init__.py +26 -9
- ksadk/sessions/_local_service_sync.py +688 -0
- ksadk/sessions/_local_tables.py +21 -0
- ksadk/sessions/_postgres_schema.py +305 -0
- ksadk/sessions/_postgres_tables.py +15 -0
- ksadk/sessions/base.py +80 -1
- ksadk/sessions/in_memory.py +53 -2
- ksadk/sessions/local_service.py +63 -604
- ksadk/sessions/postgres_service.py +58 -148
- ksadk/sessions/resilient.py +35 -1
- ksadk/skills/runtime/backends/e2b.py +1 -1
- ksadk/skills/service_client.py +13 -3
- ksadk/studio/__init__.py +5 -0
- ksadk/studio/agent_avatar_assets.py +120 -0
- ksadk/studio/agent_lifecycle.py +51 -0
- ksadk/studio/api.py +1598 -0
- ksadk/studio/api_catalog_routes.py +324 -0
- ksadk/studio/api_contracts.py +286 -0
- ksadk/studio/api_helpers.py +132 -0
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +749 -0
- ksadk/studio/authoring_coordinator.py +1017 -0
- ksadk/studio/builder.py +397 -0
- ksadk/studio/capabilities.py +368 -0
- ksadk/studio/cloud.py +2099 -0
- ksadk/studio/codex_agent_service.py +694 -0
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +437 -0
- ksadk/studio/codex_manifest.py +307 -0
- ksadk/studio/codex_run.py +395 -0
- ksadk/studio/compiler.py +190 -0
- ksadk/studio/contracts.py +755 -0
- ksadk/studio/errors.py +47 -0
- ksadk/studio/event_store.py +214 -0
- ksadk/studio/framework_run.py +342 -0
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/identifiers.py +33 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/mcp_runtime.py +229 -0
- ksadk/studio/model_client.py +629 -0
- ksadk/studio/model_profile_service.py +214 -0
- ksadk/studio/operations.py +275 -0
- ksadk/studio/otel_trace.py +1060 -0
- ksadk/studio/pagination.py +182 -0
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/python_tool_inspection.py +183 -0
- ksadk/studio/repository.py +327 -0
- ksadk/studio/resource_catalog.py +1260 -0
- ksadk/studio/run_service.py +1133 -0
- ksadk/studio/runtime_catalog.py +74 -0
- ksadk/studio/runtime_source.py +365 -0
- ksadk/studio/service.py +2249 -0
- ksadk/studio/shared_web.py +1004 -0
- ksadk/studio/skill_discovery.py +628 -0
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
- ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- ksadk/studio/static/index.html +15 -0
- ksadk/studio/templates.py +722 -0
- ksadk/studio/validator.py +265 -0
- ksadk/studio/workspace.py +135 -0
- ksadk/tools/gateway.py +9 -0
- ksadk/toolsets/__init__.py +27 -1
- ksadk/toolsets/a2a.py +426 -0
- ksadk/tracing/__init__.py +19 -21
- ksadk/tracing/setup.py +317 -403
- ksadk/tracing/span_utils.py +0 -44
- ksadk/version.py +1 -1
- ksadk_runtime_common/memory_backend/registry.py +56 -56
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk_runtime_common/workspace_files/router.py +8 -2
- agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
- ksadk/a2a/card_builder.py +0 -64
- ksadk/a2a/client.py +0 -309
- ksadk/events/parser.py +0 -191
- ksadk/harness/runner.py +0 -211
- ksadk/runners/codex_runner.py +0 -132
- ksadk/runners/utils/langfuse.py +0 -230
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
- ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
- ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
- ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
- ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
- ksadk/server/static/assets/katex-DATssjp1.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
- ksadk/tracing/exporters/langfuse_exporter.py +0 -460
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
ksadk/studio/api.py
ADDED
|
@@ -0,0 +1,1598 @@
|
|
|
1
|
+
"""FastAPI application for the loopback-only AgentKit Studio control plane."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import hmac
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import secrets
|
|
10
|
+
from collections.abc import AsyncIterator
|
|
11
|
+
from contextlib import asynccontextmanager
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Literal
|
|
14
|
+
|
|
15
|
+
from fastapi import FastAPI, File, Header, Query, Request, Response, UploadFile
|
|
16
|
+
from fastapi.exceptions import RequestValidationError
|
|
17
|
+
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
|
18
|
+
from fastapi.staticfiles import StaticFiles
|
|
19
|
+
from starlette.background import BackgroundTask
|
|
20
|
+
|
|
21
|
+
from ksadk.studio.api_catalog_routes import register_catalog_routes
|
|
22
|
+
from ksadk.studio.api_contracts import (
|
|
23
|
+
AuthoringCommitRequest,
|
|
24
|
+
BuildRequest,
|
|
25
|
+
CloudAgentVersionRollbackRequest,
|
|
26
|
+
CloudChatInteractionSubmitRequest,
|
|
27
|
+
CloudChatMessageRequest,
|
|
28
|
+
ContextPreviewRequest,
|
|
29
|
+
ConversationAuthoringRequest,
|
|
30
|
+
CreateAgentRequest,
|
|
31
|
+
ImportRootRequest,
|
|
32
|
+
InteractionSubmitRequest,
|
|
33
|
+
ProjectInspectRequest,
|
|
34
|
+
PromptCompileRequest,
|
|
35
|
+
QuickAuthoringRequest,
|
|
36
|
+
RollbackRequest,
|
|
37
|
+
RunRequest,
|
|
38
|
+
SessionExchangeRequest,
|
|
39
|
+
StudioEvaluationCreate,
|
|
40
|
+
ValidationRequest,
|
|
41
|
+
WorkspaceOpenRequest,
|
|
42
|
+
)
|
|
43
|
+
from ksadk.studio.api_helpers import (
|
|
44
|
+
error_response as _error_response,
|
|
45
|
+
)
|
|
46
|
+
from ksadk.studio.api_helpers import (
|
|
47
|
+
is_local_origin,
|
|
48
|
+
)
|
|
49
|
+
from ksadk.studio.api_helpers import (
|
|
50
|
+
optional_int as _optional_int,
|
|
51
|
+
)
|
|
52
|
+
from ksadk.studio.api_helpers import (
|
|
53
|
+
parse_revision as _parse_revision,
|
|
54
|
+
)
|
|
55
|
+
from ksadk.studio.api_helpers import (
|
|
56
|
+
require_idempotency_key as _require_idempotency_key,
|
|
57
|
+
)
|
|
58
|
+
from ksadk.studio.api_helpers import (
|
|
59
|
+
responses_input as _responses_input,
|
|
60
|
+
)
|
|
61
|
+
from ksadk.studio.api_helpers import (
|
|
62
|
+
responses_session_id as _responses_session_id,
|
|
63
|
+
)
|
|
64
|
+
from ksadk.studio.api_helpers import (
|
|
65
|
+
sse as _sse,
|
|
66
|
+
)
|
|
67
|
+
from ksadk.studio.api_memory_routes import register_memory_routes
|
|
68
|
+
from ksadk.studio.codex_manifest import CodexAgentManifest
|
|
69
|
+
from ksadk.studio.contracts import (
|
|
70
|
+
AgentAppearance,
|
|
71
|
+
AgentBindings,
|
|
72
|
+
AgentSpec,
|
|
73
|
+
AgentTemplateComposeRequest,
|
|
74
|
+
DeploymentRequest,
|
|
75
|
+
OperationStatus,
|
|
76
|
+
)
|
|
77
|
+
from ksadk.studio.errors import StudioError
|
|
78
|
+
from ksadk.studio.service import StudioService
|
|
79
|
+
from ksadk.studio.shared_web import StudioSharedWebBridge
|
|
80
|
+
|
|
81
|
+
_WRITE_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
|
|
82
|
+
_PUBLIC_API_PATHS = {
|
|
83
|
+
"/api/v1/system/health",
|
|
84
|
+
"/api/v1/system/session",
|
|
85
|
+
}
|
|
86
|
+
_LOCAL_HOSTS = {"127.0.0.1", "::1", "localhost", "testserver"}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_studio_app(
|
|
90
|
+
root: Path | str,
|
|
91
|
+
*,
|
|
92
|
+
service: StudioService | None = None,
|
|
93
|
+
session_token: str | None = None,
|
|
94
|
+
csrf_token: str | None = None,
|
|
95
|
+
security_enabled: bool = True,
|
|
96
|
+
) -> FastAPI:
|
|
97
|
+
studio = service or StudioService(root)
|
|
98
|
+
session_secret = session_token or secrets.token_urlsafe(32)
|
|
99
|
+
csrf_secret = csrf_token or secrets.token_urlsafe(24)
|
|
100
|
+
|
|
101
|
+
@asynccontextmanager
|
|
102
|
+
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
|
103
|
+
try:
|
|
104
|
+
await studio.run_service.recover_interrupted()
|
|
105
|
+
yield
|
|
106
|
+
finally:
|
|
107
|
+
studio.credentials.clear_session()
|
|
108
|
+
|
|
109
|
+
app = FastAPI(
|
|
110
|
+
title="AgentKit Local Studio",
|
|
111
|
+
version="1.0.0",
|
|
112
|
+
docs_url=None,
|
|
113
|
+
redoc_url=None,
|
|
114
|
+
openapi_url="/api/v1/openapi.json",
|
|
115
|
+
lifespan=lifespan,
|
|
116
|
+
)
|
|
117
|
+
app.state.studio_service = studio
|
|
118
|
+
app.state.session_token = session_secret
|
|
119
|
+
app.state.csrf_token = csrf_secret
|
|
120
|
+
static_root = Path(__file__).with_name("static")
|
|
121
|
+
shared_web = StudioSharedWebBridge(studio)
|
|
122
|
+
app.state.shared_web_bridge = shared_web
|
|
123
|
+
app.mount("/static", StaticFiles(directory=static_root), name="studio-static")
|
|
124
|
+
|
|
125
|
+
@app.middleware("http")
|
|
126
|
+
async def local_security(request: Request, call_next):
|
|
127
|
+
request.state.request_id = request.headers.get("X-Request-Id") or (
|
|
128
|
+
f"req_{secrets.token_hex(12)}"
|
|
129
|
+
)
|
|
130
|
+
host = (request.url.hostname or "").lower()
|
|
131
|
+
if host not in _LOCAL_HOSTS:
|
|
132
|
+
return _error_response(
|
|
133
|
+
StudioError(
|
|
134
|
+
"LOCAL_HOST_FORBIDDEN",
|
|
135
|
+
"Studio 只接受 loopback Host",
|
|
136
|
+
status_code=403,
|
|
137
|
+
),
|
|
138
|
+
request,
|
|
139
|
+
)
|
|
140
|
+
origin = request.headers.get("Origin")
|
|
141
|
+
if origin and not is_local_origin(origin, local_hosts=_LOCAL_HOSTS):
|
|
142
|
+
return _error_response(
|
|
143
|
+
StudioError(
|
|
144
|
+
"LOCAL_ORIGIN_FORBIDDEN",
|
|
145
|
+
"请求 Origin 不是本地 Studio",
|
|
146
|
+
status_code=403,
|
|
147
|
+
),
|
|
148
|
+
request,
|
|
149
|
+
)
|
|
150
|
+
content_length = request.headers.get("Content-Length")
|
|
151
|
+
large_upload_paths = {
|
|
152
|
+
"/api/v1/catalog/skills:import": 52 * 1024 * 1024,
|
|
153
|
+
"/api/v1/authoring/imports:inspect": 102 * 1024 * 1024,
|
|
154
|
+
"/api/v1/evaluation-files": 2 * 1024 * 1024 + 64 * 1024,
|
|
155
|
+
}
|
|
156
|
+
request_limit = large_upload_paths.get(request.url.path, 2 * 1024 * 1024)
|
|
157
|
+
if content_length and int(content_length) > request_limit:
|
|
158
|
+
return _error_response(
|
|
159
|
+
StudioError(
|
|
160
|
+
"REQUEST_TOO_LARGE",
|
|
161
|
+
"请求体超过 2 MiB 限制",
|
|
162
|
+
status_code=413,
|
|
163
|
+
),
|
|
164
|
+
request,
|
|
165
|
+
)
|
|
166
|
+
studio_api = request.url.path.startswith("/api/v1")
|
|
167
|
+
shared_web_api = request.url.path.startswith("/agentengine/api/v1")
|
|
168
|
+
responses_api = request.url.path == "/v1/responses" or request.url.path.startswith(
|
|
169
|
+
"/v1/responses/"
|
|
170
|
+
)
|
|
171
|
+
if security_enabled and (
|
|
172
|
+
(studio_api and request.url.path not in _PUBLIC_API_PATHS)
|
|
173
|
+
or shared_web_api
|
|
174
|
+
or responses_api
|
|
175
|
+
):
|
|
176
|
+
supplied = request.cookies.get("agentkit_studio_session") or request.headers.get(
|
|
177
|
+
"X-AgentKit-Session"
|
|
178
|
+
)
|
|
179
|
+
if not supplied or not hmac.compare_digest(supplied, session_secret):
|
|
180
|
+
return _error_response(
|
|
181
|
+
StudioError(
|
|
182
|
+
"LOCAL_SESSION_REQUIRED",
|
|
183
|
+
"缺少有效的 Studio 本地会话",
|
|
184
|
+
status_code=401,
|
|
185
|
+
),
|
|
186
|
+
request,
|
|
187
|
+
)
|
|
188
|
+
if (studio_api or responses_api) and request.method in _WRITE_METHODS:
|
|
189
|
+
csrf = request.headers.get("X-CSRF-Token")
|
|
190
|
+
if not csrf or not hmac.compare_digest(csrf, csrf_secret):
|
|
191
|
+
return _error_response(
|
|
192
|
+
StudioError(
|
|
193
|
+
"CSRF_TOKEN_INVALID",
|
|
194
|
+
"缺少有效的 CSRF token",
|
|
195
|
+
status_code=403,
|
|
196
|
+
),
|
|
197
|
+
request,
|
|
198
|
+
)
|
|
199
|
+
response = await call_next(request)
|
|
200
|
+
response.headers["X-Request-Id"] = request.state.request_id
|
|
201
|
+
response.headers["Cache-Control"] = (
|
|
202
|
+
"no-store"
|
|
203
|
+
if request.url.path.startswith(("/api/", "/v1/"))
|
|
204
|
+
else response.headers.get("Cache-Control", "no-cache")
|
|
205
|
+
)
|
|
206
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
207
|
+
response.headers["X-Frame-Options"] = "DENY"
|
|
208
|
+
response.headers["Referrer-Policy"] = "no-referrer"
|
|
209
|
+
return response
|
|
210
|
+
|
|
211
|
+
@app.exception_handler(StudioError)
|
|
212
|
+
async def studio_error_handler(request: Request, exc: StudioError):
|
|
213
|
+
return _error_response(exc, request)
|
|
214
|
+
|
|
215
|
+
@app.exception_handler(RequestValidationError)
|
|
216
|
+
async def validation_error_handler(request: Request, exc: RequestValidationError):
|
|
217
|
+
first = exc.errors()[0] if exc.errors() else {}
|
|
218
|
+
field = ".".join(str(value) for value in first.get("loc", [])[1:])
|
|
219
|
+
return _error_response(
|
|
220
|
+
StudioError(
|
|
221
|
+
"REQUEST_VALIDATION_FAILED",
|
|
222
|
+
str(first.get("msg") or "请求参数无效"),
|
|
223
|
+
status_code=422,
|
|
224
|
+
field=field or None,
|
|
225
|
+
),
|
|
226
|
+
request,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
@app.get("/")
|
|
230
|
+
async def index():
|
|
231
|
+
path = static_root / "index.html"
|
|
232
|
+
response = FileResponse(path, media_type="text/html")
|
|
233
|
+
if security_enabled:
|
|
234
|
+
response.set_cookie(
|
|
235
|
+
"agentkit_studio_session",
|
|
236
|
+
session_secret,
|
|
237
|
+
httponly=True,
|
|
238
|
+
samesite="strict",
|
|
239
|
+
secure=False,
|
|
240
|
+
path="/",
|
|
241
|
+
)
|
|
242
|
+
return response
|
|
243
|
+
|
|
244
|
+
@app.get("/favicon.ico")
|
|
245
|
+
async def favicon():
|
|
246
|
+
return Response(status_code=204)
|
|
247
|
+
|
|
248
|
+
async def runtime_model_catalog():
|
|
249
|
+
api_base = os.getenv("OPENAI_BASE_URL") or os.getenv("OPENAI_API_BASE")
|
|
250
|
+
current_model = os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
|
|
251
|
+
try:
|
|
252
|
+
api_key = studio.credentials.resolve("env://OPENAI_API_KEY")
|
|
253
|
+
except StudioError:
|
|
254
|
+
api_key = None
|
|
255
|
+
return await studio.catalog.discover_provider_models(
|
|
256
|
+
api_base=api_base,
|
|
257
|
+
api_key=api_key,
|
|
258
|
+
current_model=current_model,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
@app.get("/v1/models")
|
|
262
|
+
async def openai_models():
|
|
263
|
+
models, source = await runtime_model_catalog()
|
|
264
|
+
return {
|
|
265
|
+
"object": "list",
|
|
266
|
+
"data": [dict(item.contract.get("metadata") or {"id": item.name}) for item in models],
|
|
267
|
+
"current": os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME"),
|
|
268
|
+
"source": source,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@app.post("/v1/responses")
|
|
272
|
+
async def openai_responses(payload: dict[str, Any]):
|
|
273
|
+
metadata = payload.get("metadata")
|
|
274
|
+
metadata = metadata if isinstance(metadata, dict) else {}
|
|
275
|
+
agent_id = str(metadata.get("agent_id") or metadata.get("agentId") or "") or None
|
|
276
|
+
requested_approval_mode = (
|
|
277
|
+
str(metadata.get("approval_mode") or metadata.get("approvalMode") or "").strip().lower()
|
|
278
|
+
)
|
|
279
|
+
if requested_approval_mode and requested_approval_mode not in {"ask", "risk", "full"}:
|
|
280
|
+
raise StudioError(
|
|
281
|
+
"APPROVAL_MODE_INVALID",
|
|
282
|
+
"批准模式必须是 ask、risk 或 full",
|
|
283
|
+
status_code=422,
|
|
284
|
+
field="metadata.approval_mode",
|
|
285
|
+
)
|
|
286
|
+
collaboration_mode = (
|
|
287
|
+
str(metadata.get("collaboration_mode") or metadata.get("collaborationMode") or "")
|
|
288
|
+
.strip()
|
|
289
|
+
.lower()
|
|
290
|
+
)
|
|
291
|
+
if collaboration_mode and collaboration_mode not in {"default", "plan"}:
|
|
292
|
+
raise StudioError(
|
|
293
|
+
"COLLABORATION_MODE_INVALID",
|
|
294
|
+
"协作模式必须是 default 或 plan",
|
|
295
|
+
status_code=422,
|
|
296
|
+
field="metadata.collaboration_mode",
|
|
297
|
+
)
|
|
298
|
+
goal_objective = str(
|
|
299
|
+
metadata.get("goal_objective") or metadata.get("goalObjective") or ""
|
|
300
|
+
).strip()
|
|
301
|
+
reasoning = payload.get("reasoning")
|
|
302
|
+
reasoning = reasoning if isinstance(reasoning, dict) else {}
|
|
303
|
+
reasoning_effort = str(reasoning.get("effort") or "").strip().lower()
|
|
304
|
+
if reasoning_effort and reasoning_effort not in {"low", "medium", "high"}:
|
|
305
|
+
raise StudioError(
|
|
306
|
+
"REASONING_EFFORT_INVALID",
|
|
307
|
+
"推理强度必须是 low、medium 或 high",
|
|
308
|
+
status_code=422,
|
|
309
|
+
field="reasoning.effort",
|
|
310
|
+
)
|
|
311
|
+
session_id = _responses_session_id(payload, bridge=shared_web)
|
|
312
|
+
response_id = str(
|
|
313
|
+
metadata.get("invocation_id")
|
|
314
|
+
or metadata.get("invocationId")
|
|
315
|
+
or f"resp_{secrets.token_hex(12)}"
|
|
316
|
+
)
|
|
317
|
+
bridge_payload: dict[str, Any] = {
|
|
318
|
+
"AgentId": shared_web.resolve_agent_id(agent_id),
|
|
319
|
+
"SessionId": session_id or f"ses_{secrets.token_hex(12)}",
|
|
320
|
+
"InvocationId": response_id,
|
|
321
|
+
"Model": str(payload.get("model") or ""),
|
|
322
|
+
"ResponsesInput": _responses_input(payload.get("input")),
|
|
323
|
+
"ApprovalMode": requested_approval_mode,
|
|
324
|
+
"CollaborationMode": collaboration_mode,
|
|
325
|
+
"GoalObjective": goal_objective,
|
|
326
|
+
"ReasoningEffort": reasoning_effort,
|
|
327
|
+
}
|
|
328
|
+
bridge_payload["Model"] = shared_web.select_model(
|
|
329
|
+
bridge_payload["AgentId"],
|
|
330
|
+
bridge_payload["Model"],
|
|
331
|
+
)
|
|
332
|
+
if bool(payload.get("stream")):
|
|
333
|
+
return StreamingResponse(
|
|
334
|
+
shared_web.stream_run(bridge_payload),
|
|
335
|
+
media_type="text/event-stream",
|
|
336
|
+
headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
|
|
337
|
+
)
|
|
338
|
+
return await shared_web.invoke_response(bridge_payload)
|
|
339
|
+
|
|
340
|
+
@app.post("/v1/responses/{response_id}/cancel")
|
|
341
|
+
async def cancel_openai_response(response_id: str):
|
|
342
|
+
result = shared_web.cancel_run(response_id)
|
|
343
|
+
return {
|
|
344
|
+
"id": response_id,
|
|
345
|
+
"object": "response",
|
|
346
|
+
"status": "cancelled" if result["Cancelled"] else "not_found",
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
@app.post("/v1/responses/{response_id}:pause", status_code=202)
|
|
350
|
+
async def pause_openai_response(response_id: str):
|
|
351
|
+
return await shared_web.pause_run(response_id)
|
|
352
|
+
|
|
353
|
+
@app.post("/v1/responses/{response_id}:resume", status_code=202)
|
|
354
|
+
async def resume_openai_response(response_id: str):
|
|
355
|
+
return await shared_web.resume_run(response_id)
|
|
356
|
+
|
|
357
|
+
@app.post("/agentengine/api/v1/{action}")
|
|
358
|
+
async def shared_chat_action(
|
|
359
|
+
action: str,
|
|
360
|
+
request: Request,
|
|
361
|
+
payload: dict[str, Any],
|
|
362
|
+
):
|
|
363
|
+
try:
|
|
364
|
+
cookie_agent_id = request.cookies.get("agentkit_studio_chat_agent")
|
|
365
|
+
requested_agent_id = str(payload.get("AgentId") or cookie_agent_id or "")
|
|
366
|
+
if action == "GetAgentUiBootstrap":
|
|
367
|
+
data = shared_web.bootstrap(shared_web.resolve_agent_id(requested_agent_id or None))
|
|
368
|
+
elif action == "ListAgentModels":
|
|
369
|
+
data = shared_web.list_models(
|
|
370
|
+
shared_web.resolve_agent_id(requested_agent_id or None)
|
|
371
|
+
)
|
|
372
|
+
elif action == "ListSessions":
|
|
373
|
+
data = shared_web.list_sessions(
|
|
374
|
+
shared_web.resolve_agent_id(requested_agent_id or None),
|
|
375
|
+
page=int(payload.get("Page") or 1),
|
|
376
|
+
page_size=int(payload.get("PageSize") or 30),
|
|
377
|
+
)
|
|
378
|
+
elif action == "CreateSession":
|
|
379
|
+
data = shared_web.create_session(
|
|
380
|
+
shared_web.resolve_agent_id(requested_agent_id or None)
|
|
381
|
+
)
|
|
382
|
+
elif action == "GetSession":
|
|
383
|
+
data = shared_web.get_session(str(payload.get("SessionId") or ""))
|
|
384
|
+
elif action == "DeleteSession":
|
|
385
|
+
data = shared_web.delete_session(str(payload.get("SessionId") or ""))
|
|
386
|
+
elif action == "ListSessionMessages":
|
|
387
|
+
data = await shared_web.list_messages(
|
|
388
|
+
str(payload.get("SessionId") or ""),
|
|
389
|
+
after_seq_id=_optional_int(payload.get("AfterSeqId")),
|
|
390
|
+
before_seq_id=_optional_int(payload.get("BeforeSeqId")),
|
|
391
|
+
limit=int(payload.get("Limit") or 50),
|
|
392
|
+
)
|
|
393
|
+
elif action == "ListSessionEvents":
|
|
394
|
+
data = await shared_web.list_session_events(str(payload.get("SessionId") or ""))
|
|
395
|
+
elif action == "RunAgent":
|
|
396
|
+
return StreamingResponse(
|
|
397
|
+
shared_web.stream_run(payload),
|
|
398
|
+
media_type="text/event-stream",
|
|
399
|
+
headers={
|
|
400
|
+
"Cache-Control": "no-store",
|
|
401
|
+
"X-Accel-Buffering": "no",
|
|
402
|
+
},
|
|
403
|
+
)
|
|
404
|
+
elif action == "CancelRun":
|
|
405
|
+
data = shared_web.cancel_run(str(payload.get("InvocationId") or ""))
|
|
406
|
+
elif action in {
|
|
407
|
+
"GetResponseFeedback",
|
|
408
|
+
"UpsertResponseFeedback",
|
|
409
|
+
"DeleteResponseFeedback",
|
|
410
|
+
}:
|
|
411
|
+
data = {"Feedback": None} if action == "GetResponseFeedback" else {}
|
|
412
|
+
elif action == "ListSessionCheckpoints":
|
|
413
|
+
data = {"Checkpoints": []}
|
|
414
|
+
elif action == "ListToolReceipts":
|
|
415
|
+
data = {"ToolReceipts": []}
|
|
416
|
+
else:
|
|
417
|
+
return JSONResponse(
|
|
418
|
+
status_code=404,
|
|
419
|
+
content={
|
|
420
|
+
"Code": 404,
|
|
421
|
+
"Message": f"Studio 尚未实现共享 Web 动作:{action}",
|
|
422
|
+
"Data": {},
|
|
423
|
+
},
|
|
424
|
+
)
|
|
425
|
+
return {"Code": 0, "Message": "OK", "Data": data}
|
|
426
|
+
except StudioError as exc:
|
|
427
|
+
return JSONResponse(
|
|
428
|
+
status_code=exc.status_code,
|
|
429
|
+
content={
|
|
430
|
+
"Code": exc.status_code,
|
|
431
|
+
"Message": exc.message,
|
|
432
|
+
"Data": {"errorCode": exc.code},
|
|
433
|
+
},
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
@app.get("/agentengine/api/v1/SubscribeRunEvents")
|
|
437
|
+
async def shared_chat_subscribe_run_events():
|
|
438
|
+
async def completed_stream():
|
|
439
|
+
yield "event: done\ndata: [DONE]\n\n"
|
|
440
|
+
|
|
441
|
+
return StreamingResponse(
|
|
442
|
+
completed_stream(),
|
|
443
|
+
media_type="text/event-stream",
|
|
444
|
+
headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
@app.get("/api/v1/system/health")
|
|
448
|
+
async def health():
|
|
449
|
+
return {
|
|
450
|
+
"status": "ok",
|
|
451
|
+
"version": "1.0.0",
|
|
452
|
+
"workspaceReady": studio.workspace.root.is_dir(),
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
@app.post("/api/v1/system/session")
|
|
456
|
+
async def exchange_session(payload: SessionExchangeRequest, response: Response):
|
|
457
|
+
if security_enabled and not hmac.compare_digest(payload.token, session_secret):
|
|
458
|
+
raise StudioError(
|
|
459
|
+
"LOCAL_SESSION_INVALID",
|
|
460
|
+
"Studio 启动会话无效",
|
|
461
|
+
status_code=401,
|
|
462
|
+
)
|
|
463
|
+
response.set_cookie(
|
|
464
|
+
"agentkit_studio_session",
|
|
465
|
+
session_secret,
|
|
466
|
+
httponly=True,
|
|
467
|
+
samesite="strict",
|
|
468
|
+
secure=False,
|
|
469
|
+
path="/",
|
|
470
|
+
)
|
|
471
|
+
return {"csrfToken": csrf_secret}
|
|
472
|
+
|
|
473
|
+
@app.get("/api/v1/system/bootstrap")
|
|
474
|
+
async def bootstrap():
|
|
475
|
+
return {
|
|
476
|
+
"serverVersion": "1.0.0",
|
|
477
|
+
"apiVersion": "v1",
|
|
478
|
+
"sessionTokenRequired": security_enabled,
|
|
479
|
+
"csrfToken": csrf_secret,
|
|
480
|
+
"workspace": {
|
|
481
|
+
"name": studio.workspace.root.name,
|
|
482
|
+
"path": str(studio.workspace.root),
|
|
483
|
+
},
|
|
484
|
+
"operationScope": studio.deployment_operation_scope(),
|
|
485
|
+
"features": {
|
|
486
|
+
"build": True,
|
|
487
|
+
"run": True,
|
|
488
|
+
"runtimeRegistry": True,
|
|
489
|
+
"runtimeTypes": ["codex", "adk", "langgraph"],
|
|
490
|
+
"evaluation": True,
|
|
491
|
+
"deployment": True,
|
|
492
|
+
"cloudRebuild": False,
|
|
493
|
+
"reactChat": True,
|
|
494
|
+
},
|
|
495
|
+
"runtimes": studio.runtime_catalog(),
|
|
496
|
+
"importableProject": studio.detect_importable_project(),
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@app.get("/api/v1/system/settings")
|
|
500
|
+
async def get_settings():
|
|
501
|
+
return studio.get_settings()
|
|
502
|
+
|
|
503
|
+
@app.put("/api/v1/system/settings")
|
|
504
|
+
async def update_settings(payload: dict[str, Any]):
|
|
505
|
+
return studio.update_settings(payload)
|
|
506
|
+
|
|
507
|
+
@app.post("/api/v1/workspaces:open")
|
|
508
|
+
async def open_workspace(payload: WorkspaceOpenRequest):
|
|
509
|
+
# This endpoint only reconnects to the daemon's already-bound root. Do
|
|
510
|
+
# not resolve or otherwise touch a caller-provided filesystem path.
|
|
511
|
+
requested = os.path.normcase(os.path.abspath(os.path.expanduser(payload.path)))
|
|
512
|
+
bound_root = os.path.normcase(str(studio.workspace.root))
|
|
513
|
+
if requested != bound_root:
|
|
514
|
+
raise StudioError(
|
|
515
|
+
"WORKSPACE_PATH_FORBIDDEN",
|
|
516
|
+
"当前 Daemon 不允许切换到启动 root 之外的工作区",
|
|
517
|
+
status_code=403,
|
|
518
|
+
)
|
|
519
|
+
return {
|
|
520
|
+
"name": studio.workspace.root.name,
|
|
521
|
+
"path": str(studio.workspace.root),
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
@app.get("/api/v1/codex/manifest")
|
|
525
|
+
async def get_codex_manifest():
|
|
526
|
+
return studio.codex_manifest_state()
|
|
527
|
+
|
|
528
|
+
@app.put("/api/v1/codex/manifest")
|
|
529
|
+
async def put_codex_manifest(payload: CodexAgentManifest):
|
|
530
|
+
return studio.save_codex_manifest(payload)
|
|
531
|
+
|
|
532
|
+
@app.post("/api/v1/codex/manifest:validate")
|
|
533
|
+
async def validate_codex_manifest(_payload: CodexAgentManifest):
|
|
534
|
+
return {"valid": True, "diagnostics": []}
|
|
535
|
+
|
|
536
|
+
@app.post("/api/v1/codex/builds", status_code=202)
|
|
537
|
+
async def create_codex_build(
|
|
538
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
539
|
+
):
|
|
540
|
+
return studio.submit_codex_build(idempotency_key=_require_idempotency_key(idempotency_key))
|
|
541
|
+
|
|
542
|
+
@app.get("/api/v1/codex/builds")
|
|
543
|
+
async def list_codex_builds():
|
|
544
|
+
return {"items": studio.codex_builds.list()}
|
|
545
|
+
|
|
546
|
+
@app.get("/api/v1/codex/builds/{build_id}")
|
|
547
|
+
async def get_codex_build(build_id: str):
|
|
548
|
+
return studio.codex_builds.get(build_id)
|
|
549
|
+
|
|
550
|
+
@app.post("/api/v1/codex/builds/{build_id}/runs", status_code=202)
|
|
551
|
+
async def create_codex_run(
|
|
552
|
+
build_id: str,
|
|
553
|
+
payload: RunRequest,
|
|
554
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
555
|
+
):
|
|
556
|
+
if payload.environment != "local":
|
|
557
|
+
raise StudioError(
|
|
558
|
+
"RUN_ENVIRONMENT_UNSUPPORTED",
|
|
559
|
+
"Codex Studio 只支持只读本地运行",
|
|
560
|
+
status_code=422,
|
|
561
|
+
)
|
|
562
|
+
return studio.submit_codex_run(
|
|
563
|
+
build_id,
|
|
564
|
+
payload.input.content,
|
|
565
|
+
session_id=payload.session_id,
|
|
566
|
+
model=payload.model,
|
|
567
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
@app.post("/api/v1/builds/{build_id}/run:stream")
|
|
571
|
+
async def stream_run(
|
|
572
|
+
build_id: str,
|
|
573
|
+
payload: RunRequest,
|
|
574
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
575
|
+
):
|
|
576
|
+
key = _require_idempotency_key(idempotency_key)
|
|
577
|
+
if payload.environment != "local":
|
|
578
|
+
raise StudioError(
|
|
579
|
+
"RUN_ENVIRONMENT_UNSUPPORTED",
|
|
580
|
+
"Codex Studio 只支持只读本地运行",
|
|
581
|
+
status_code=422,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
585
|
+
|
|
586
|
+
def observe(event):
|
|
587
|
+
queue.put_nowait(event)
|
|
588
|
+
|
|
589
|
+
# The Operation owns the runtime task. The SSE response is only one
|
|
590
|
+
# observer, so refreshing or switching chats cannot cancel the Run.
|
|
591
|
+
operation = studio.submit_studio_run(
|
|
592
|
+
build_id,
|
|
593
|
+
payload.input.content,
|
|
594
|
+
session_id=payload.session_id,
|
|
595
|
+
model=payload.model,
|
|
596
|
+
sandbox=payload.sandbox,
|
|
597
|
+
idempotency_key=key,
|
|
598
|
+
on_event=observe,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
async def render():
|
|
602
|
+
while True:
|
|
603
|
+
try:
|
|
604
|
+
event = await asyncio.wait_for(queue.get(), timeout=0.1)
|
|
605
|
+
except TimeoutError:
|
|
606
|
+
event = None
|
|
607
|
+
if event is not None:
|
|
608
|
+
data = json.dumps(
|
|
609
|
+
event.data,
|
|
610
|
+
ensure_ascii=False,
|
|
611
|
+
separators=(",", ":"),
|
|
612
|
+
)
|
|
613
|
+
yield f"id: {event.id}\nevent: {event.type}\ndata: {data}\n\n"
|
|
614
|
+
current = studio.operations.get(operation.id)
|
|
615
|
+
if (
|
|
616
|
+
current.status
|
|
617
|
+
in {
|
|
618
|
+
OperationStatus.SUCCEEDED,
|
|
619
|
+
OperationStatus.FAILED,
|
|
620
|
+
OperationStatus.CANCELLED,
|
|
621
|
+
OperationStatus.INTERRUPTED,
|
|
622
|
+
}
|
|
623
|
+
and queue.empty()
|
|
624
|
+
):
|
|
625
|
+
if current.status == OperationStatus.FAILED:
|
|
626
|
+
data = json.dumps(current.error or {}, ensure_ascii=False)
|
|
627
|
+
yield f"event: run.failed\ndata: {data}\n\n"
|
|
628
|
+
break
|
|
629
|
+
|
|
630
|
+
return StreamingResponse(
|
|
631
|
+
render(),
|
|
632
|
+
media_type="text/event-stream",
|
|
633
|
+
headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
@app.post("/api/v1/agents", status_code=201)
|
|
637
|
+
async def create_agent(payload: CreateAgentRequest):
|
|
638
|
+
return studio.create_studio_agent(
|
|
639
|
+
agent_id=payload.id,
|
|
640
|
+
name=payload.name,
|
|
641
|
+
description=payload.description,
|
|
642
|
+
template=payload.template,
|
|
643
|
+
spec=payload.spec,
|
|
644
|
+
runtime=payload.runtime,
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
@app.post("/api/v1/assets/agent-avatars", status_code=201)
|
|
648
|
+
async def upload_agent_avatar(request: Request):
|
|
649
|
+
declared_size = request.headers.get("Content-Length")
|
|
650
|
+
if (
|
|
651
|
+
declared_size
|
|
652
|
+
and declared_size.isdigit()
|
|
653
|
+
and int(declared_size) > studio.avatar_assets.MAX_BYTES
|
|
654
|
+
):
|
|
655
|
+
raise StudioError(
|
|
656
|
+
"AGENT_AVATAR_TOO_LARGE",
|
|
657
|
+
"头像文件不能超过 2 MiB",
|
|
658
|
+
status_code=413,
|
|
659
|
+
field="file",
|
|
660
|
+
details={"maxBytes": studio.avatar_assets.MAX_BYTES},
|
|
661
|
+
)
|
|
662
|
+
return studio.avatar_assets.store(
|
|
663
|
+
await request.body(),
|
|
664
|
+
content_type=request.headers.get("Content-Type", ""),
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
@app.get("/api/v1/assets/agent-avatars/{asset_name}")
|
|
668
|
+
async def get_agent_avatar(asset_name: str):
|
|
669
|
+
path, media_type = studio.avatar_assets.get(asset_name)
|
|
670
|
+
return FileResponse(
|
|
671
|
+
path,
|
|
672
|
+
media_type=media_type,
|
|
673
|
+
headers={"Cache-Control": "private, max-age=31536000, immutable"},
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
@app.get("/api/v1/agent-templates")
|
|
677
|
+
async def agent_templates():
|
|
678
|
+
return {"items": studio.list_agent_templates()}
|
|
679
|
+
|
|
680
|
+
@app.post("/api/v1/agent-templates/{template_id}:compose")
|
|
681
|
+
async def compose_agent_template(
|
|
682
|
+
template_id: str,
|
|
683
|
+
payload: AgentTemplateComposeRequest,
|
|
684
|
+
):
|
|
685
|
+
return studio.compose_agent_template(template_id, payload)
|
|
686
|
+
|
|
687
|
+
@app.post("/api/v1/authoring/quick", status_code=201)
|
|
688
|
+
async def quick_author_agent(payload: QuickAuthoringRequest):
|
|
689
|
+
return studio.create_authored_agent(
|
|
690
|
+
name=payload.name,
|
|
691
|
+
slug=payload.slug,
|
|
692
|
+
runtime_type=payload.runtime_type,
|
|
693
|
+
template=payload.template,
|
|
694
|
+
description=payload.description,
|
|
695
|
+
spec=payload.spec,
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
@app.post("/api/v1/authoring/conversations:compose")
|
|
699
|
+
async def compose_authoring_conversation(payload: ConversationAuthoringRequest):
|
|
700
|
+
return await studio.compose_agent_conversation(
|
|
701
|
+
messages=[item.model_dump(mode="json") for item in payload.messages],
|
|
702
|
+
model_profile_id=payload.model_profile_id,
|
|
703
|
+
runtime_type=payload.runtime_type,
|
|
704
|
+
agent_model_profile_ids=payload.agent_model_profile_ids,
|
|
705
|
+
agent_default_model_profile_id=payload.agent_default_model_profile_id,
|
|
706
|
+
tool_resource_ids=payload.tool_resource_ids,
|
|
707
|
+
mcp_resource_ids=payload.mcp_resource_ids,
|
|
708
|
+
skill_resource_ids=payload.skill_resource_ids,
|
|
709
|
+
request_id=payload.request_id,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
@app.get("/api/v1/authoring/conversations:status/{request_id}")
|
|
713
|
+
async def get_authoring_conversation_status(request_id: str):
|
|
714
|
+
status = studio.conversation_authoring_status(request_id)
|
|
715
|
+
if status is None:
|
|
716
|
+
raise StudioError(
|
|
717
|
+
"AUTHORING_STATUS_NOT_FOUND",
|
|
718
|
+
"未找到该构建请求的阶段记录",
|
|
719
|
+
status_code=404,
|
|
720
|
+
)
|
|
721
|
+
return status
|
|
722
|
+
|
|
723
|
+
@app.post("/api/v1/authoring/imports:inspect")
|
|
724
|
+
async def inspect_agent_import(file: UploadFile = File(...)):
|
|
725
|
+
content = await file.read(100 * 1024 * 1024 + 1)
|
|
726
|
+
return studio.inspect_agent_import(
|
|
727
|
+
content,
|
|
728
|
+
filename=file.filename or "agent.yaml",
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
@app.post(
|
|
732
|
+
"/api/v1/authoring/imports/{inspection_token}:commit",
|
|
733
|
+
status_code=201,
|
|
734
|
+
)
|
|
735
|
+
async def commit_agent_import(
|
|
736
|
+
inspection_token: str,
|
|
737
|
+
payload: AuthoringCommitRequest,
|
|
738
|
+
):
|
|
739
|
+
return studio.commit_agent_import(
|
|
740
|
+
inspection_token,
|
|
741
|
+
name=payload.name,
|
|
742
|
+
slug=payload.slug,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
@app.post("/api/v1/authoring/projects:inspect")
|
|
746
|
+
async def inspect_agent_project(payload: ProjectInspectRequest):
|
|
747
|
+
return studio.inspect_agent_project(payload.path)
|
|
748
|
+
|
|
749
|
+
@app.post(
|
|
750
|
+
"/api/v1/authoring/projects/{inspection_token}:commit",
|
|
751
|
+
status_code=201,
|
|
752
|
+
)
|
|
753
|
+
async def commit_agent_project(
|
|
754
|
+
inspection_token: str,
|
|
755
|
+
payload: AuthoringCommitRequest,
|
|
756
|
+
):
|
|
757
|
+
return studio.commit_agent_project(
|
|
758
|
+
inspection_token,
|
|
759
|
+
name=payload.name,
|
|
760
|
+
slug=payload.slug,
|
|
761
|
+
model_profile_id=payload.model_profile_id,
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
@app.get("/api/v1/agents")
|
|
765
|
+
async def list_agents(
|
|
766
|
+
limit: int = Query(default=50, ge=1, le=200),
|
|
767
|
+
query: str = "",
|
|
768
|
+
):
|
|
769
|
+
return {
|
|
770
|
+
"items": studio.list_agents(query=query, limit=limit),
|
|
771
|
+
"nextCursor": None,
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
@app.get("/api/v1/agents/{agent_id}")
|
|
775
|
+
async def get_agent(agent_id: str):
|
|
776
|
+
return studio.agent_detail(agent_id)
|
|
777
|
+
|
|
778
|
+
@app.get("/api/v1/agents/{agent_id}/models")
|
|
779
|
+
async def get_agent_models(agent_id: str):
|
|
780
|
+
return shared_web.list_models(shared_web.resolve_agent_id(agent_id))
|
|
781
|
+
|
|
782
|
+
@app.put("/api/v1/agents/{agent_id}")
|
|
783
|
+
async def update_agent(
|
|
784
|
+
agent_id: str,
|
|
785
|
+
spec: AgentSpec,
|
|
786
|
+
if_match: str | None = Header(default=None, alias="If-Match"),
|
|
787
|
+
name: str | None = Query(default=None, min_length=1, max_length=128),
|
|
788
|
+
):
|
|
789
|
+
if not if_match:
|
|
790
|
+
raise StudioError(
|
|
791
|
+
"AGENT_REVISION_REQUIRED",
|
|
792
|
+
"更新 Agent 必须提供 If-Match revision",
|
|
793
|
+
status_code=428,
|
|
794
|
+
)
|
|
795
|
+
try:
|
|
796
|
+
revision = int(if_match.strip().strip('"'))
|
|
797
|
+
except ValueError as exc:
|
|
798
|
+
raise StudioError(
|
|
799
|
+
"AGENT_REVISION_INVALID",
|
|
800
|
+
"If-Match 必须是整数 revision",
|
|
801
|
+
status_code=400,
|
|
802
|
+
) from exc
|
|
803
|
+
return studio.update_studio_agent(
|
|
804
|
+
agent_id,
|
|
805
|
+
spec,
|
|
806
|
+
expected_revision=revision,
|
|
807
|
+
name=name,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
@app.put("/api/v1/agents/{agent_id}/bindings")
|
|
811
|
+
async def update_agent_bindings(
|
|
812
|
+
agent_id: str,
|
|
813
|
+
bindings: AgentBindings,
|
|
814
|
+
if_match: str | None = Header(default=None, alias="If-Match"),
|
|
815
|
+
):
|
|
816
|
+
revision = _parse_revision(if_match)
|
|
817
|
+
return studio.update_studio_agent_bindings(
|
|
818
|
+
agent_id,
|
|
819
|
+
bindings,
|
|
820
|
+
expected_revision=revision,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
@app.put("/api/v1/agents/{agent_id}/appearance")
|
|
824
|
+
async def update_agent_appearance(
|
|
825
|
+
agent_id: str,
|
|
826
|
+
appearance: AgentAppearance,
|
|
827
|
+
if_match: str | None = Header(default=None, alias="If-Match"),
|
|
828
|
+
):
|
|
829
|
+
return studio.update_studio_agent_appearance(
|
|
830
|
+
agent_id,
|
|
831
|
+
appearance,
|
|
832
|
+
expected_revision=_parse_revision(if_match),
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
@app.delete("/api/v1/agents/{agent_id}", status_code=204)
|
|
836
|
+
async def delete_agent(agent_id: str, purge: bool = False):
|
|
837
|
+
studio.delete_studio_agent(agent_id, purge=purge)
|
|
838
|
+
return Response(status_code=204)
|
|
839
|
+
|
|
840
|
+
@app.post("/api/v1/agents/{agent_id}/validations")
|
|
841
|
+
async def validate_agent(agent_id: str, payload: ValidationRequest):
|
|
842
|
+
return studio.validate_studio_agent(
|
|
843
|
+
agent_id,
|
|
844
|
+
revision=payload.revision,
|
|
845
|
+
level=payload.level,
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
@app.post("/api/v1/workspace:import-root", status_code=201)
|
|
849
|
+
async def import_root_project(payload: ImportRootRequest):
|
|
850
|
+
"""PR-S6:一键导入根 Framework 项目(方案 §6.1)。"""
|
|
851
|
+
return studio.import_root_project(name=payload.name, slug=payload.slug)
|
|
852
|
+
|
|
853
|
+
@app.post("/api/v1/agents/{agent_id}/prompt:compile")
|
|
854
|
+
async def compile_prompt(agent_id: str, payload: PromptCompileRequest):
|
|
855
|
+
"""PR-S2:Prompt 编译预览(方案 §6.2)。只读,不写 Session/Trace/Build。"""
|
|
856
|
+
return studio.compile_prompt_preview(
|
|
857
|
+
agent_id,
|
|
858
|
+
request_instructions=payload.request_instructions,
|
|
859
|
+
include_content=payload.include_content,
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
@app.post("/api/v1/agents/{agent_id}/context:preview")
|
|
863
|
+
async def preview_context(agent_id: str, payload: ContextPreviewRequest):
|
|
864
|
+
"""PR-S2:Context 预览(方案 §6.2)。复用真实 Planner,不调模型。"""
|
|
865
|
+
return await studio.preview_context(
|
|
866
|
+
agent_id,
|
|
867
|
+
user_input=payload.user_input,
|
|
868
|
+
request_instructions=payload.request_instructions,
|
|
869
|
+
simulated_history=[
|
|
870
|
+
{"role": m.role, "content": m.content} for m in payload.simulated_history
|
|
871
|
+
],
|
|
872
|
+
include_content=payload.include_content,
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
@app.post("/api/v1/agents/{agent_id}/builds", status_code=202)
|
|
876
|
+
async def create_build(
|
|
877
|
+
agent_id: str,
|
|
878
|
+
payload: BuildRequest,
|
|
879
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
880
|
+
):
|
|
881
|
+
return studio.submit_studio_build(
|
|
882
|
+
agent_id,
|
|
883
|
+
revision=payload.revision,
|
|
884
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
@app.get("/api/v1/builds/{build_id}")
|
|
888
|
+
async def get_build(build_id: str):
|
|
889
|
+
return studio.build_view(build_id)
|
|
890
|
+
|
|
891
|
+
@app.get("/api/v1/builds/{build_id}/manifest")
|
|
892
|
+
async def get_build_manifest(build_id: str):
|
|
893
|
+
try:
|
|
894
|
+
record = studio.codex_builds.get(build_id)
|
|
895
|
+
except StudioError as exc:
|
|
896
|
+
if exc.status_code != 404:
|
|
897
|
+
raise
|
|
898
|
+
else:
|
|
899
|
+
return {
|
|
900
|
+
"agentengine": studio.codex_manifests.load(record.agent_name).manifest,
|
|
901
|
+
"runtimeLock": record.runtime_lock,
|
|
902
|
+
"manifestSha256": record.manifest_sha256,
|
|
903
|
+
}
|
|
904
|
+
build = studio.builds.get(build_id)
|
|
905
|
+
if not build.artifact_path:
|
|
906
|
+
raise StudioError("BUILD_NOT_READY", "Build 尚未生成制品", status_code=409)
|
|
907
|
+
archive = studio.workspace.resolve(build.artifact_path)
|
|
908
|
+
manifest = archive.parent / "agent-bundle" / "manifest.json"
|
|
909
|
+
return json.loads(manifest.read_text(encoding="utf-8"))
|
|
910
|
+
|
|
911
|
+
@app.post("/api/v1/builds/{build_id}/runs", status_code=202)
|
|
912
|
+
async def create_run(
|
|
913
|
+
build_id: str,
|
|
914
|
+
payload: RunRequest,
|
|
915
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
916
|
+
):
|
|
917
|
+
if payload.environment != "local":
|
|
918
|
+
raise StudioError(
|
|
919
|
+
"RUN_ENVIRONMENT_UNSUPPORTED",
|
|
920
|
+
"一期 Local Run 只支持 environment=local",
|
|
921
|
+
status_code=422,
|
|
922
|
+
)
|
|
923
|
+
return studio.submit_studio_run(
|
|
924
|
+
build_id,
|
|
925
|
+
payload.input.content,
|
|
926
|
+
session_id=payload.session_id,
|
|
927
|
+
model=payload.model,
|
|
928
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
@app.get("/api/v1/runs/{run_id}")
|
|
932
|
+
async def get_run(run_id: str):
|
|
933
|
+
return studio.event_store.get(run_id)
|
|
934
|
+
|
|
935
|
+
@app.post("/api/v1/runs/{run_id}:cancel", status_code=202)
|
|
936
|
+
async def cancel_run(run_id: str):
|
|
937
|
+
return await studio.run_service.cancel_run(run_id)
|
|
938
|
+
|
|
939
|
+
@app.post("/api/v1/runs/{run_id}:pause", status_code=202)
|
|
940
|
+
async def pause_run(run_id: str):
|
|
941
|
+
return await studio.run_service.pause_run(run_id)
|
|
942
|
+
|
|
943
|
+
@app.post("/api/v1/runs/{run_id}:resume", status_code=202)
|
|
944
|
+
async def resume_run(run_id: str):
|
|
945
|
+
return await studio.run_service.resume_run(run_id)
|
|
946
|
+
|
|
947
|
+
@app.post("/api/v1/runs/{run_id}/interactions/{interaction_id}:submit")
|
|
948
|
+
async def submit_run_interaction(
|
|
949
|
+
run_id: str,
|
|
950
|
+
interaction_id: str,
|
|
951
|
+
payload: InteractionSubmitRequest,
|
|
952
|
+
):
|
|
953
|
+
return await studio.run_service.submit_interaction(
|
|
954
|
+
run_id,
|
|
955
|
+
interaction_id,
|
|
956
|
+
name=payload.name,
|
|
957
|
+
data=payload.data,
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
@app.get("/api/v1/runs/{run_id}/context")
|
|
961
|
+
async def get_run_context(run_id: str):
|
|
962
|
+
"""Runtime Context Evidence:planned/projected/actual + 精度 + ownership。"""
|
|
963
|
+
record = studio.event_store.get(run_id)
|
|
964
|
+
plan = record.context_plan or {}
|
|
965
|
+
evidence = record.prompt_evidence or {}
|
|
966
|
+
return {
|
|
967
|
+
"planId": plan.get("plan_id"),
|
|
968
|
+
"accuracy": evidence.get("accountingAccuracy")
|
|
969
|
+
or plan.get("accounting_accuracy", "opaque"),
|
|
970
|
+
"policyVersion": plan.get("policy_version"),
|
|
971
|
+
"tokensByKind": plan.get("tokens_by_kind", {}),
|
|
972
|
+
"plannedInputTokens": plan.get("planned_input_tokens"),
|
|
973
|
+
"projectedInputTokens": plan.get("projected_input_tokens"),
|
|
974
|
+
"runtimeReportedInputTokens": plan.get("runtime_reported_input_tokens"),
|
|
975
|
+
"selected": plan.get("selected", []),
|
|
976
|
+
"decisions": plan.get("decisions", []),
|
|
977
|
+
"ownership": {
|
|
978
|
+
"promptOwner": evidence.get("promptOwner"),
|
|
979
|
+
"historyOwner": (plan.get("history_owner") if isinstance(plan, dict) else None),
|
|
980
|
+
"integrationMode": evidence.get("integrationMode"),
|
|
981
|
+
"runtimeType": evidence.get("runtimeType"),
|
|
982
|
+
"deploymentMode": evidence.get("deploymentMode"),
|
|
983
|
+
"capabilityHash": evidence.get("capabilityHash"),
|
|
984
|
+
},
|
|
985
|
+
"warnings": [],
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
@app.get("/api/v1/runs/{run_id}/prompt")
|
|
989
|
+
async def get_run_prompt(run_id: str, include_content: bool = Query(default=False)):
|
|
990
|
+
"""PR-S4:Prompt evidence(方案 §6.3 / §7.3)。section hash/版本,默认不返回正文。"""
|
|
991
|
+
record = studio.event_store.get(run_id)
|
|
992
|
+
evidence = record.prompt_evidence or {}
|
|
993
|
+
result = {
|
|
994
|
+
"contentHash": evidence.get("contentHash"),
|
|
995
|
+
"stablePrefixHash": evidence.get("stablePrefixHash"),
|
|
996
|
+
"sectionHashes": evidence.get("sectionHashes", {}),
|
|
997
|
+
"tokensBySection": evidence.get("tokensBySection", {}),
|
|
998
|
+
"estimatedTokens": evidence.get("estimatedTokens"),
|
|
999
|
+
"sectionCount": evidence.get("sectionCount"),
|
|
1000
|
+
"plannedInputTokens": evidence.get("plannedInputTokens"),
|
|
1001
|
+
"accountingAccuracy": evidence.get("accountingAccuracy"),
|
|
1002
|
+
"runtimeType": evidence.get("runtimeType"),
|
|
1003
|
+
"integrationMode": evidence.get("integrationMode"),
|
|
1004
|
+
}
|
|
1005
|
+
if include_content:
|
|
1006
|
+
result["reveal"] = studio.reveal_run_prompt(run_id)
|
|
1007
|
+
return result
|
|
1008
|
+
|
|
1009
|
+
@app.get("/api/v1/runs/{run_id}/working-state")
|
|
1010
|
+
async def get_run_working_state(run_id: str):
|
|
1011
|
+
"""PR-S4:Working State evidence(方案 §6.5)。从 checkpoint/read record 读取。"""
|
|
1012
|
+
record = studio.event_store.get(run_id)
|
|
1013
|
+
return {"workingState": record.working_state}
|
|
1014
|
+
|
|
1015
|
+
@app.delete("/api/v1/sessions/{session_id}", status_code=204)
|
|
1016
|
+
async def delete_studio_session(session_id: str):
|
|
1017
|
+
await studio.delete_session(session_id)
|
|
1018
|
+
return Response(status_code=204)
|
|
1019
|
+
|
|
1020
|
+
@app.get("/api/v1/sessions/{session_id}/events")
|
|
1021
|
+
async def session_events(
|
|
1022
|
+
session_id: str,
|
|
1023
|
+
before_seq_id: int | None = Query(default=None, ge=1, alias="beforeSeqId"),
|
|
1024
|
+
invocation_id: str | None = Query(default=None, alias="invocationId"),
|
|
1025
|
+
limit: int = Query(default=100, ge=1, le=500),
|
|
1026
|
+
):
|
|
1027
|
+
return await studio.trajectory_page(
|
|
1028
|
+
session_id,
|
|
1029
|
+
before_seq_id=before_seq_id,
|
|
1030
|
+
invocation_id=invocation_id,
|
|
1031
|
+
limit=limit,
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
@app.get("/api/v1/sessions/{session_id}/events/stream")
|
|
1035
|
+
async def session_event_stream(
|
|
1036
|
+
session_id: str,
|
|
1037
|
+
request: Request,
|
|
1038
|
+
after_seq_id: int = Query(default=0, ge=0, alias="afterSeqId"),
|
|
1039
|
+
invocation_id: str | None = Query(default=None, alias="invocationId"),
|
|
1040
|
+
):
|
|
1041
|
+
await studio._require_runtime_session(session_id)
|
|
1042
|
+
last = request.headers.get("Last-Event-ID")
|
|
1043
|
+
cursor = int(last) if last and last.isdigit() else after_seq_id
|
|
1044
|
+
stream = studio.stream_trajectory(
|
|
1045
|
+
session_id,
|
|
1046
|
+
cursor,
|
|
1047
|
+
invocation_id=invocation_id,
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
async def frames():
|
|
1051
|
+
try:
|
|
1052
|
+
async for frame in stream:
|
|
1053
|
+
if await request.is_disconnected():
|
|
1054
|
+
return
|
|
1055
|
+
yield frame
|
|
1056
|
+
finally:
|
|
1057
|
+
await stream.aclose()
|
|
1058
|
+
|
|
1059
|
+
return StreamingResponse(
|
|
1060
|
+
frames(),
|
|
1061
|
+
media_type="text/event-stream",
|
|
1062
|
+
headers={
|
|
1063
|
+
"Cache-Control": "no-store",
|
|
1064
|
+
"X-Accel-Buffering": "no",
|
|
1065
|
+
},
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
@app.post("/api/v1/sessions/{session_id}:export")
|
|
1069
|
+
async def export_session(session_id: str, payload: dict[str, Any]):
|
|
1070
|
+
filename = payload.get("filename")
|
|
1071
|
+
invocation_id = payload.get("invocationId")
|
|
1072
|
+
download = payload.get("download", False)
|
|
1073
|
+
if not isinstance(filename, str):
|
|
1074
|
+
raise StudioError(
|
|
1075
|
+
"SESSION_EXPORT_FILENAME_INVALID",
|
|
1076
|
+
"filename 必须是字符串",
|
|
1077
|
+
status_code=422,
|
|
1078
|
+
field="filename",
|
|
1079
|
+
)
|
|
1080
|
+
if invocation_id is not None and not isinstance(invocation_id, str):
|
|
1081
|
+
raise StudioError(
|
|
1082
|
+
"SESSION_EXPORT_INVOCATION_INVALID",
|
|
1083
|
+
"invocationId 必须是字符串",
|
|
1084
|
+
status_code=422,
|
|
1085
|
+
field="invocationId",
|
|
1086
|
+
)
|
|
1087
|
+
if not isinstance(download, bool):
|
|
1088
|
+
raise StudioError(
|
|
1089
|
+
"SESSION_EXPORT_DOWNLOAD_INVALID",
|
|
1090
|
+
"download 必须是布尔值",
|
|
1091
|
+
status_code=422,
|
|
1092
|
+
field="download",
|
|
1093
|
+
)
|
|
1094
|
+
result = await studio.export_runtime_session(
|
|
1095
|
+
session_id,
|
|
1096
|
+
filename=filename,
|
|
1097
|
+
invocation_id=invocation_id,
|
|
1098
|
+
)
|
|
1099
|
+
if not download:
|
|
1100
|
+
return result
|
|
1101
|
+
|
|
1102
|
+
path = studio.workspace.resolve(result["path"])
|
|
1103
|
+
return FileResponse(
|
|
1104
|
+
path,
|
|
1105
|
+
filename=filename,
|
|
1106
|
+
media_type="application/x-ndjson",
|
|
1107
|
+
headers={"X-Session-Event-Count": str(result["eventCount"])},
|
|
1108
|
+
background=BackgroundTask(path.unlink, missing_ok=True),
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
@app.get("/api/v1/runs")
|
|
1112
|
+
async def list_runs(session_id: str | None = Query(default=None, alias="sessionId")):
|
|
1113
|
+
return {"items": studio.event_store.list_runs(session_id=session_id)}
|
|
1114
|
+
|
|
1115
|
+
@app.get("/api/v1/runs/{run_id}/events")
|
|
1116
|
+
async def run_events(
|
|
1117
|
+
run_id: str,
|
|
1118
|
+
request: Request,
|
|
1119
|
+
after: int = Query(default=0, ge=0),
|
|
1120
|
+
):
|
|
1121
|
+
last = request.headers.get("Last-Event-ID")
|
|
1122
|
+
cursor = int(last) if last and last.isdigit() else after
|
|
1123
|
+
events = await studio.run_service.events(run_id, after=cursor)
|
|
1124
|
+
return _sse(events)
|
|
1125
|
+
|
|
1126
|
+
@app.get("/api/v1/traces/overview")
|
|
1127
|
+
async def trace_overview(
|
|
1128
|
+
range_name: Literal["24h", "7d"] = Query(default="24h", alias="range"),
|
|
1129
|
+
agent_id: str | None = Query(default=None, alias="agentId"),
|
|
1130
|
+
status: str | None = Query(default=None),
|
|
1131
|
+
):
|
|
1132
|
+
return studio.event_store.trace_overview(
|
|
1133
|
+
range_name=range_name,
|
|
1134
|
+
agent_id=agent_id,
|
|
1135
|
+
status=status,
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
@app.get("/api/v1/traces")
|
|
1139
|
+
async def list_traces(
|
|
1140
|
+
agent_id: str | None = Query(default=None, alias="agentId"),
|
|
1141
|
+
status: str | None = Query(default=None),
|
|
1142
|
+
query: str = Query(default=""),
|
|
1143
|
+
limit: int = Query(default=50, ge=1, le=1000),
|
|
1144
|
+
cursor: str | None = Query(default=None),
|
|
1145
|
+
sort: Literal["startedAt:desc", "startedAt:asc"] = "startedAt:desc",
|
|
1146
|
+
):
|
|
1147
|
+
return studio.event_store.list_traces_page(
|
|
1148
|
+
agent_id=agent_id,
|
|
1149
|
+
status=status,
|
|
1150
|
+
query=query,
|
|
1151
|
+
limit=limit,
|
|
1152
|
+
cursor=cursor,
|
|
1153
|
+
sort=sort,
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
@app.get("/api/v1/traces/{trace_id}")
|
|
1157
|
+
async def get_trace(trace_id: str):
|
|
1158
|
+
return studio.event_store.trace(trace_id)
|
|
1159
|
+
|
|
1160
|
+
@app.get("/api/v1/traces/{trace_id}/otlp")
|
|
1161
|
+
async def get_trace_otlp(trace_id: str):
|
|
1162
|
+
return studio.event_store.trace_otlp(trace_id)
|
|
1163
|
+
|
|
1164
|
+
@app.post("/api/v1/evaluations", status_code=202)
|
|
1165
|
+
async def create_public_evaluation(
|
|
1166
|
+
payload: StudioEvaluationCreate,
|
|
1167
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
1168
|
+
):
|
|
1169
|
+
return studio.submit_public_evaluation(
|
|
1170
|
+
payload.evalset_file,
|
|
1171
|
+
payload.target,
|
|
1172
|
+
payload.config,
|
|
1173
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
@app.post("/api/v1/evaluation-files", status_code=201)
|
|
1177
|
+
async def import_evaluation_file(file: UploadFile = File(...)):
|
|
1178
|
+
return studio.import_evaluation_file(
|
|
1179
|
+
await file.read(2 * 1024 * 1024 + 1),
|
|
1180
|
+
filename=file.filename or "evalset.yaml",
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
@app.get("/api/v1/evaluations")
|
|
1184
|
+
async def list_public_evaluations():
|
|
1185
|
+
return {"items": studio.list_public_evaluations()}
|
|
1186
|
+
|
|
1187
|
+
@app.get("/api/v1/evaluation-runs")
|
|
1188
|
+
async def list_public_evaluation_runs():
|
|
1189
|
+
return {"items": studio.list_public_evaluation_runs()}
|
|
1190
|
+
|
|
1191
|
+
@app.get("/api/v1/evaluation-runs/{evaluation_id}")
|
|
1192
|
+
async def get_public_evaluation_run(evaluation_id: str):
|
|
1193
|
+
return studio.get_public_evaluation_run(evaluation_id)
|
|
1194
|
+
|
|
1195
|
+
@app.get("/api/v1/evaluation-targets")
|
|
1196
|
+
async def list_evaluation_targets():
|
|
1197
|
+
return studio.evaluation_catalog()
|
|
1198
|
+
|
|
1199
|
+
@app.get("/api/v1/evaluation-cloud/catalog")
|
|
1200
|
+
async def list_evaluation_cloud_catalog(
|
|
1201
|
+
project_id: str | None = Query(default=None, alias="projectId"),
|
|
1202
|
+
):
|
|
1203
|
+
items = await studio.evaluation_cloud_catalog(project_id=project_id)
|
|
1204
|
+
return {"items": items}
|
|
1205
|
+
|
|
1206
|
+
@app.get("/api/v1/evaluations/{evaluation_id}")
|
|
1207
|
+
async def get_evaluation(evaluation_id: str):
|
|
1208
|
+
return studio.get_public_evaluation(evaluation_id)
|
|
1209
|
+
|
|
1210
|
+
@app.get("/api/v1/evaluations/{evaluation_id}/cases/{case_id}")
|
|
1211
|
+
async def get_public_evaluation_case(evaluation_id: str, case_id: str):
|
|
1212
|
+
report = studio.get_public_evaluation(evaluation_id)
|
|
1213
|
+
for case_run in report.case_runs:
|
|
1214
|
+
if case_run.case_id == case_id:
|
|
1215
|
+
return case_run
|
|
1216
|
+
raise StudioError(
|
|
1217
|
+
"EVALUATION_CASE_NOT_FOUND",
|
|
1218
|
+
"Evaluation Case 不存在",
|
|
1219
|
+
status_code=404,
|
|
1220
|
+
details={"id": case_id, "evaluationId": evaluation_id},
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
@app.post("/api/v1/builds/{build_id}/deployments", status_code=202)
|
|
1224
|
+
async def create_deployment(
|
|
1225
|
+
build_id: str,
|
|
1226
|
+
payload: DeploymentRequest,
|
|
1227
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
1228
|
+
):
|
|
1229
|
+
return studio.submit_deployment(
|
|
1230
|
+
build_id,
|
|
1231
|
+
payload,
|
|
1232
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
@app.get("/api/v1/deployments")
|
|
1236
|
+
async def list_deployments():
|
|
1237
|
+
"""Read local deployment receipts without implicit cloud refreshes."""
|
|
1238
|
+
|
|
1239
|
+
return {"items": studio.cloud.list()}
|
|
1240
|
+
|
|
1241
|
+
@app.get("/api/v1/cloud-agents")
|
|
1242
|
+
async def list_account_cloud_agents(
|
|
1243
|
+
page: int = Query(default=1, ge=1),
|
|
1244
|
+
size: int = Query(default=100, ge=1, le=100),
|
|
1245
|
+
):
|
|
1246
|
+
"""List Agents visible to Studio's configured signed cloud account."""
|
|
1247
|
+
|
|
1248
|
+
return await studio.cloud.list_account_agents(page=page, size=size)
|
|
1249
|
+
|
|
1250
|
+
@app.get("/api/v1/cloud-agents/{agent_id}")
|
|
1251
|
+
async def get_account_cloud_agent(agent_id: str):
|
|
1252
|
+
return await studio.cloud.get_account_agent(agent_id)
|
|
1253
|
+
|
|
1254
|
+
@app.get("/api/v1/cloud-agents/{agent_id}/versions")
|
|
1255
|
+
async def list_account_cloud_agent_versions(
|
|
1256
|
+
agent_id: str,
|
|
1257
|
+
page: int = Query(default=1, ge=1),
|
|
1258
|
+
size: int = Query(default=100, ge=1, le=100),
|
|
1259
|
+
):
|
|
1260
|
+
"""List the Server-owned version history and rollback eligibility."""
|
|
1261
|
+
|
|
1262
|
+
return await studio.cloud.list_account_agent_versions(
|
|
1263
|
+
agent_id,
|
|
1264
|
+
page=page,
|
|
1265
|
+
size=size,
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
@app.post(
|
|
1269
|
+
"/api/v1/cloud-agents/{agent_id}:rollback-version",
|
|
1270
|
+
status_code=202,
|
|
1271
|
+
)
|
|
1272
|
+
async def rollback_account_cloud_agent_version(
|
|
1273
|
+
agent_id: str,
|
|
1274
|
+
payload: CloudAgentVersionRollbackRequest,
|
|
1275
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
1276
|
+
):
|
|
1277
|
+
"""Submit Server RollbackVersion through Studio's process-only AK/SK."""
|
|
1278
|
+
|
|
1279
|
+
return studio.submit_account_agent_version_rollback(
|
|
1280
|
+
agent_id,
|
|
1281
|
+
version_id=payload.version_id,
|
|
1282
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
1283
|
+
)
|
|
1284
|
+
|
|
1285
|
+
@app.post("/api/v1/cloud-agents/{agent_id}:dashboard")
|
|
1286
|
+
async def open_account_cloud_agent_dashboard(agent_id: str):
|
|
1287
|
+
return await studio.cloud.account_agent_dashboard_access(agent_id)
|
|
1288
|
+
|
|
1289
|
+
@app.delete("/api/v1/cloud-agents/{agent_id}")
|
|
1290
|
+
async def delete_account_cloud_agent(agent_id: str):
|
|
1291
|
+
return await studio.cloud.delete_account_agent(agent_id)
|
|
1292
|
+
|
|
1293
|
+
@app.get("/api/v1/deployments/{deployment_id}")
|
|
1294
|
+
async def get_deployment(deployment_id: str):
|
|
1295
|
+
return await studio.cloud.refresh(deployment_id)
|
|
1296
|
+
|
|
1297
|
+
@app.post("/api/v1/deployments/{deployment_id}:dashboard")
|
|
1298
|
+
async def open_deployment_dashboard(deployment_id: str):
|
|
1299
|
+
return await studio.deployment_dashboard_access(deployment_id)
|
|
1300
|
+
|
|
1301
|
+
@app.delete("/api/v1/deployments/{deployment_id}")
|
|
1302
|
+
async def delete_deployment(deployment_id: str):
|
|
1303
|
+
"""Delete the receipt-bound cloud Agent and its superseded local receipts."""
|
|
1304
|
+
|
|
1305
|
+
return await studio.cloud.delete(deployment_id)
|
|
1306
|
+
|
|
1307
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/sessions")
|
|
1308
|
+
async def list_cloud_chat_sessions(
|
|
1309
|
+
deployment_id: str,
|
|
1310
|
+
page: int = Query(default=1, ge=1),
|
|
1311
|
+
size: int = Query(default=50, ge=1, le=100),
|
|
1312
|
+
):
|
|
1313
|
+
"""List Server-owned sessions for this local deployment receipt only."""
|
|
1314
|
+
|
|
1315
|
+
return await studio.cloud.list_cloud_chat_sessions(
|
|
1316
|
+
deployment_id, page=page, size=size
|
|
1317
|
+
)
|
|
1318
|
+
|
|
1319
|
+
@app.get("/api/v1/deployments/{deployment_id}/cloud-chat/models")
|
|
1320
|
+
async def list_cloud_chat_models(deployment_id: str):
|
|
1321
|
+
"""List models through Studio's signed Server client."""
|
|
1322
|
+
|
|
1323
|
+
return await studio.cloud.list_cloud_chat_models(deployment_id)
|
|
1324
|
+
|
|
1325
|
+
@app.post(
|
|
1326
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions",
|
|
1327
|
+
status_code=201,
|
|
1328
|
+
)
|
|
1329
|
+
async def create_cloud_chat_session(deployment_id: str):
|
|
1330
|
+
"""Create a cloud session via loopback-held AK/SK; no secret reaches JS."""
|
|
1331
|
+
|
|
1332
|
+
return await studio.cloud.create_cloud_chat_session(deployment_id)
|
|
1333
|
+
|
|
1334
|
+
@app.get(
|
|
1335
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages"
|
|
1336
|
+
)
|
|
1337
|
+
async def list_cloud_chat_messages(
|
|
1338
|
+
deployment_id: str,
|
|
1339
|
+
session_id: str,
|
|
1340
|
+
after_seq_id: int | None = Query(default=None, alias="afterSeqId", ge=0),
|
|
1341
|
+
limit: int = Query(default=100, ge=1, le=200),
|
|
1342
|
+
):
|
|
1343
|
+
return await studio.cloud.list_cloud_chat_messages(
|
|
1344
|
+
deployment_id,
|
|
1345
|
+
session_id=session_id,
|
|
1346
|
+
after_seq_id=after_seq_id,
|
|
1347
|
+
limit=limit,
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
@app.get(
|
|
1351
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events"
|
|
1352
|
+
)
|
|
1353
|
+
async def list_cloud_chat_events(
|
|
1354
|
+
deployment_id: str,
|
|
1355
|
+
session_id: str,
|
|
1356
|
+
after_seq_id: int | None = Query(default=None, alias="afterSeqId", ge=0),
|
|
1357
|
+
limit: int = Query(default=200, ge=1, le=1000),
|
|
1358
|
+
):
|
|
1359
|
+
return await studio.cloud.list_cloud_chat_events(
|
|
1360
|
+
deployment_id,
|
|
1361
|
+
session_id=session_id,
|
|
1362
|
+
after_seq_id=after_seq_id,
|
|
1363
|
+
limit=limit,
|
|
1364
|
+
)
|
|
1365
|
+
|
|
1366
|
+
@app.get(
|
|
1367
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/events/stream"
|
|
1368
|
+
)
|
|
1369
|
+
async def stream_cloud_chat_events(
|
|
1370
|
+
request: Request,
|
|
1371
|
+
deployment_id: str,
|
|
1372
|
+
session_id: str,
|
|
1373
|
+
after_seq_id: int = Query(default=0, alias="afterSeqId", ge=0),
|
|
1374
|
+
):
|
|
1375
|
+
"""Stream canonical cloud events to the loopback browser.
|
|
1376
|
+
|
|
1377
|
+
The public cloud control plane currently exposes cursor reads for this
|
|
1378
|
+
surface. Keep that cursor in the Studio backend and present one SSE
|
|
1379
|
+
response to the browser, so assistant deltas arrive before the durable
|
|
1380
|
+
terminal message projection without exposing cloud credentials to JS.
|
|
1381
|
+
"""
|
|
1382
|
+
|
|
1383
|
+
async def event_stream() -> AsyncIterator[str]:
|
|
1384
|
+
cursor = after_seq_id
|
|
1385
|
+
idle_polls = 0
|
|
1386
|
+
while not await request.is_disconnected():
|
|
1387
|
+
payload = await studio.cloud.list_cloud_chat_events(
|
|
1388
|
+
deployment_id,
|
|
1389
|
+
session_id=session_id,
|
|
1390
|
+
after_seq_id=cursor,
|
|
1391
|
+
limit=200,
|
|
1392
|
+
)
|
|
1393
|
+
events = payload.get("events") or []
|
|
1394
|
+
if not isinstance(events, list):
|
|
1395
|
+
events = []
|
|
1396
|
+
terminal = False
|
|
1397
|
+
emitted = False
|
|
1398
|
+
for event in events:
|
|
1399
|
+
if not isinstance(event, dict):
|
|
1400
|
+
continue
|
|
1401
|
+
event_payload = (
|
|
1402
|
+
event.get("payload")
|
|
1403
|
+
if isinstance(event.get("payload"), dict)
|
|
1404
|
+
else event
|
|
1405
|
+
)
|
|
1406
|
+
seq = int(
|
|
1407
|
+
event_payload.get("seq")
|
|
1408
|
+
or event_payload.get("seq_id")
|
|
1409
|
+
or event_payload.get("source_session_seq")
|
|
1410
|
+
or event.get("seq")
|
|
1411
|
+
or event.get("seq_id")
|
|
1412
|
+
or 0
|
|
1413
|
+
)
|
|
1414
|
+
if seq and seq <= cursor:
|
|
1415
|
+
continue
|
|
1416
|
+
if seq:
|
|
1417
|
+
cursor = max(cursor, seq)
|
|
1418
|
+
event_type = str(
|
|
1419
|
+
event.get("event_type")
|
|
1420
|
+
or event.get("eventType")
|
|
1421
|
+
or event_payload.get("event_type")
|
|
1422
|
+
or event_payload.get("eventType")
|
|
1423
|
+
or ""
|
|
1424
|
+
).lower()
|
|
1425
|
+
content = (
|
|
1426
|
+
event_payload.get("content")
|
|
1427
|
+
if isinstance(event_payload.get("content"), dict)
|
|
1428
|
+
else {}
|
|
1429
|
+
)
|
|
1430
|
+
status = str(
|
|
1431
|
+
event_payload.get("status") or content.get("status") or ""
|
|
1432
|
+
).lower()
|
|
1433
|
+
event_is_terminal = event_type in {
|
|
1434
|
+
"run.completed", "run.complete", "run.succeeded",
|
|
1435
|
+
"run.failed", "run.cancelled", "run.expired", "run.error",
|
|
1436
|
+
} or (
|
|
1437
|
+
event_type in {"run_status", "run.status"}
|
|
1438
|
+
and status in {
|
|
1439
|
+
"completed", "complete", "succeeded", "success",
|
|
1440
|
+
"failed", "cancelled", "canceled", "expired", "error", "aborted",
|
|
1441
|
+
}
|
|
1442
|
+
)
|
|
1443
|
+
terminal = terminal or event_is_terminal
|
|
1444
|
+
emitted = True
|
|
1445
|
+
yield (
|
|
1446
|
+
(f"id: {seq}\n" if seq else "")
|
|
1447
|
+
+ "event: session.event\n"
|
|
1448
|
+
+ f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
|
|
1449
|
+
)
|
|
1450
|
+
if terminal or payload.get("session_deleted"):
|
|
1451
|
+
break
|
|
1452
|
+
idle_polls = 0 if emitted else idle_polls + 1
|
|
1453
|
+
if idle_polls and idle_polls % 20 == 0:
|
|
1454
|
+
yield ": keepalive\n\n"
|
|
1455
|
+
await asyncio.sleep(0.25)
|
|
1456
|
+
|
|
1457
|
+
return StreamingResponse(
|
|
1458
|
+
event_stream(),
|
|
1459
|
+
media_type="text/event-stream",
|
|
1460
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
@app.delete(
|
|
1464
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}",
|
|
1465
|
+
status_code=204,
|
|
1466
|
+
)
|
|
1467
|
+
async def delete_cloud_chat_session(deployment_id: str, session_id: str):
|
|
1468
|
+
await studio.cloud.delete_cloud_chat_session(
|
|
1469
|
+
deployment_id, session_id=session_id
|
|
1470
|
+
)
|
|
1471
|
+
return Response(status_code=204)
|
|
1472
|
+
|
|
1473
|
+
@app.post(
|
|
1474
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages",
|
|
1475
|
+
status_code=202,
|
|
1476
|
+
)
|
|
1477
|
+
async def send_cloud_chat_message(
|
|
1478
|
+
deployment_id: str,
|
|
1479
|
+
session_id: str,
|
|
1480
|
+
payload: CloudChatMessageRequest,
|
|
1481
|
+
):
|
|
1482
|
+
"""Admit one cloud message through Server; response is a durable receipt."""
|
|
1483
|
+
|
|
1484
|
+
return await studio.cloud.send_cloud_chat_message(
|
|
1485
|
+
deployment_id,
|
|
1486
|
+
session_id=session_id,
|
|
1487
|
+
content=payload.content,
|
|
1488
|
+
model=payload.model,
|
|
1489
|
+
model_options=payload.model_options,
|
|
1490
|
+
tool_approval_mode=payload.tool_approval_mode,
|
|
1491
|
+
collaboration_mode=payload.collaboration_mode,
|
|
1492
|
+
goal_objective=payload.goal_objective,
|
|
1493
|
+
)
|
|
1494
|
+
|
|
1495
|
+
@app.post(
|
|
1496
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/messages/stream"
|
|
1497
|
+
)
|
|
1498
|
+
async def stream_cloud_chat_message(
|
|
1499
|
+
request: Request,
|
|
1500
|
+
deployment_id: str,
|
|
1501
|
+
session_id: str,
|
|
1502
|
+
payload: CloudChatMessageRequest,
|
|
1503
|
+
):
|
|
1504
|
+
"""Proxy one signed foreground RunAgent SSE response to loopback UI."""
|
|
1505
|
+
|
|
1506
|
+
upstream = await studio.cloud.stream_cloud_chat_message(
|
|
1507
|
+
deployment_id,
|
|
1508
|
+
session_id=session_id,
|
|
1509
|
+
content=payload.content,
|
|
1510
|
+
model=payload.model,
|
|
1511
|
+
model_options=payload.model_options,
|
|
1512
|
+
tool_approval_mode=payload.tool_approval_mode,
|
|
1513
|
+
collaboration_mode=payload.collaboration_mode,
|
|
1514
|
+
goal_objective=payload.goal_objective,
|
|
1515
|
+
)
|
|
1516
|
+
|
|
1517
|
+
async def proxy_stream() -> AsyncIterator[bytes]:
|
|
1518
|
+
try:
|
|
1519
|
+
while not await request.is_disconnected():
|
|
1520
|
+
try:
|
|
1521
|
+
chunk = await anext(upstream)
|
|
1522
|
+
except StopAsyncIteration:
|
|
1523
|
+
break
|
|
1524
|
+
if await request.is_disconnected():
|
|
1525
|
+
break
|
|
1526
|
+
yield chunk
|
|
1527
|
+
finally:
|
|
1528
|
+
close = getattr(upstream, "aclose", None)
|
|
1529
|
+
if close is not None:
|
|
1530
|
+
await close()
|
|
1531
|
+
|
|
1532
|
+
return StreamingResponse(
|
|
1533
|
+
proxy_stream(),
|
|
1534
|
+
media_type="text/event-stream",
|
|
1535
|
+
headers={"Cache-Control": "no-store", "X-Accel-Buffering": "no"},
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
@app.post(
|
|
1539
|
+
"/api/v1/deployments/{deployment_id}/cloud-chat/sessions/{session_id}/interactions",
|
|
1540
|
+
status_code=202,
|
|
1541
|
+
)
|
|
1542
|
+
async def submit_cloud_chat_interaction(
|
|
1543
|
+
deployment_id: str,
|
|
1544
|
+
session_id: str,
|
|
1545
|
+
payload: CloudChatInteractionSubmitRequest,
|
|
1546
|
+
):
|
|
1547
|
+
return await studio.cloud.submit_cloud_chat_interaction(
|
|
1548
|
+
deployment_id,
|
|
1549
|
+
session_id=session_id,
|
|
1550
|
+
run_id=payload.run_id,
|
|
1551
|
+
interaction_id=payload.interaction_id,
|
|
1552
|
+
expected_revision=payload.expected_revision,
|
|
1553
|
+
action=payload.action,
|
|
1554
|
+
response=payload.response,
|
|
1555
|
+
idempotency_key=payload.idempotency_key,
|
|
1556
|
+
)
|
|
1557
|
+
|
|
1558
|
+
@app.post("/api/v1/deployments/{deployment_id}:rollback", status_code=202)
|
|
1559
|
+
async def rollback_deployment(
|
|
1560
|
+
deployment_id: str,
|
|
1561
|
+
payload: RollbackRequest,
|
|
1562
|
+
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
|
|
1563
|
+
):
|
|
1564
|
+
return studio.submit_rollback(
|
|
1565
|
+
deployment_id,
|
|
1566
|
+
target_build_id=payload.target_build_id,
|
|
1567
|
+
idempotency_key=_require_idempotency_key(idempotency_key),
|
|
1568
|
+
)
|
|
1569
|
+
|
|
1570
|
+
@app.get("/api/v1/operations/{operation_id}")
|
|
1571
|
+
async def get_operation(operation_id: str):
|
|
1572
|
+
return studio.operations.get(operation_id)
|
|
1573
|
+
|
|
1574
|
+
@app.post("/api/v1/operations/{operation_id}:cancel")
|
|
1575
|
+
async def cancel_operation(operation_id: str):
|
|
1576
|
+
return studio.operations.cancel(operation_id)
|
|
1577
|
+
|
|
1578
|
+
@app.get("/api/v1/operations/{operation_id}/events")
|
|
1579
|
+
async def operation_events(
|
|
1580
|
+
operation_id: str,
|
|
1581
|
+
request: Request,
|
|
1582
|
+
after: int = Query(default=0, ge=0),
|
|
1583
|
+
):
|
|
1584
|
+
last = request.headers.get("Last-Event-ID")
|
|
1585
|
+
cursor = int(last) if last and last.isdigit() else after
|
|
1586
|
+
events = studio.operations.events(operation_id, after=cursor)
|
|
1587
|
+
if "application/json" in request.headers.get("Accept", ""):
|
|
1588
|
+
return {"items": events}
|
|
1589
|
+
return _sse(events)
|
|
1590
|
+
|
|
1591
|
+
register_catalog_routes(
|
|
1592
|
+
app,
|
|
1593
|
+
studio,
|
|
1594
|
+
runtime_model_catalog=runtime_model_catalog,
|
|
1595
|
+
)
|
|
1596
|
+
register_memory_routes(app, studio)
|
|
1597
|
+
|
|
1598
|
+
return app
|