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/codex/runtime.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""CodexRuntimeAdapter — 非 ADK 体系的第三验证样本 (goal-09)。
|
|
2
2
|
|
|
3
3
|
对执行生命周期负责(不做 veadk 式薄桥接),后端能力面对齐 ``openai-codex`` SDK 真实线程模型
|
|
4
4
|
(``thread_start``/``thread.turn``/``handle.stream``/``handle.interrupt``/``thread_resume``):
|
|
@@ -19,19 +19,39 @@
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import asyncio
|
|
22
|
+
import base64
|
|
23
|
+
import binascii
|
|
24
|
+
import hashlib
|
|
22
25
|
import json
|
|
23
26
|
import logging
|
|
27
|
+
import re
|
|
28
|
+
import time
|
|
29
|
+
from collections.abc import Mapping
|
|
24
30
|
from dataclasses import dataclass, field
|
|
31
|
+
from pathlib import Path
|
|
25
32
|
from typing import Any, AsyncIterator, Optional
|
|
26
33
|
|
|
27
34
|
from ksadk.codex.client import CodexClient
|
|
28
|
-
from ksadk.codex
|
|
29
|
-
from ksadk.events.
|
|
35
|
+
from ksadk.events.adapters.codex import CodexAdapterContext, CodexEventAdapter
|
|
36
|
+
from ksadk.events.canonical import (
|
|
37
|
+
ErrorInfo,
|
|
38
|
+
InteractionRequested,
|
|
39
|
+
InteractionResolved,
|
|
40
|
+
RunCanceled,
|
|
41
|
+
RunCompleted,
|
|
42
|
+
RunFailed,
|
|
43
|
+
RunInterrupted,
|
|
44
|
+
RuntimeEvent,
|
|
45
|
+
SourceRef,
|
|
46
|
+
)
|
|
47
|
+
from ksadk.events.identity import stable_event_id, stable_item_id, stable_scope_id
|
|
48
|
+
from ksadk.kernel.contracts import RuntimeCapability, RuntimeCapabilityMatrix
|
|
30
49
|
from ksadk.runtime.adapter import (
|
|
31
50
|
BaseRuntime,
|
|
32
51
|
CancelResult,
|
|
33
52
|
CheckpointCapability,
|
|
34
53
|
CheckpointDescriptor,
|
|
54
|
+
PauseResult,
|
|
35
55
|
ResumePayload,
|
|
36
56
|
ResumeTarget,
|
|
37
57
|
RunHandle,
|
|
@@ -41,6 +61,8 @@ from ksadk.runtime.adapter import (
|
|
|
41
61
|
|
|
42
62
|
logger = logging.getLogger(__name__)
|
|
43
63
|
|
|
64
|
+
_INTERRUPT_DRAIN_TIMEOUT_SECONDS = 5.0
|
|
65
|
+
|
|
44
66
|
|
|
45
67
|
class _CodexAsBaseRuntime(BaseRuntime):
|
|
46
68
|
"""把 CodexClient 包装为 BaseRuntime(原生能力面)。"""
|
|
@@ -50,7 +72,13 @@ class _CodexAsBaseRuntime(BaseRuntime):
|
|
|
50
72
|
self.runtime_type = "codex"
|
|
51
73
|
|
|
52
74
|
def native_capabilities(self) -> dict[str, Any]:
|
|
53
|
-
return {
|
|
75
|
+
return {
|
|
76
|
+
"Framework": "codex",
|
|
77
|
+
"cancel": "thread",
|
|
78
|
+
"pause": "interrupt_then_resume_thread",
|
|
79
|
+
"resume": "thread_id",
|
|
80
|
+
"live_interaction": True,
|
|
81
|
+
}
|
|
54
82
|
|
|
55
83
|
|
|
56
84
|
@dataclass
|
|
@@ -62,9 +90,15 @@ class _CodexThread:
|
|
|
62
90
|
pending_approvals: set[str] = field(default_factory=set)
|
|
63
91
|
done: bool = False
|
|
64
92
|
interrupted: bool = False
|
|
93
|
+
paused: bool = False
|
|
94
|
+
started_at: int | None = None
|
|
95
|
+
completed_at: int | None = None
|
|
96
|
+
duration_ms: int | None = None
|
|
97
|
+
goal_mode: bool = False
|
|
98
|
+
continuation_preexisting: bool = False
|
|
65
99
|
|
|
66
100
|
|
|
67
|
-
class
|
|
101
|
+
class CodexRuntimeAdapter(RuntimeAdapter):
|
|
68
102
|
"""Codex 的 RuntimeAdapter(重托管)。"""
|
|
69
103
|
|
|
70
104
|
def __init__(
|
|
@@ -79,6 +113,7 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
79
113
|
self._sandbox_read_only = sandbox_read_only
|
|
80
114
|
self._turn_timeout_seconds = turn_timeout_seconds
|
|
81
115
|
self._threads: dict[str, _CodexThread] = {}
|
|
116
|
+
self._requests: dict[str, StartRequest] = {}
|
|
82
117
|
self._known_threads: set[str] = set()
|
|
83
118
|
self._pending_cancels: set[str] = set()
|
|
84
119
|
# 被杀/interrupt 的 session 不持久化(goal-09 契约 1)。
|
|
@@ -86,6 +121,33 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
86
121
|
# 可观测:最近一次 cancel 级联丢弃的审批集(contract test 断言用)。
|
|
87
122
|
self.last_cancel_dropped_approvals: set[str] = set()
|
|
88
123
|
self._seq = 0
|
|
124
|
+
self._closed = False
|
|
125
|
+
|
|
126
|
+
# ---- capability matrix(v1,诚实声明) ----
|
|
127
|
+
|
|
128
|
+
def capabilities(self) -> RuntimeCapabilityMatrix:
|
|
129
|
+
"""Codex 真实矩阵:thread 级 cancel/pause/resume + 审批 submit + snapshot
|
|
130
|
+
checkpoint 均为后端原生能力;attach/durable_restore 未实现(线程表在本进程,
|
|
131
|
+
attach seam 缺失),steer/inject 无原生通道。
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def _unavailable(reason: str) -> RuntimeCapability:
|
|
135
|
+
return RuntimeCapability(supported=False, mode="unavailable", reason=reason)
|
|
136
|
+
|
|
137
|
+
return RuntimeCapabilityMatrix(
|
|
138
|
+
cancel=RuntimeCapability(supported=True, mode="native"),
|
|
139
|
+
pause=RuntimeCapability(supported=True, mode="native"),
|
|
140
|
+
resume=RuntimeCapability(supported=True, mode="native"),
|
|
141
|
+
submit_interaction=RuntimeCapability(supported=True, mode="native"),
|
|
142
|
+
attach=_unavailable("codex_process_local_thread_table"),
|
|
143
|
+
steer=_unavailable("runtime_no_native_steer"),
|
|
144
|
+
inject=_unavailable("runtime_no_native_inject"),
|
|
145
|
+
checkpoint=RuntimeCapability(supported=True, mode="native"),
|
|
146
|
+
durable_restore=_unavailable("codex_durable_restore_requires_attach_seam"),
|
|
147
|
+
goal=RuntimeCapability(supported=True, mode="native"),
|
|
148
|
+
loop=_unavailable("codex_loop_requires_run_control_spec"),
|
|
149
|
+
plan=RuntimeCapability(supported=True, mode="native"),
|
|
150
|
+
)
|
|
89
151
|
|
|
90
152
|
# ---- 六动词 ----
|
|
91
153
|
|
|
@@ -98,16 +160,36 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
98
160
|
else:
|
|
99
161
|
# 把 model + base_instructions 传给 codex thread(配置契约,见 plan C)
|
|
100
162
|
thread_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
|
|
163
|
+
if request.config:
|
|
164
|
+
for key in ("sandbox", "approval_mode", "summary", "ephemeral"):
|
|
165
|
+
value = request.config.get(key)
|
|
166
|
+
if key == "ephemeral" and value is not None:
|
|
167
|
+
thread_config[key] = bool(value)
|
|
168
|
+
elif value:
|
|
169
|
+
thread_config[key] = value
|
|
101
170
|
if request.model:
|
|
102
171
|
thread_config["model"] = request.model
|
|
103
172
|
base_instructions = request.config.get("base_instructions")
|
|
104
173
|
if base_instructions:
|
|
105
174
|
thread_config["base_instructions"] = base_instructions
|
|
175
|
+
cwd = request.config.get("cwd")
|
|
176
|
+
if cwd:
|
|
177
|
+
thread_config["cwd"] = str(cwd)
|
|
178
|
+
# AgentKernel creates one adapter/transport per durable turn and
|
|
179
|
+
# closes it after the canonical terminal event. The next turn
|
|
180
|
+
# therefore resumes the native thread from a new app-server
|
|
181
|
+
# process; an ephemeral Codex thread has no rollout and cannot be
|
|
182
|
+
# resumed across that transport boundary.
|
|
183
|
+
thread_config.setdefault("ephemeral", False)
|
|
106
184
|
thread_id = await self._client.start_thread(thread_config)
|
|
107
185
|
self._known_threads.add(thread_id)
|
|
108
|
-
thread = _CodexThread(
|
|
186
|
+
thread = _CodexThread(
|
|
187
|
+
thread_id=thread_id,
|
|
188
|
+
continuation_preexisting=bool(provided),
|
|
189
|
+
)
|
|
109
190
|
thread.__dict__["_start_request"] = request
|
|
110
191
|
self._threads[thread_id] = thread
|
|
192
|
+
self._requests[thread_id] = request
|
|
111
193
|
return RunHandle(
|
|
112
194
|
run_id=thread_id,
|
|
113
195
|
session_id=request.session_id,
|
|
@@ -135,21 +217,75 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
135
217
|
thread.pending_approvals.clear()
|
|
136
218
|
# 真实中断:handle.interrupt() 停活跃 turn(真实 SDK 机制;无"杀进程"概念,
|
|
137
219
|
# thread 由 codex 后端托管,interrupt 即终止当前 turn 的执行)。
|
|
138
|
-
|
|
220
|
+
# 先唤醒 Studio 的 stream;等待 request_user_input 时,中断 RPC
|
|
221
|
+
# 可能要等 turn 状态前进,否则取消接口与流会互相等待。
|
|
222
|
+
thread.interrupt_event.set()
|
|
223
|
+
interrupted = (
|
|
224
|
+
await self._client.cancel_goal(thread.thread_id)
|
|
225
|
+
if thread.goal_mode
|
|
226
|
+
else await self._client.interrupt_active_turn(thread.thread_id)
|
|
227
|
+
)
|
|
139
228
|
if not interrupted:
|
|
140
229
|
# 无活跃 handle 可 interrupt(竞态:turn 刚好结束)→ 视为未在运行。
|
|
141
230
|
return CancelResult.NOT_RUNNING
|
|
142
|
-
thread.interrupt_event.set()
|
|
143
231
|
# 被中断的 session 不持久化(goal-09 契约 1)。
|
|
144
232
|
self._do_not_persist.add(thread_id)
|
|
145
233
|
thread.done = True
|
|
146
234
|
self._threads.pop(thread_id, None)
|
|
235
|
+
self._requests.pop(thread_id, None)
|
|
147
236
|
self._pending_cancels.discard(thread_id)
|
|
148
237
|
return CancelResult.INTERRUPTED_ACTIVE_TURN
|
|
149
238
|
except Exception: # noqa: BLE001
|
|
150
239
|
logger.exception("codex cancel thread %s 失败", thread_id)
|
|
151
240
|
return CancelResult.FAILED
|
|
152
241
|
|
|
242
|
+
async def pause(self, handle: RunHandle) -> PauseResult:
|
|
243
|
+
thread = self._threads.get(handle.run_id)
|
|
244
|
+
if thread is None or thread.done or not thread.streaming:
|
|
245
|
+
return PauseResult.NOT_RUNNING
|
|
246
|
+
if thread.pending_approvals:
|
|
247
|
+
# A blocked native approval must be answered through the interaction
|
|
248
|
+
# card. Interrupting while the SDK reader waits on that approval can
|
|
249
|
+
# deadlock the JSON-RPC command channel.
|
|
250
|
+
return PauseResult.FAILED
|
|
251
|
+
try:
|
|
252
|
+
interrupted = (
|
|
253
|
+
await self._client.pause_goal(thread.thread_id)
|
|
254
|
+
if thread.goal_mode
|
|
255
|
+
else await self._client.interrupt_active_turn(thread.thread_id)
|
|
256
|
+
)
|
|
257
|
+
if not interrupted:
|
|
258
|
+
return PauseResult.NOT_RUNNING
|
|
259
|
+
thread.paused = True
|
|
260
|
+
# ``thread/goal/set(status=paused)`` only prevents the next
|
|
261
|
+
# continuation. Wake the adapter stream as well so the current
|
|
262
|
+
# physical goal turn becomes an honest resumable pause now.
|
|
263
|
+
thread.interrupt_event.set()
|
|
264
|
+
return PauseResult.PAUSED_ACTIVE_TURN
|
|
265
|
+
except Exception: # noqa: BLE001
|
|
266
|
+
logger.exception("codex pause thread %s 失败", handle.run_id)
|
|
267
|
+
return PauseResult.FAILED
|
|
268
|
+
|
|
269
|
+
async def submit(self, handle: RunHandle, payload: ResumePayload) -> None:
|
|
270
|
+
if not payload.call_id:
|
|
271
|
+
raise ValueError("Codex live submit requires call_id")
|
|
272
|
+
raw = payload.data if isinstance(payload.data, dict) else {"decision": payload.data}
|
|
273
|
+
if payload.kind == "approval_decision":
|
|
274
|
+
decision = str(raw.get("decision") or raw.get("name") or "")
|
|
275
|
+
resolved = await self._client.resolve_approval(payload.call_id, decision)
|
|
276
|
+
elif payload.kind == "hitl_answer":
|
|
277
|
+
resolved = await self._client.resolve_interaction(
|
|
278
|
+
payload.call_id,
|
|
279
|
+
{key: value for key, value in raw.items() if key != "decision"},
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
raise ValueError("Codex live submit requires approval_decision or hitl_answer")
|
|
283
|
+
if not resolved:
|
|
284
|
+
raise ValueError(f"interaction {payload.call_id!r} is not pending")
|
|
285
|
+
thread = self._threads.get(handle.run_id)
|
|
286
|
+
if thread is not None:
|
|
287
|
+
thread.pending_approvals.discard(payload.call_id)
|
|
288
|
+
|
|
153
289
|
async def resume(
|
|
154
290
|
self,
|
|
155
291
|
handle: RunHandle,
|
|
@@ -158,16 +294,35 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
158
294
|
) -> RunHandle:
|
|
159
295
|
# resume 用 thread id 语义(resume_thread_id),不套 ADK invocation 模型。
|
|
160
296
|
if target.kind != "thread_id":
|
|
161
|
-
raise ValueError(
|
|
297
|
+
raise ValueError(
|
|
298
|
+
f"CodexRuntimeAdapter resume 仅支持 thread_id 目标,得到 {target.kind!r}"
|
|
299
|
+
)
|
|
162
300
|
if handle.run_id in self._do_not_persist:
|
|
163
301
|
raise ValueError(f"thread {handle.run_id} 已被中断/杀进程,不持久化,不可 resume")
|
|
164
302
|
self._pending_cancels.discard(handle.run_id)
|
|
165
303
|
self._known_threads.add(target.id)
|
|
166
|
-
thread = _CodexThread(
|
|
304
|
+
thread = _CodexThread(
|
|
305
|
+
thread_id=target.id,
|
|
306
|
+
continuation_preexisting=True,
|
|
307
|
+
)
|
|
167
308
|
thread.__dict__["_resume"] = {"target": target, "payload": payload}
|
|
309
|
+
request = self._requests.get(handle.run_id)
|
|
310
|
+
if request is not None:
|
|
311
|
+
thread.__dict__["_request_config"] = request
|
|
312
|
+
if request is not None and str(request.config.get("goal_objective") or "").strip():
|
|
313
|
+
# Restart the same native Goal operation on the persisted thread;
|
|
314
|
+
# its transcript remains in Codex, while the objective/config are
|
|
315
|
+
# needed to resume Goal rather than issuing a normal text turn.
|
|
316
|
+
thread.goal_mode = True
|
|
168
317
|
self._threads[handle.run_id] = thread
|
|
169
318
|
# 真实恢复:thread_resume 接入后端既有 thread(thread_id 语义,不套 ADK invocation)。
|
|
170
|
-
|
|
319
|
+
resume_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
|
|
320
|
+
if request is not None and request.config:
|
|
321
|
+
for key in ("sandbox", "approval_mode"):
|
|
322
|
+
value = request.config.get(key)
|
|
323
|
+
if value:
|
|
324
|
+
resume_config[key] = value
|
|
325
|
+
await self._client.resume_thread(target.id, resume_config)
|
|
171
326
|
handle.native_ref["thread_id"] = target.id
|
|
172
327
|
handle.native_ref["resume_thread_id"] = target.id
|
|
173
328
|
handle.native_ref["resume_payload"] = payload.data if payload else None
|
|
@@ -198,12 +353,17 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
198
353
|
)
|
|
199
354
|
|
|
200
355
|
async def close(self, handle: RunHandle) -> None:
|
|
356
|
+
if self._closed:
|
|
357
|
+
return
|
|
358
|
+
self._closed = True
|
|
201
359
|
thread = self._threads.pop(handle.run_id, None)
|
|
202
|
-
|
|
360
|
+
self._requests.pop(handle.run_id, None)
|
|
361
|
+
active = thread is not None and thread.streaming and not thread.done
|
|
362
|
+
if active:
|
|
203
363
|
thread.interrupt_event.set()
|
|
204
364
|
try:
|
|
205
|
-
|
|
206
|
-
|
|
365
|
+
if active:
|
|
366
|
+
await self._client.interrupt_active_turn(thread.thread_id)
|
|
207
367
|
finally:
|
|
208
368
|
# AsyncCodex.close owns terminate/wait/kill for the app-server child.
|
|
209
369
|
await self._client.close()
|
|
@@ -229,34 +389,31 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
229
389
|
|
|
230
390
|
if handle.run_id in self._pending_cancels:
|
|
231
391
|
self._pending_cancels.discard(handle.run_id)
|
|
232
|
-
yield self.
|
|
392
|
+
yield self._make_run_canceled(
|
|
233
393
|
handle,
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
"status": "cancelled",
|
|
237
|
-
"cancel_result": CancelResult.PENDING_CANCEL_RECORDED.value,
|
|
238
|
-
},
|
|
394
|
+
reason=f"pending_cancel:{CancelResult.PENDING_CANCEL_RECORDED.value}",
|
|
239
395
|
)
|
|
240
396
|
return
|
|
241
397
|
|
|
242
|
-
yield self._event(handle, EventType.RUN_STARTED, {"status": "in_progress"})
|
|
243
|
-
tracker = CodexPhaseTracker()
|
|
244
398
|
request = thread.__dict__.get("_start_request")
|
|
245
399
|
resume_state = thread.__dict__.get("_resume")
|
|
246
400
|
if request is not None:
|
|
247
|
-
prompt = request
|
|
401
|
+
prompt = _request_prompt(request)
|
|
248
402
|
elif resume_state is not None:
|
|
249
403
|
prompt = _resume_prompt(resume_state.get("payload"))
|
|
250
404
|
else:
|
|
251
405
|
prompt = ""
|
|
406
|
+
run_input = _build_run_input(request, prompt)
|
|
252
407
|
thread.streaming = True
|
|
253
408
|
thread.turn_id = thread.turn_id or f"turn_{thread.thread_id}"
|
|
254
409
|
try:
|
|
255
|
-
async for event in self._map_codex_stream(handle, thread,
|
|
410
|
+
async for event in self._map_codex_stream(handle, thread, run_input):
|
|
256
411
|
yield event
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
412
|
+
except asyncio.CancelledError:
|
|
413
|
+
thread.interrupted = True
|
|
414
|
+
self._do_not_persist.add(handle.run_id)
|
|
415
|
+
await self._client.interrupt_active_turn(thread.thread_id)
|
|
416
|
+
raise
|
|
260
417
|
except TimeoutError:
|
|
261
418
|
self.last_cancel_dropped_approvals = set(thread.pending_approvals)
|
|
262
419
|
thread.pending_approvals.clear()
|
|
@@ -264,18 +421,10 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
264
421
|
# Closing the SDK transport terminates and waits for the app-server
|
|
265
422
|
# child even when the stream is stuck between notifications.
|
|
266
423
|
await self._client.close()
|
|
267
|
-
yield self.
|
|
268
|
-
|
|
269
|
-
EventType.RUN_FAILED,
|
|
270
|
-
{"status": "failed", "error": "codex turn timed out"},
|
|
271
|
-
)
|
|
272
|
-
except Exception as exc: # noqa: BLE001 通用兜底:任何异常都发 RUN_FAILED
|
|
424
|
+
yield self._make_run_failed(handle, "codex turn timed out")
|
|
425
|
+
except Exception as exc: # noqa: BLE001 通用兜底:任何异常都发 RunFailed
|
|
273
426
|
self._do_not_persist.add(handle.run_id)
|
|
274
|
-
yield self.
|
|
275
|
-
handle,
|
|
276
|
-
EventType.RUN_FAILED,
|
|
277
|
-
{"status": "failed", "error": str(exc)},
|
|
278
|
-
)
|
|
427
|
+
yield self._make_run_failed(handle, str(exc))
|
|
279
428
|
finally:
|
|
280
429
|
thread.streaming = False
|
|
281
430
|
thread.done = True
|
|
@@ -285,19 +434,50 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
285
434
|
self,
|
|
286
435
|
handle: RunHandle,
|
|
287
436
|
thread: _CodexThread,
|
|
288
|
-
tracker: CodexPhaseTracker,
|
|
289
437
|
prompt: Any,
|
|
290
438
|
) -> AsyncIterator[RuntimeEvent]:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
439
|
+
request = thread.__dict__.get("_start_request") or thread.__dict__.get("_request_config")
|
|
440
|
+
adapter = CodexEventAdapter(
|
|
441
|
+
known_thread_ids=(thread.thread_id,)
|
|
442
|
+
if thread.continuation_preexisting
|
|
443
|
+
else (),
|
|
295
444
|
)
|
|
445
|
+
context = CodexAdapterContext(run_id=self._event_run_id(handle))
|
|
446
|
+
run_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
|
|
447
|
+
if request is not None and request.config:
|
|
448
|
+
for key in ("sandbox", "approval_mode", "summary", "collaboration_mode"):
|
|
449
|
+
value = request.config.get(key)
|
|
450
|
+
if key == "approval_mode" and value == "manual":
|
|
451
|
+
continue
|
|
452
|
+
if value:
|
|
453
|
+
run_config[key] = value
|
|
454
|
+
if request is not None and request.model:
|
|
455
|
+
run_config["model"] = request.model
|
|
456
|
+
goal_objective = str(
|
|
457
|
+
request.config.get("goal_objective") or ""
|
|
458
|
+
if request is not None and request.config
|
|
459
|
+
else ""
|
|
460
|
+
).strip()
|
|
461
|
+
if goal_objective:
|
|
462
|
+
thread.goal_mode = True
|
|
463
|
+
codex_gen = self._client.run_goal(
|
|
464
|
+
thread.thread_id,
|
|
465
|
+
goal_objective,
|
|
466
|
+
config=run_config,
|
|
467
|
+
)
|
|
468
|
+
else:
|
|
469
|
+
codex_gen = self._client.run_turn(
|
|
470
|
+
thread.thread_id,
|
|
471
|
+
prompt,
|
|
472
|
+
config=run_config,
|
|
473
|
+
)
|
|
296
474
|
deadline = (
|
|
297
475
|
asyncio.get_running_loop().time() + self._turn_timeout_seconds
|
|
298
476
|
if self._turn_timeout_seconds is not None
|
|
299
477
|
else None
|
|
300
478
|
)
|
|
479
|
+
chunk_task: asyncio.Task[Any] | None = None
|
|
480
|
+
interrupt_task: asyncio.Task[bool] | None = None
|
|
301
481
|
try:
|
|
302
482
|
while True:
|
|
303
483
|
chunk_task = asyncio.ensure_future(_anext_or_stop(codex_gen))
|
|
@@ -321,23 +501,116 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
321
501
|
await self._client.close()
|
|
322
502
|
await asyncio.gather(chunk_task, interrupt_task, return_exceptions=True)
|
|
323
503
|
raise TimeoutError("codex turn timed out")
|
|
324
|
-
for task in pending:
|
|
325
|
-
task.cancel()
|
|
326
504
|
if interrupt_task in done:
|
|
327
|
-
|
|
505
|
+
# ``openai-codex`` implements ``next_turn_notification``
|
|
506
|
+
# with ``asyncio.to_thread(queue.get)``. Cancelling that
|
|
507
|
+
# awaiter unregisters its queue while the worker thread is
|
|
508
|
+
# still blocked, so neither a later transport close nor
|
|
509
|
+
# event-loop teardown can wake it. After interrupting a
|
|
510
|
+
# turn, keep the queue registered and drain the SDK's
|
|
511
|
+
# terminal notifications instead. If the backend fails to
|
|
512
|
+
# finish promptly, close the transport *before* awaiting
|
|
513
|
+
# the waiter so MessageRouter.fail_all can release it.
|
|
514
|
+
drain_deadline = (
|
|
515
|
+
asyncio.get_running_loop().time()
|
|
516
|
+
+ _INTERRUPT_DRAIN_TIMEOUT_SECONDS
|
|
517
|
+
)
|
|
518
|
+
while True:
|
|
519
|
+
drain_remaining = max(
|
|
520
|
+
0.0,
|
|
521
|
+
drain_deadline - asyncio.get_running_loop().time(),
|
|
522
|
+
)
|
|
523
|
+
drained, _ = await asyncio.wait(
|
|
524
|
+
{chunk_task},
|
|
525
|
+
timeout=drain_remaining,
|
|
526
|
+
)
|
|
527
|
+
if not drained:
|
|
528
|
+
await self._client.close()
|
|
529
|
+
await asyncio.gather(chunk_task, return_exceptions=True)
|
|
530
|
+
break
|
|
531
|
+
try:
|
|
532
|
+
drained_chunk = chunk_task.result()
|
|
533
|
+
except Exception: # noqa: BLE001
|
|
534
|
+
break
|
|
535
|
+
if drained_chunk is _STREAM_STOP:
|
|
536
|
+
break
|
|
537
|
+
chunk_task = asyncio.ensure_future(_anext_or_stop(codex_gen))
|
|
538
|
+
chunk_task = None
|
|
328
539
|
thread.interrupted = True
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
|
|
540
|
+
# Runtime interrupt (user pause) — adapter doesn't know;
|
|
541
|
+
# emit canonical RunInterrupted explicitly.
|
|
542
|
+
yield self._make_run_interrupted(
|
|
543
|
+
handle,
|
|
544
|
+
reason="user_pause" if thread.paused else "runtime_interrupt",
|
|
332
545
|
)
|
|
333
546
|
return
|
|
547
|
+
for task in pending:
|
|
548
|
+
task.cancel()
|
|
549
|
+
if pending:
|
|
550
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
334
551
|
chunk = chunk_task.result()
|
|
335
552
|
if chunk is _STREAM_STOP:
|
|
336
553
|
return
|
|
337
|
-
event
|
|
338
|
-
|
|
554
|
+
# TODO(runtime-event-v2): use real native cursor from chunk if
|
|
555
|
+
# available; fallback to thread:seq for now.
|
|
556
|
+
native_cursor = f"{thread.thread_id}:{self._next_seq()}"
|
|
557
|
+
# autoApprovalReview 不产生 canonical 事件(adapter 静默),但
|
|
558
|
+
# cancel 级联丢弃审批的契约依赖 runtime 的 pending 跟踪。
|
|
559
|
+
chunk_method = (
|
|
560
|
+
str((chunk or {}).get("method") or "")
|
|
561
|
+
if isinstance(chunk, dict)
|
|
562
|
+
else ""
|
|
563
|
+
)
|
|
564
|
+
if chunk_method in {
|
|
565
|
+
"item/autoApprovalReview/started",
|
|
566
|
+
"item/autoApprovalReview/completed",
|
|
567
|
+
}:
|
|
568
|
+
review_params = chunk.get("params") or {}
|
|
569
|
+
review_id = str(
|
|
570
|
+
review_params.get("reviewId")
|
|
571
|
+
or review_params.get("review_id")
|
|
572
|
+
or ""
|
|
573
|
+
)
|
|
574
|
+
if review_id:
|
|
575
|
+
if chunk_method.endswith("started"):
|
|
576
|
+
thread.pending_approvals.add(review_id)
|
|
577
|
+
else:
|
|
578
|
+
thread.pending_approvals.discard(review_id)
|
|
579
|
+
for event in adapter.map_protocol_message(
|
|
580
|
+
chunk,
|
|
581
|
+
context,
|
|
582
|
+
native_cursor=native_cursor,
|
|
583
|
+
timestamp=time.time(),
|
|
584
|
+
):
|
|
585
|
+
event = self._with_caller_scope(event, request)
|
|
586
|
+
# 跟踪 pending 审批(cancel 级联丢弃契约依赖该集合)。
|
|
587
|
+
if isinstance(event, InteractionRequested):
|
|
588
|
+
if event.interaction_id:
|
|
589
|
+
thread.pending_approvals.add(event.interaction_id)
|
|
590
|
+
call_id = getattr(event.request, "call_id", None)
|
|
591
|
+
if call_id:
|
|
592
|
+
thread.pending_approvals.add(str(call_id))
|
|
593
|
+
elif isinstance(event, InteractionResolved):
|
|
594
|
+
thread.pending_approvals.discard(event.interaction_id)
|
|
595
|
+
call_id = getattr(event.response, "call_id", None)
|
|
596
|
+
if call_id:
|
|
597
|
+
thread.pending_approvals.discard(str(call_id))
|
|
598
|
+
if isinstance(event, (RunCompleted, RunFailed, RunCanceled)):
|
|
599
|
+
# The Kernel stops consuming as soon as it persists a
|
|
600
|
+
# canonical terminal fact, so generator ``finally`` may
|
|
601
|
+
# not run before worker cleanup calls ``close``. Mark the
|
|
602
|
+
# native turn terminal before yielding that fact; close
|
|
603
|
+
# must terminate the transport without sending a stale
|
|
604
|
+
# turn/interrupt RPC to an already-completed app-server.
|
|
605
|
+
thread.done = True
|
|
339
606
|
yield event
|
|
340
607
|
finally:
|
|
608
|
+
waiter_tasks = [task for task in (chunk_task, interrupt_task) if task is not None]
|
|
609
|
+
for task in waiter_tasks:
|
|
610
|
+
if not task.done():
|
|
611
|
+
task.cancel()
|
|
612
|
+
if waiter_tasks:
|
|
613
|
+
await asyncio.gather(*waiter_tasks, return_exceptions=True)
|
|
341
614
|
aclose = getattr(codex_gen, "aclose", None)
|
|
342
615
|
if callable(aclose):
|
|
343
616
|
try:
|
|
@@ -345,88 +618,177 @@ class CodexRuntime(RuntimeAdapter):
|
|
|
345
618
|
except Exception: # noqa: BLE001
|
|
346
619
|
pass
|
|
347
620
|
|
|
348
|
-
|
|
621
|
+
# ---- canonical run.* helpers (for runtime-owned lifecycle) ----
|
|
622
|
+
|
|
623
|
+
def _event_run_id(self, handle: RunHandle) -> str:
|
|
624
|
+
"""事件的 canonical run_id:调用方 invocation_id 优先,退回 thread id。
|
|
625
|
+
|
|
626
|
+
``handle.run_id`` 是 codex 原生 thread id(resume/cancel 按 thread 寻址);
|
|
627
|
+
但 canonical RuntimeEvent 的 run_id 必须与调用方
|
|
628
|
+
``StartRequest.metadata['invocation_id']`` 一致(conversation kernel 的
|
|
629
|
+
event scope 校验),否则 hosted/web 执行路径会在首个事件上 fail。
|
|
630
|
+
"""
|
|
631
|
+
request = self._requests.get(handle.run_id)
|
|
632
|
+
if request is not None:
|
|
633
|
+
invocation_id = str(
|
|
634
|
+
(getattr(request, "metadata", None) or {}).get("invocation_id") or ""
|
|
635
|
+
).strip()
|
|
636
|
+
if invocation_id:
|
|
637
|
+
return invocation_id
|
|
638
|
+
return handle.run_id
|
|
639
|
+
|
|
640
|
+
def _make_source(self, handle: RunHandle) -> SourceRef:
|
|
641
|
+
request = self._requests.get(handle.run_id)
|
|
642
|
+
return SourceRef(
|
|
643
|
+
framework="codex",
|
|
644
|
+
native_run_id=handle.run_id,
|
|
645
|
+
metadata={
|
|
646
|
+
"agent_id": (
|
|
647
|
+
str(request.agent_id or "codex") if request is not None else "codex"
|
|
648
|
+
),
|
|
649
|
+
"user_id": (
|
|
650
|
+
request.user_id
|
|
651
|
+
if request is not None
|
|
652
|
+
else str(handle.native_ref.get("user_id") or "user")
|
|
653
|
+
),
|
|
654
|
+
"session_id": handle.session_id,
|
|
655
|
+
"invocation_id": (
|
|
656
|
+
str(request.metadata.get("invocation_id") or handle.run_id)
|
|
657
|
+
if request is not None
|
|
658
|
+
else handle.run_id
|
|
659
|
+
),
|
|
660
|
+
},
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
def _with_caller_scope(self, event: RuntimeEvent, request: Any) -> RuntimeEvent:
|
|
664
|
+
"""把调用方 scope(request 的 agent/user/session/invocation)并入事件 source。"""
|
|
665
|
+
|
|
666
|
+
if request is None:
|
|
667
|
+
return event
|
|
668
|
+
caller_scope = {
|
|
669
|
+
"agent_id": str(getattr(request, "agent_id", "") or "codex"),
|
|
670
|
+
"user_id": str(getattr(request, "user_id", "") or "user"),
|
|
671
|
+
"session_id": str(getattr(request, "session_id", "") or ""),
|
|
672
|
+
"invocation_id": str(
|
|
673
|
+
(getattr(request, "metadata", None) or {}).get("invocation_id")
|
|
674
|
+
or ""
|
|
675
|
+
),
|
|
676
|
+
}
|
|
677
|
+
merged = {**caller_scope, **dict(event.source.metadata or {})}
|
|
678
|
+
# adapter 自身字段优先;仅补齐缺失的调用方 scope 键。
|
|
679
|
+
for key, value in caller_scope.items():
|
|
680
|
+
if not merged.get(key):
|
|
681
|
+
merged[key] = value
|
|
682
|
+
source = event.source.model_copy(update={"metadata": merged})
|
|
683
|
+
return event.model_copy(update={"source": source})
|
|
684
|
+
|
|
685
|
+
def _canonical_kwargs(
|
|
349
686
|
self,
|
|
350
687
|
handle: RunHandle,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
688
|
+
*,
|
|
689
|
+
scope_id: str,
|
|
690
|
+
item_id: str,
|
|
691
|
+
event_type: str,
|
|
692
|
+
part_id: str,
|
|
693
|
+
) -> dict[str, Any]:
|
|
694
|
+
framework = "codex"
|
|
695
|
+
run_id = self._event_run_id(handle)
|
|
696
|
+
n = self._next_seq()
|
|
697
|
+
return {
|
|
698
|
+
"schema_version": 2,
|
|
699
|
+
"event_id": stable_event_id(
|
|
700
|
+
framework, scope_id, item_id, event_type, part_id, run_id, n
|
|
701
|
+
),
|
|
702
|
+
"seq": n,
|
|
703
|
+
"timestamp": time.time(),
|
|
704
|
+
"run_id": run_id,
|
|
705
|
+
"scope_id": scope_id,
|
|
706
|
+
"source": self._make_source(handle),
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
def _make_run_canceled(
|
|
710
|
+
self, handle: RunHandle, *, reason: str | None = None
|
|
711
|
+
) -> RunCanceled:
|
|
712
|
+
framework = "codex"
|
|
713
|
+
run_id = self._event_run_id(handle)
|
|
714
|
+
scope_id = stable_scope_id(framework, run_id)
|
|
715
|
+
item_id = stable_item_id(framework, run_id, "$run")
|
|
716
|
+
return RunCanceled(
|
|
717
|
+
**self._canonical_kwargs(
|
|
372
718
|
handle,
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
review_id = str(params.get("review_id") or params.get("reviewId") or "")
|
|
392
|
-
thread.pending_approvals.discard(review_id)
|
|
393
|
-
return None
|
|
394
|
-
if (
|
|
395
|
-
"approval" in method
|
|
396
|
-
or "requestPermission" in method
|
|
397
|
-
or "approval" in str(chunk.get("type") or "")
|
|
398
|
-
):
|
|
399
|
-
call_id = str(
|
|
400
|
-
params.get("id") or params.get("call_id") or params.get("requestId") or ""
|
|
401
|
-
)
|
|
402
|
-
if call_id:
|
|
403
|
-
thread.pending_approvals.add(call_id)
|
|
404
|
-
return self._event(
|
|
719
|
+
scope_id=scope_id,
|
|
720
|
+
item_id=item_id,
|
|
721
|
+
event_type="run.canceled",
|
|
722
|
+
part_id="run",
|
|
723
|
+
),
|
|
724
|
+
status="canceled",
|
|
725
|
+
reason=reason,
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
def _make_run_interrupted(
|
|
729
|
+
self, handle: RunHandle, *, reason: str | None = None
|
|
730
|
+
) -> RunInterrupted:
|
|
731
|
+
framework = "codex"
|
|
732
|
+
run_id = self._event_run_id(handle)
|
|
733
|
+
scope_id = stable_scope_id(framework, run_id)
|
|
734
|
+
item_id = stable_item_id(framework, run_id, "$run")
|
|
735
|
+
return RunInterrupted(
|
|
736
|
+
**self._canonical_kwargs(
|
|
405
737
|
handle,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
738
|
+
scope_id=scope_id,
|
|
739
|
+
item_id=item_id,
|
|
740
|
+
event_type="run.interrupted",
|
|
741
|
+
part_id="run",
|
|
742
|
+
),
|
|
743
|
+
status="interrupted",
|
|
744
|
+
reason=reason,
|
|
745
|
+
)
|
|
410
746
|
|
|
411
|
-
def
|
|
412
|
-
self,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
747
|
+
def _make_run_failed(
|
|
748
|
+
self, handle: RunHandle, error_message: str
|
|
749
|
+
) -> RunFailed:
|
|
750
|
+
framework = "codex"
|
|
751
|
+
run_id = self._event_run_id(handle)
|
|
752
|
+
scope_id = stable_scope_id(framework, run_id)
|
|
753
|
+
item_id = stable_item_id(framework, run_id, "$run")
|
|
754
|
+
return RunFailed(
|
|
755
|
+
**self._canonical_kwargs(
|
|
756
|
+
handle,
|
|
757
|
+
scope_id=scope_id,
|
|
758
|
+
item_id=item_id,
|
|
759
|
+
event_type="run.failed",
|
|
760
|
+
part_id="run",
|
|
761
|
+
),
|
|
762
|
+
status="failed",
|
|
763
|
+
error=ErrorInfo(
|
|
764
|
+
code="codex_runtime_failed",
|
|
765
|
+
message=error_message,
|
|
766
|
+
source="codex",
|
|
767
|
+
scope_id=scope_id,
|
|
768
|
+
source_ref=self._make_source(handle),
|
|
769
|
+
),
|
|
428
770
|
)
|
|
429
771
|
|
|
772
|
+
@staticmethod
|
|
773
|
+
def _mcp_result_text(result: dict[str, Any]) -> str:
|
|
774
|
+
"""把 MCP result.content 列表提取为可读文本;无文本时退化 structuredContent。"""
|
|
775
|
+
if not result:
|
|
776
|
+
return ""
|
|
777
|
+
content = result.get("content")
|
|
778
|
+
if isinstance(content, list):
|
|
779
|
+
texts = [
|
|
780
|
+
str(item.get("text") or "")
|
|
781
|
+
for item in content
|
|
782
|
+
if isinstance(item, dict) and item.get("type") == "text"
|
|
783
|
+
]
|
|
784
|
+
texts = [text for text in texts if text]
|
|
785
|
+
if texts:
|
|
786
|
+
return "\n".join(texts)
|
|
787
|
+
structured = result.get("structuredContent", result.get("structured_content"))
|
|
788
|
+
if structured is not None:
|
|
789
|
+
return json.dumps(structured, ensure_ascii=False)
|
|
790
|
+
return ""
|
|
791
|
+
|
|
430
792
|
|
|
431
793
|
_STREAM_STOP = object()
|
|
432
794
|
|
|
@@ -446,4 +808,235 @@ def _resume_prompt(payload: Optional[ResumePayload]) -> Any:
|
|
|
446
808
|
return json.dumps(payload.data, ensure_ascii=False, sort_keys=True)
|
|
447
809
|
|
|
448
810
|
|
|
449
|
-
|
|
811
|
+
def _coerce_prompt_text(value: Any) -> Any:
|
|
812
|
+
"""把 canonical message 形态的 input 压成 SDK 可接受的文本。
|
|
813
|
+
|
|
814
|
+
``openai-codex`` 0.147 的 run input 只接受 TextInput/str;请求侧没有
|
|
815
|
+
conversation preprocessing 时 ``request.input`` 可能是
|
|
816
|
+
``[{role, content}]`` 历史列表,直接透传会 ``unsupported input item``。
|
|
817
|
+
"""
|
|
818
|
+
if isinstance(value, str) or value is None:
|
|
819
|
+
return value
|
|
820
|
+
if isinstance(value, dict):
|
|
821
|
+
content = value.get("content") if "role" in value else value.get("text")
|
|
822
|
+
if isinstance(content, str):
|
|
823
|
+
return content
|
|
824
|
+
if isinstance(content, dict):
|
|
825
|
+
text = content.get("text") or content.get("content")
|
|
826
|
+
if isinstance(text, str):
|
|
827
|
+
return text
|
|
828
|
+
return str(value)
|
|
829
|
+
if isinstance(value, list):
|
|
830
|
+
texts = [
|
|
831
|
+
text
|
|
832
|
+
for text in (_coerce_prompt_text(item) for item in value)
|
|
833
|
+
if isinstance(text, str) and text
|
|
834
|
+
]
|
|
835
|
+
return "\n".join(texts) if texts else str(value)
|
|
836
|
+
return str(value)
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def _request_prompt(request: StartRequest) -> Any:
|
|
840
|
+
"""Render canonical conversation history for a native Codex turn.
|
|
841
|
+
|
|
842
|
+
只传 user 消息历史 + 当前 input,不传 assistant 的完整回复(避免 codex 复述历史)。
|
|
843
|
+
assistant 回复用简短摘要代替,仅维持对话结构。
|
|
844
|
+
"""
|
|
845
|
+
# A resumed Codex thread already owns its transcript. Re-sending Studio's
|
|
846
|
+
# transport-neutral history would duplicate every prior turn after refresh.
|
|
847
|
+
if str(request.metadata.get("thread_id") or "").strip():
|
|
848
|
+
return (
|
|
849
|
+
request.input
|
|
850
|
+
if _is_structured_turn_input(request.input)
|
|
851
|
+
else _coerce_prompt_text(request.input)
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
conversation = request.conversation_preprocessing()
|
|
855
|
+
if conversation is None or not conversation.messages:
|
|
856
|
+
# Keep native text/image/mention parts intact for _build_run_input().
|
|
857
|
+
# Flattening this list turns an image dict into user-visible text.
|
|
858
|
+
return (
|
|
859
|
+
request.input
|
|
860
|
+
if _is_structured_turn_input(request.input)
|
|
861
|
+
else _coerce_prompt_text(request.input)
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
lines: list[str] = []
|
|
865
|
+
for message in conversation.messages:
|
|
866
|
+
content = message.get("content")
|
|
867
|
+
if not isinstance(content, str):
|
|
868
|
+
continue
|
|
869
|
+
role = str(message.get("role") or "user").strip().lower()
|
|
870
|
+
if role == "assistant":
|
|
871
|
+
# 不传完整回复,只标注"已回复",避免 codex 复述
|
|
872
|
+
summary = content[:80] + ("..." if len(content) > 80 else "")
|
|
873
|
+
lines.append(f"[上一轮已回复: {summary}]")
|
|
874
|
+
elif role == "user":
|
|
875
|
+
lines.append(f"User: {content}")
|
|
876
|
+
return "\n".join(lines) or request.input
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def _is_structured_turn_input(value: Any) -> bool:
|
|
880
|
+
return isinstance(value, list) and any(
|
|
881
|
+
isinstance(item, dict) and isinstance(item.get("type"), str)
|
|
882
|
+
for item in value
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
|
|
887
|
+
"""Compose Codex skills and native text/image/mention turn input."""
|
|
888
|
+
try:
|
|
889
|
+
from openai_codex import ImageInput, LocalImageInput, MentionInput, SkillInput, TextInput
|
|
890
|
+
except ImportError:
|
|
891
|
+
return prompt
|
|
892
|
+
skills: list[SkillInput] = []
|
|
893
|
+
if request is not None:
|
|
894
|
+
raw = request.config.get("skills") if request.config else None
|
|
895
|
+
if isinstance(raw, list):
|
|
896
|
+
for item in raw:
|
|
897
|
+
if not isinstance(item, dict):
|
|
898
|
+
continue
|
|
899
|
+
name = str(item.get("name") or "").strip()
|
|
900
|
+
path = str(item.get("path") or "").strip()
|
|
901
|
+
if name and path:
|
|
902
|
+
skills.append(SkillInput(name=name, path=path))
|
|
903
|
+
native_items: list[Any] = []
|
|
904
|
+
raw_input = request.input if request is not None else prompt
|
|
905
|
+
if isinstance(raw_input, list):
|
|
906
|
+
text_replaced = False
|
|
907
|
+
for item in raw_input:
|
|
908
|
+
if not isinstance(item, dict):
|
|
909
|
+
continue
|
|
910
|
+
kind = str(item.get("type") or "")
|
|
911
|
+
if not kind and "role" in item:
|
|
912
|
+
# canonical conversation message({role, content});当前 input
|
|
913
|
+
# 已由 prompt(或 conversation preprocessing)承载,跳过历史项。
|
|
914
|
+
continue
|
|
915
|
+
if kind in {"text", "input_text"}:
|
|
916
|
+
text = str(
|
|
917
|
+
prompt
|
|
918
|
+
if not text_replaced and isinstance(prompt, str)
|
|
919
|
+
else item.get("text") or ""
|
|
920
|
+
)
|
|
921
|
+
text_replaced = True
|
|
922
|
+
if text:
|
|
923
|
+
native_items.append(TextInput(text=text))
|
|
924
|
+
elif kind in {"image", "input_image"} and (
|
|
925
|
+
item.get("url") or item.get("image_url")
|
|
926
|
+
):
|
|
927
|
+
native_items.append(
|
|
928
|
+
ImageInput(url=str(item.get("url") or item.get("image_url")))
|
|
929
|
+
)
|
|
930
|
+
elif kind == "localImage" and item.get("path"):
|
|
931
|
+
native_items.append(LocalImageInput(path=str(item["path"])))
|
|
932
|
+
elif kind == "input_file" and (
|
|
933
|
+
item.get("file_data")
|
|
934
|
+
or str(item.get("file_url") or "").startswith("data:")
|
|
935
|
+
):
|
|
936
|
+
file_path = _materialize_inline_file(
|
|
937
|
+
str(item.get("file_data") or item.get("file_url")),
|
|
938
|
+
str(item.get("filename") or "attachment"),
|
|
939
|
+
)
|
|
940
|
+
if file_path is not None:
|
|
941
|
+
# App Server's ``mention`` input is presentation metadata:
|
|
942
|
+
# current Codex versions do not include it in the model's
|
|
943
|
+
# user message. Always add an explicit model-visible
|
|
944
|
+
# attachment context as well. Small textual files are
|
|
945
|
+
# inlined deterministically; binary/large files expose a
|
|
946
|
+
# sandbox-readable path that Codex can inspect with tools.
|
|
947
|
+
native_items.append(
|
|
948
|
+
TextInput(text=_attachment_context_text(file_path, item))
|
|
949
|
+
)
|
|
950
|
+
native_items.append(
|
|
951
|
+
MentionInput(name=file_path.name, path=str(file_path))
|
|
952
|
+
)
|
|
953
|
+
elif kind == "mention" and item.get("path"):
|
|
954
|
+
native_items.append(
|
|
955
|
+
MentionInput(
|
|
956
|
+
name=str(item.get("name") or item["path"]),
|
|
957
|
+
path=str(item["path"]),
|
|
958
|
+
)
|
|
959
|
+
)
|
|
960
|
+
else:
|
|
961
|
+
text = prompt if isinstance(prompt, str) else str(prompt or "")
|
|
962
|
+
if text:
|
|
963
|
+
native_items.append(TextInput(text=text))
|
|
964
|
+
combined = [*skills, *native_items]
|
|
965
|
+
if not combined:
|
|
966
|
+
return prompt
|
|
967
|
+
if len(combined) == 1 and isinstance(combined[0], TextInput) and not skills:
|
|
968
|
+
return combined[0].text
|
|
969
|
+
return combined
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def _materialize_inline_file(data_url: str, filename: str) -> Path | None:
|
|
973
|
+
"""Materialize a bounded Studio inline attachment for native Codex."""
|
|
974
|
+
|
|
975
|
+
match = re.fullmatch(r"data:([^;,]+)?;base64,([A-Za-z0-9+/=\s]+)", data_url)
|
|
976
|
+
encoded = match.group(2) if match is not None else data_url.strip()
|
|
977
|
+
try:
|
|
978
|
+
payload = base64.b64decode(encoded, validate=True)
|
|
979
|
+
except (ValueError, binascii.Error):
|
|
980
|
+
return None
|
|
981
|
+
if not payload or len(payload) > 10 * 1024 * 1024:
|
|
982
|
+
return None
|
|
983
|
+
safe_name = re.sub(r"[^A-Za-z0-9._-]+", "-", Path(filename).name).strip(".-")
|
|
984
|
+
safe_name = safe_name[:120] or "attachment"
|
|
985
|
+
digest = hashlib.sha256(payload).hexdigest()
|
|
986
|
+
path = Path("/tmp/ksadk-codex-attachments") / digest / safe_name
|
|
987
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
988
|
+
if not path.exists():
|
|
989
|
+
path.write_bytes(payload)
|
|
990
|
+
return path
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
_MAX_INLINE_ATTACHMENT_TEXT_BYTES = 64 * 1024
|
|
994
|
+
_TEXT_ATTACHMENT_SUFFIXES = {
|
|
995
|
+
".csv",
|
|
996
|
+
".html",
|
|
997
|
+
".htm",
|
|
998
|
+
".ini",
|
|
999
|
+
".json",
|
|
1000
|
+
".jsonl",
|
|
1001
|
+
".log",
|
|
1002
|
+
".md",
|
|
1003
|
+
".py",
|
|
1004
|
+
".rst",
|
|
1005
|
+
".toml",
|
|
1006
|
+
".tsv",
|
|
1007
|
+
".txt",
|
|
1008
|
+
".xml",
|
|
1009
|
+
".yaml",
|
|
1010
|
+
".yml",
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
def _attachment_context_text(path: Path, item: Mapping[str, Any]) -> str:
|
|
1015
|
+
"""Build model-visible context for a materialized Responses input file."""
|
|
1016
|
+
|
|
1017
|
+
name = str(item.get("filename") or path.name).replace('"', "'")
|
|
1018
|
+
inline_data = item.get("inlineData")
|
|
1019
|
+
inline_mime = inline_data.get("mimeType") if isinstance(inline_data, Mapping) else None
|
|
1020
|
+
mime_type = str(item.get("mime_type") or inline_mime or "").strip().lower()
|
|
1021
|
+
source = str(item.get("file_data") or item.get("file_url") or "")
|
|
1022
|
+
data_url_match = re.match(r"data:([^;,]+)", source)
|
|
1023
|
+
if not mime_type and data_url_match is not None:
|
|
1024
|
+
mime_type = data_url_match.group(1).strip().lower()
|
|
1025
|
+
is_text = mime_type.startswith("text/") or path.suffix.lower() in _TEXT_ATTACHMENT_SUFFIXES
|
|
1026
|
+
header = f'<uploaded_attachment name="{name}" path="{path}">'
|
|
1027
|
+
if not is_text:
|
|
1028
|
+
return (
|
|
1029
|
+
f"{header}\n"
|
|
1030
|
+
"The uploaded file is available at the path above. Read it with an appropriate "
|
|
1031
|
+
"tool before answering questions about its contents.\n"
|
|
1032
|
+
"</uploaded_attachment>"
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
raw = path.read_bytes()
|
|
1036
|
+
truncated = len(raw) > _MAX_INLINE_ATTACHMENT_TEXT_BYTES
|
|
1037
|
+
text = raw[:_MAX_INLINE_ATTACHMENT_TEXT_BYTES].decode("utf-8", errors="replace")
|
|
1038
|
+
suffix = "\n[attachment content truncated]" if truncated else ""
|
|
1039
|
+
return f"{header}\n{text}{suffix}\n</uploaded_attachment>"
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
__all__ = ["CodexRuntimeAdapter"]
|