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/client.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""CodexRuntimeAdapter 的 codex 后端接口 (goal-09)。
|
|
2
2
|
|
|
3
|
-
重托管模式:
|
|
3
|
+
重托管模式:CodexRuntimeAdapter 对执行生命周期负责,不把 cancel/进程管理薄委托给上层。
|
|
4
4
|
``CodexClient`` 是 codex 后端(thread/turn 生命周期)的最小接口,方法集对齐 OpenAI
|
|
5
5
|
官方 ``openai-codex`` SDK 的**真实**线程模型(``thread_start`` / ``thread.turn`` /
|
|
6
6
|
``handle.stream`` / ``handle.interrupt`` / ``thread_resume``):
|
|
@@ -11,27 +11,75 @@
|
|
|
11
11
|
- 测试实现:用 fake(见 tests/runners/test_codex_runtime.py / test_adapter_contract.py),
|
|
12
12
|
不需要真 CLI 二进制。
|
|
13
13
|
|
|
14
|
-
诚实边界:本模块的 SDK **方法面**已对安装的 ``openai-codex==0.
|
|
14
|
+
诚实边界:本模块的 SDK **方法面**已对安装的 ``openai-codex==0.147.0`` 实证(方法存在性 +
|
|
15
15
|
协程/asyncgen 形态);Notification → RuntimeEvent 的**字段级** phase 映射需在接真实 codex
|
|
16
16
|
后端时按实况对齐(结构已按生成的 payload 类型映射,见 ``_notification_to_event_dict``)。
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
import asyncio
|
|
21
22
|
import os
|
|
23
|
+
import queue
|
|
22
24
|
import secrets
|
|
25
|
+
import threading
|
|
23
26
|
from abc import ABC, abstractmethod
|
|
27
|
+
from dataclasses import dataclass, field
|
|
24
28
|
from importlib.metadata import PackageNotFoundError, version
|
|
25
29
|
from typing import Any, AsyncIterator, Optional
|
|
30
|
+
from uuid import uuid4
|
|
26
31
|
|
|
27
32
|
from ksadk.model_proxy import ProxyConfig, ProxyServer
|
|
28
33
|
from ksadk.model_proxy.cache import CapabilityCache, credential_scope
|
|
29
|
-
from ksadk.model_proxy.detect import
|
|
34
|
+
from ksadk.model_proxy.detect import (
|
|
35
|
+
CODEX_DIRECT_REQUIRED_TOOL_TYPES,
|
|
36
|
+
CODEX_OPTIONAL_TOOL_TYPES,
|
|
37
|
+
ModelCapabilities,
|
|
38
|
+
probe_responses_capability,
|
|
39
|
+
)
|
|
30
40
|
|
|
31
41
|
# 探测缓存单例:能力判定跨 client 共享,按 (model, base, credential_scope) 长缓存
|
|
32
42
|
_CAPABILITY_CACHE = CapabilityCache(ttl=3600)
|
|
33
43
|
|
|
34
44
|
|
|
45
|
+
class CodexCapabilityUnavailableError(RuntimeError):
|
|
46
|
+
"""A caller-required Codex capability cannot be preserved by this route."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class _CapabilityRoute:
|
|
51
|
+
use_proxy: bool
|
|
52
|
+
disabled_tool_types: frozenset[str] = frozenset()
|
|
53
|
+
unavailable_required_tool_types: frozenset[str] = frozenset()
|
|
54
|
+
|
|
55
|
+
def require_available(self) -> None:
|
|
56
|
+
if self.unavailable_required_tool_types:
|
|
57
|
+
names = ", ".join(sorted(self.unavailable_required_tool_types))
|
|
58
|
+
raise CodexCapabilityUnavailableError(
|
|
59
|
+
f"required Codex capabilities unavailable on selected route: {names}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class _PendingApproval:
|
|
65
|
+
approval_id: str
|
|
66
|
+
thread_id: str
|
|
67
|
+
method: str
|
|
68
|
+
params: dict[str, Any]
|
|
69
|
+
resolved: threading.Event = field(default_factory=threading.Event)
|
|
70
|
+
response: dict[str, Any] = field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class _PendingInteraction:
|
|
75
|
+
interaction_id: str
|
|
76
|
+
thread_id: str
|
|
77
|
+
method: str
|
|
78
|
+
params: dict[str, Any]
|
|
79
|
+
resolved: threading.Event = field(default_factory=threading.Event)
|
|
80
|
+
response: dict[str, Any] = field(default_factory=dict)
|
|
81
|
+
|
|
82
|
+
|
|
35
83
|
def _is_openai_official(base: str) -> bool:
|
|
36
84
|
"""OpenAI 官方 base_url(直连,不探测不代理)。"""
|
|
37
85
|
from urllib.parse import urlsplit
|
|
@@ -56,11 +104,48 @@ def _upgrade_http_to_https(upstream: str) -> str:
|
|
|
56
104
|
return upstream
|
|
57
105
|
|
|
58
106
|
|
|
59
|
-
def
|
|
60
|
-
|
|
107
|
+
def _route_for_capabilities(
|
|
108
|
+
caps: ModelCapabilities,
|
|
109
|
+
*,
|
|
110
|
+
required_tool_types: set[str] | frozenset[str] = frozenset(),
|
|
111
|
+
) -> _CapabilityRoute:
|
|
112
|
+
"""Split protocol requirements from optional tool degradation.
|
|
113
|
+
|
|
114
|
+
The current Studio/Codex launch contract has no user-facing required-tool
|
|
115
|
+
declaration, so callers pass the default empty set. This explicit input is
|
|
116
|
+
the fail-closed seam for a future ``web_search=required`` contract.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
native_ready = caps.responses_supported is True and CODEX_DIRECT_REQUIRED_TOOL_TYPES.issubset(
|
|
120
|
+
caps.tool_types
|
|
121
|
+
)
|
|
122
|
+
use_proxy = not native_ready
|
|
123
|
+
disabled = (
|
|
124
|
+
CODEX_OPTIONAL_TOOL_TYPES
|
|
125
|
+
if use_proxy
|
|
126
|
+
else CODEX_OPTIONAL_TOOL_TYPES.difference(caps.tool_types)
|
|
127
|
+
)
|
|
128
|
+
required = frozenset(required_tool_types)
|
|
129
|
+
unavailable = (required.difference(caps.tool_types)) | required.intersection(disabled)
|
|
130
|
+
return _CapabilityRoute(
|
|
131
|
+
use_proxy=use_proxy,
|
|
132
|
+
disabled_tool_types=frozenset(disabled),
|
|
133
|
+
unavailable_required_tool_types=frozenset(unavailable),
|
|
134
|
+
)
|
|
135
|
+
|
|
61
136
|
|
|
62
|
-
|
|
63
|
-
|
|
137
|
+
def _probe_capability_route(
|
|
138
|
+
model: str,
|
|
139
|
+
base: str,
|
|
140
|
+
key: str,
|
|
141
|
+
*,
|
|
142
|
+
required_tool_types: set[str] | frozenset[str] = frozenset(),
|
|
143
|
+
) -> _CapabilityRoute:
|
|
144
|
+
"""Probe once and return protocol plus optional-tool routing decisions.
|
|
145
|
+
|
|
146
|
+
纯文本 Responses 成功不代表能接收 Codex 0.147 的完整
|
|
147
|
+
``additional_tools`` 方言。namespace/custom 缺失或未知走兼容代理;仅缺
|
|
148
|
+
web_search 时保留原生 Responses,并在 Codex 生成请求前禁用该可选工具。
|
|
64
149
|
"""
|
|
65
150
|
|
|
66
151
|
def probe(m: str, b: str):
|
|
@@ -70,7 +155,13 @@ def _probe_requires_proxy(model: str, base: str, key: str) -> bool:
|
|
|
70
155
|
return probe_responses_capability(client, b, key, m, timeout=15.0)
|
|
71
156
|
|
|
72
157
|
caps = _CAPABILITY_CACHE.get_or_probe(model, base, credential_scope(key), probe)
|
|
73
|
-
return caps
|
|
158
|
+
return _route_for_capabilities(caps, required_tool_types=required_tool_types)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _probe_requires_proxy(model: str, base: str, key: str) -> bool:
|
|
162
|
+
"""Compatibility predicate for callers/tests that only need protocol choice."""
|
|
163
|
+
|
|
164
|
+
return _probe_capability_route(model, base, key).use_proxy
|
|
74
165
|
|
|
75
166
|
|
|
76
167
|
class CodexClient(ABC):
|
|
@@ -93,6 +184,17 @@ class CodexClient(ABC):
|
|
|
93
184
|
产出规范化事件 dict(``{"method": ..., "params": ...}``)。"""
|
|
94
185
|
raise NotImplementedError
|
|
95
186
|
|
|
187
|
+
def run_goal(
|
|
188
|
+
self,
|
|
189
|
+
thread_id: str,
|
|
190
|
+
objective: str,
|
|
191
|
+
*,
|
|
192
|
+
config: Optional[dict[str, Any]] = None,
|
|
193
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
194
|
+
"""Run one persisted Codex goal as a logical notification stream."""
|
|
195
|
+
|
|
196
|
+
raise RuntimeError("connected Codex client does not support goal operations")
|
|
197
|
+
|
|
96
198
|
@abstractmethod
|
|
97
199
|
async def interrupt_active_turn(self, thread_id: str) -> bool:
|
|
98
200
|
"""interrupt 该 thread 当前活跃 turn(真实 SDK ``handle.interrupt``);
|
|
@@ -104,6 +206,35 @@ class CodexClient(ABC):
|
|
|
104
206
|
"""恢复 thread(真实 SDK ``thread_resume``),返回 thread_id。"""
|
|
105
207
|
raise NotImplementedError
|
|
106
208
|
|
|
209
|
+
async def resolve_approval(self, approval_id: str, decision: str) -> bool:
|
|
210
|
+
"""Resolve one live native approval request.
|
|
211
|
+
|
|
212
|
+
Older/custom clients may not expose interactive approvals; returning
|
|
213
|
+
``False`` keeps that capability boundary explicit without breaking the
|
|
214
|
+
existing client protocol.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
async def resolve_interaction(
|
|
220
|
+
self,
|
|
221
|
+
interaction_id: str,
|
|
222
|
+
data: dict[str, Any],
|
|
223
|
+
) -> bool:
|
|
224
|
+
"""Resolve one live structured user-input request."""
|
|
225
|
+
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
async def pause_goal(self, thread_id: str) -> bool:
|
|
229
|
+
"""Pause an active native goal when supported."""
|
|
230
|
+
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
async def cancel_goal(self, thread_id: str) -> bool:
|
|
234
|
+
"""Cancel an active native goal operation when supported."""
|
|
235
|
+
|
|
236
|
+
return False
|
|
237
|
+
|
|
107
238
|
@abstractmethod
|
|
108
239
|
async def close(self) -> None:
|
|
109
240
|
"""释放后端资源(真实 SDK ``close``)。"""
|
|
@@ -112,7 +243,7 @@ class CodexClient(ABC):
|
|
|
112
243
|
|
|
113
244
|
def _missing_codex_error() -> RuntimeError:
|
|
114
245
|
return RuntimeError(
|
|
115
|
-
"
|
|
246
|
+
"CodexRuntimeAdapter 需要 openai-codex SDK;请安装 ksadk[codex]:"
|
|
116
247
|
" pip install 'ksadk[codex]'(openai-codex 为可选 extra,不进默认依赖)"
|
|
117
248
|
)
|
|
118
249
|
|
|
@@ -126,7 +257,7 @@ class AsyncCodexClient(CodexClient):
|
|
|
126
257
|
``AsyncThread.turn`` / ``AsyncTurnHandle.stream``/``interrupt``),版本漂移 fail-fast。
|
|
127
258
|
"""
|
|
128
259
|
|
|
129
|
-
def __init__(self, config: Any = None) -> None:
|
|
260
|
+
def __init__(self, config: Any = None, *, proxy_observer: Any = None) -> None:
|
|
130
261
|
try:
|
|
131
262
|
from openai_codex import ( # type: ignore[import-not-found]
|
|
132
263
|
AsyncCodex,
|
|
@@ -155,50 +286,358 @@ class AsyncCodexClient(CodexClient):
|
|
|
155
286
|
f"{owner.__name__}.{method_name}(版本不兼容)"
|
|
156
287
|
)
|
|
157
288
|
|
|
158
|
-
# AsyncCodex 0.
|
|
159
|
-
config, self._proxy = self._maybe_apply_proxy(
|
|
289
|
+
# AsyncCodex 0.147.0 only accepts one CodexConfig positional/keyword.
|
|
290
|
+
config, self._proxy = self._maybe_apply_proxy(
|
|
291
|
+
config,
|
|
292
|
+
proxy_observer=proxy_observer,
|
|
293
|
+
)
|
|
160
294
|
self._codex = AsyncCodex(config=config)
|
|
161
295
|
self.sdk_version = sdk_version
|
|
162
296
|
self._threads: dict[str, Any] = {} # thread_id -> AsyncThread
|
|
163
297
|
self._active_handles: dict[str, Any] = {} # thread_id -> 活跃 AsyncTurnHandle
|
|
298
|
+
self._goal_states: dict[str, Any] = {}
|
|
299
|
+
self._approval_queues: dict[str, queue.Queue[Any]] = {}
|
|
300
|
+
self._pending_approvals: dict[str, _PendingApproval] = {}
|
|
301
|
+
self._pending_interactions: dict[str, _PendingInteraction] = {}
|
|
302
|
+
self._approval_lock = threading.Lock()
|
|
303
|
+
self._install_approval_bridge()
|
|
304
|
+
|
|
305
|
+
def _install_approval_bridge(self) -> None:
|
|
306
|
+
"""Replace the SDK's unconditional accept handler with a HITL bridge.
|
|
307
|
+
|
|
308
|
+
``openai-codex==0.147.0`` exposes approval callbacks only on its sync
|
|
309
|
+
JSON-RPC client. The public ``AsyncCodex`` wrapper owns that client, so
|
|
310
|
+
this pinned compatibility seam is validated eagerly instead of silently
|
|
311
|
+
auto-accepting tool and file changes.
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
async_client = getattr(self._codex, "_client", None)
|
|
315
|
+
sync_client = getattr(async_client, "_sync", None)
|
|
316
|
+
if sync_client is None or not hasattr(sync_client, "_approval_handler"):
|
|
317
|
+
raise RuntimeError(
|
|
318
|
+
f"openai-codex {self.sdk_version} 不支持交互式审批桥接(内部 API 不兼容)"
|
|
319
|
+
)
|
|
320
|
+
sync_client._approval_handler = self._handle_server_request
|
|
321
|
+
|
|
322
|
+
def _active_request_queue(self, raw: dict[str, Any]) -> tuple[str, queue.Queue[Any] | None]:
|
|
323
|
+
thread_id = str(raw.get("threadId") or raw.get("thread_id") or "")
|
|
324
|
+
if not thread_id:
|
|
325
|
+
with self._approval_lock:
|
|
326
|
+
candidates = list(self._approval_queues)
|
|
327
|
+
if len(candidates) == 1:
|
|
328
|
+
thread_id = candidates[0]
|
|
329
|
+
with self._approval_lock:
|
|
330
|
+
return thread_id, self._approval_queues.get(thread_id)
|
|
331
|
+
|
|
332
|
+
def _handle_server_request(
|
|
333
|
+
self,
|
|
334
|
+
method: str,
|
|
335
|
+
params: dict[str, Any] | None,
|
|
336
|
+
) -> dict[str, Any]:
|
|
337
|
+
if method in {
|
|
338
|
+
"item/commandExecution/requestApproval",
|
|
339
|
+
"item/fileChange/requestApproval",
|
|
340
|
+
}:
|
|
341
|
+
return self._handle_approval_request(method, params)
|
|
342
|
+
if method == "item/tool/requestUserInput":
|
|
343
|
+
return self._handle_user_input_request(method, params)
|
|
344
|
+
return {}
|
|
345
|
+
|
|
346
|
+
def _handle_approval_request(
|
|
347
|
+
self,
|
|
348
|
+
method: str,
|
|
349
|
+
params: dict[str, Any] | None,
|
|
350
|
+
) -> dict[str, Any]:
|
|
351
|
+
raw = dict(params or {})
|
|
352
|
+
if method not in {
|
|
353
|
+
"item/commandExecution/requestApproval",
|
|
354
|
+
"item/fileChange/requestApproval",
|
|
355
|
+
}:
|
|
356
|
+
return {}
|
|
357
|
+
thread_id, approval_queue = self._active_request_queue(raw)
|
|
358
|
+
approval_id = str(
|
|
359
|
+
raw.get("itemId") or raw.get("item_id") or raw.get("id") or f"approval_{uuid4().hex}"
|
|
360
|
+
)
|
|
361
|
+
pending = _PendingApproval(
|
|
362
|
+
approval_id=approval_id,
|
|
363
|
+
thread_id=thread_id,
|
|
364
|
+
method=method,
|
|
365
|
+
params=raw,
|
|
366
|
+
)
|
|
367
|
+
with self._approval_lock:
|
|
368
|
+
if approval_queue is None:
|
|
369
|
+
# Fail closed when a request cannot be associated with an
|
|
370
|
+
# active run; never fall back to the SDK's auto-accept default.
|
|
371
|
+
return {"decision": "decline"}
|
|
372
|
+
self._pending_approvals[approval_id] = pending
|
|
373
|
+
# 下发原生 JSON-RPC requestApproval 消息(synthetic id=approval_id):
|
|
374
|
+
# canonical mapper 只认原生方法(unsupported_method fail-closed),
|
|
375
|
+
# 旧合成 ``item/approval/requested`` 事件会让整个 run 失败。
|
|
376
|
+
approval_queue.put(
|
|
377
|
+
{
|
|
378
|
+
"id": approval_id,
|
|
379
|
+
"method": method,
|
|
380
|
+
"params": raw,
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
pending.resolved.wait()
|
|
384
|
+
with self._approval_lock:
|
|
385
|
+
self._pending_approvals.pop(approval_id, None)
|
|
386
|
+
# 回包也以 JSON-RPC response 形态下发,mapper 才会产出
|
|
387
|
+
# InteractionResolved(原 call_id 闭环)并释放 continuation。
|
|
388
|
+
response = pending.response or {"decision": "decline"}
|
|
389
|
+
if approval_queue is not None:
|
|
390
|
+
approval_queue.put(
|
|
391
|
+
{
|
|
392
|
+
"id": approval_id,
|
|
393
|
+
"result": dict(response),
|
|
394
|
+
}
|
|
395
|
+
)
|
|
396
|
+
return pending.response or {"decision": "decline"}
|
|
397
|
+
|
|
398
|
+
def _handle_user_input_request(
|
|
399
|
+
self,
|
|
400
|
+
method: str,
|
|
401
|
+
params: dict[str, Any] | None,
|
|
402
|
+
) -> dict[str, Any]:
|
|
403
|
+
raw = dict(params or {})
|
|
404
|
+
thread_id, request_queue = self._active_request_queue(raw)
|
|
405
|
+
interaction_id = str(
|
|
406
|
+
raw.get("itemId")
|
|
407
|
+
or raw.get("item_id")
|
|
408
|
+
or raw.get("requestId")
|
|
409
|
+
or raw.get("request_id")
|
|
410
|
+
or raw.get("id")
|
|
411
|
+
or f"input_{uuid4().hex}"
|
|
412
|
+
)
|
|
413
|
+
pending = _PendingInteraction(
|
|
414
|
+
interaction_id=interaction_id,
|
|
415
|
+
thread_id=thread_id,
|
|
416
|
+
method=method,
|
|
417
|
+
params=raw,
|
|
418
|
+
)
|
|
419
|
+
if request_queue is None:
|
|
420
|
+
return {"answers": {}}
|
|
421
|
+
|
|
422
|
+
surface_id = f"input-{interaction_id}"
|
|
423
|
+
components: list[dict[str, Any]] = []
|
|
424
|
+
question_components: list[dict[str, Any]] = []
|
|
425
|
+
input_schema: dict[str, Any] = {"type": "object", "properties": {}}
|
|
426
|
+
questions = raw.get("questions") if isinstance(raw.get("questions"), list) else []
|
|
427
|
+
for index, raw_question in enumerate(questions):
|
|
428
|
+
if not isinstance(raw_question, dict):
|
|
429
|
+
continue
|
|
430
|
+
question_id = str(raw_question.get("id") or f"question_{index + 1}")
|
|
431
|
+
raw_options = raw_question.get("options")
|
|
432
|
+
options = (
|
|
433
|
+
[dict(option) for option in raw_options if isinstance(option, dict)]
|
|
434
|
+
if isinstance(raw_options, list)
|
|
435
|
+
else []
|
|
436
|
+
)
|
|
437
|
+
multiple = bool(
|
|
438
|
+
raw_question.get("multiple")
|
|
439
|
+
or raw_question.get("isMultiple")
|
|
440
|
+
or raw_question.get("is_multiple")
|
|
441
|
+
or raw_question.get("isMultiSelect")
|
|
442
|
+
or raw_question.get("is_multi_select")
|
|
443
|
+
)
|
|
444
|
+
question_components.append(
|
|
445
|
+
{
|
|
446
|
+
"id": question_id,
|
|
447
|
+
"component": "MultipleChoice",
|
|
448
|
+
"props": {
|
|
449
|
+
"name": question_id,
|
|
450
|
+
"label": str(
|
|
451
|
+
raw_question.get("header")
|
|
452
|
+
or raw_question.get("question")
|
|
453
|
+
or question_id
|
|
454
|
+
),
|
|
455
|
+
"description": str(raw_question.get("question") or ""),
|
|
456
|
+
"options": options,
|
|
457
|
+
"multiple": multiple,
|
|
458
|
+
"allow_other": bool(
|
|
459
|
+
raw_question.get("isOther") or raw_question.get("is_other")
|
|
460
|
+
),
|
|
461
|
+
"secret": bool(
|
|
462
|
+
raw_question.get("isSecret") or raw_question.get("is_secret")
|
|
463
|
+
),
|
|
464
|
+
},
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
option_labels = [
|
|
468
|
+
str(option.get("label") or "") for option in options if option.get("label")
|
|
469
|
+
]
|
|
470
|
+
input_schema["properties"][question_id] = (
|
|
471
|
+
{
|
|
472
|
+
"type": "array",
|
|
473
|
+
"items": {"type": "string", "enum": option_labels},
|
|
474
|
+
}
|
|
475
|
+
if multiple
|
|
476
|
+
else {"type": "string", "enum": option_labels}
|
|
477
|
+
)
|
|
478
|
+
components.append(
|
|
479
|
+
{
|
|
480
|
+
"id": "form",
|
|
481
|
+
"component": "Form",
|
|
482
|
+
"props": {"title": "需要你的反馈", "submit_label": "提交"},
|
|
483
|
+
"children": question_components,
|
|
484
|
+
}
|
|
485
|
+
)
|
|
486
|
+
with self._approval_lock:
|
|
487
|
+
self._pending_interactions[interaction_id] = pending
|
|
488
|
+
request_queue.put(
|
|
489
|
+
{
|
|
490
|
+
"method": "a2ui/surface",
|
|
491
|
+
"params": {
|
|
492
|
+
"surface_id": surface_id,
|
|
493
|
+
"surface": {
|
|
494
|
+
"catalog_id": "https://a2ui.org/specification/v0_9/basic_catalog.json",
|
|
495
|
+
"components": components,
|
|
496
|
+
"data_model": {},
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
request_queue.put(
|
|
502
|
+
{
|
|
503
|
+
"method": "a2ui/interaction",
|
|
504
|
+
"params": {
|
|
505
|
+
"surface_id": surface_id,
|
|
506
|
+
"interaction_id": interaction_id,
|
|
507
|
+
"kind": "form",
|
|
508
|
+
"input_schema": input_schema,
|
|
509
|
+
"is_blocking": bool(raw.get("isBlocking", raw.get("is_blocking", True))),
|
|
510
|
+
},
|
|
511
|
+
}
|
|
512
|
+
)
|
|
513
|
+
pending.resolved.wait()
|
|
514
|
+
with self._approval_lock:
|
|
515
|
+
self._pending_interactions.pop(interaction_id, None)
|
|
516
|
+
return pending.response or {"answers": {}}
|
|
164
517
|
|
|
165
518
|
@staticmethod
|
|
166
|
-
def _maybe_apply_proxy(
|
|
519
|
+
def _maybe_apply_proxy(
|
|
520
|
+
config: Any,
|
|
521
|
+
*,
|
|
522
|
+
proxy_observer: Any = None,
|
|
523
|
+
) -> tuple[Any, Any]:
|
|
167
524
|
"""codex 代理启用:**智能探测 fallback(带显式覆盖)**。
|
|
168
525
|
|
|
169
526
|
- ``KSADK_CODEX_USE_PROXY=1`` → 强制开代理;``=0`` → 强制直连(可人工覆盖误判)。
|
|
170
527
|
- **未设 env 时智能探测**:OpenAI 官方 base_url 直连(不探测);自定义上游
|
|
171
528
|
(星流等)探测 responses 能力(detect.py + CapabilityCache 缓存,一次探测长缓存):
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
529
|
+
- namespace/custom 支持 → 原生 Responses 直连
|
|
530
|
+
- 仅 web_search 缺失 → 仍直连,并关闭该可选能力
|
|
531
|
+
- namespace/custom 缺失或无法确认 → 自动启用代理
|
|
175
532
|
- 凭证闭合:codex 子进程只拿随机 KSADK_PROXY_TOKEN;上游 key 留父进程。
|
|
176
533
|
- 互斥:launch_args_override 已设时 raise(override 整体覆盖命令行)。
|
|
534
|
+
- P1:直连分支(协议必需工具面确认、env=0)遇到自定义 base 也注入
|
|
535
|
+
``ksadk_direct`` provider——否则 codex 子进程回落默认 OpenAI 官方
|
|
536
|
+
端点,自定义上游(OPENAI_API_BASE)静默失效。已显式设
|
|
537
|
+
``model_provider=`` 的 config 不覆盖;官方 base 不注入。
|
|
177
538
|
|
|
178
539
|
返回 (新 config, ProxyServer | None)。staticmethod 便于单测。
|
|
179
540
|
"""
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
541
|
+
runtime_env = {**os.environ, **(getattr(config, "env", None) or {})}
|
|
542
|
+
env_val = runtime_env.get("KSADK_CODEX_USE_PROXY")
|
|
543
|
+
if env_val in {"0", "direct"}:
|
|
544
|
+
return AsyncCodexClient._inject_direct_provider(config), None
|
|
545
|
+
if env_val in {"1", "forced"}:
|
|
546
|
+
return AsyncCodexClient._start_proxy_and_inject(
|
|
547
|
+
config,
|
|
548
|
+
proxy_observer=proxy_observer,
|
|
549
|
+
)
|
|
185
550
|
# 未设:智能探测
|
|
186
551
|
base = (
|
|
187
|
-
|
|
188
|
-
or
|
|
189
|
-
or
|
|
552
|
+
runtime_env.get("KSADK_PROXY_UPSTREAM_BASE")
|
|
553
|
+
or runtime_env.get("OPENAI_BASE_URL")
|
|
554
|
+
or runtime_env.get("OPENAI_API_BASE")
|
|
190
555
|
or ""
|
|
191
556
|
)
|
|
192
557
|
if not base or _is_openai_official(base):
|
|
193
558
|
return config, None # OpenAI 官方:直连,不探测
|
|
194
|
-
model =
|
|
195
|
-
key =
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
559
|
+
model = runtime_env.get("OPENAI_MODEL_NAME") or runtime_env.get("MODEL_NAME") or ""
|
|
560
|
+
key = runtime_env.get("KSADK_PROXY_UPSTREAM_KEY") or runtime_env.get("OPENAI_API_KEY") or ""
|
|
561
|
+
# Probe the exact URL scheme that Codex/proxy will use. Managed
|
|
562
|
+
# runtimes discover KSPMAS through its historical ``http://`` internal
|
|
563
|
+
# URL, while the provider is upgraded to HTTPS before execution. A
|
|
564
|
+
# probe against HTTP can see only a redirect and incorrectly classify
|
|
565
|
+
# an HTTPS ``/responses`` 404 as unknown, causing a broken direct path.
|
|
566
|
+
probe_base = _upgrade_http_to_https(base)
|
|
567
|
+
route = _probe_capability_route(model, probe_base, key)
|
|
568
|
+
# Future required-tool declarations must be checked here before either
|
|
569
|
+
# proxying or suppressing optional tools.
|
|
570
|
+
if isinstance(route, bool): # compatibility for injected test doubles
|
|
571
|
+
route = _CapabilityRoute(use_proxy=route)
|
|
572
|
+
route.require_available()
|
|
573
|
+
if route.use_proxy:
|
|
574
|
+
return AsyncCodexClient._start_proxy_and_inject(
|
|
575
|
+
config,
|
|
576
|
+
proxy_observer=proxy_observer,
|
|
577
|
+
)
|
|
578
|
+
# 探测确认协议必需工具面:直连,但必须把自定义 base 配成 provider。
|
|
579
|
+
return (
|
|
580
|
+
AsyncCodexClient._inject_direct_provider(
|
|
581
|
+
config,
|
|
582
|
+
disabled_tool_types=route.disabled_tool_types,
|
|
583
|
+
),
|
|
584
|
+
None,
|
|
585
|
+
)
|
|
199
586
|
|
|
200
587
|
@staticmethod
|
|
201
|
-
def
|
|
588
|
+
def _inject_direct_provider(
|
|
589
|
+
config: Any,
|
|
590
|
+
*,
|
|
591
|
+
disabled_tool_types: frozenset[str] = frozenset(),
|
|
592
|
+
) -> Any:
|
|
593
|
+
"""直连模式注入 ``ksadk_direct`` provider(P1:非 proxy 不丢自定义 base)。
|
|
594
|
+
|
|
595
|
+
- 无自定义 base / 官方 OpenAI base → 原样返回。
|
|
596
|
+
- 已设 ``model_provider=`` → 保留 provider,仅追加必要的可选工具关闭项。
|
|
597
|
+
- 否则追加 ``model_provider=ksadk_direct`` + base_url/env_key/wire_api
|
|
598
|
+
(responses;该分支只在探测确认或显式强制直连时到达,其余走 proxy)。
|
|
599
|
+
"""
|
|
600
|
+
import dataclasses
|
|
601
|
+
|
|
602
|
+
from openai_codex import CodexConfig # type: ignore[import-not-found]
|
|
603
|
+
|
|
604
|
+
cfg = config if isinstance(config, CodexConfig) else CodexConfig()
|
|
605
|
+
overrides = list(cfg.config_overrides or ())
|
|
606
|
+
if any(str(o).startswith("model_provider=") for o in overrides):
|
|
607
|
+
if "web_search" in disabled_tool_types and "web_search=disabled" not in overrides:
|
|
608
|
+
return dataclasses.replace(
|
|
609
|
+
cfg,
|
|
610
|
+
config_overrides=tuple([*overrides, "web_search=disabled"]),
|
|
611
|
+
)
|
|
612
|
+
return config
|
|
613
|
+
runtime_env = {**os.environ, **(cfg.env or {})}
|
|
614
|
+
base = (
|
|
615
|
+
runtime_env.get("KSADK_PROXY_UPSTREAM_BASE")
|
|
616
|
+
or runtime_env.get("OPENAI_BASE_URL")
|
|
617
|
+
or runtime_env.get("OPENAI_API_BASE")
|
|
618
|
+
or ""
|
|
619
|
+
)
|
|
620
|
+
if not base or _is_openai_official(base):
|
|
621
|
+
return config
|
|
622
|
+
base = _upgrade_http_to_https(base)
|
|
623
|
+
overrides += [
|
|
624
|
+
"model_provider=ksadk_direct",
|
|
625
|
+
"model_providers.ksadk_direct.name=ksadk_direct",
|
|
626
|
+
f"model_providers.ksadk_direct.base_url={base}",
|
|
627
|
+
"model_providers.ksadk_direct.env_key=OPENAI_API_KEY",
|
|
628
|
+
"model_providers.ksadk_direct.wire_api=responses",
|
|
629
|
+
"model_providers.ksadk_direct.supports_websockets=false",
|
|
630
|
+
]
|
|
631
|
+
if "web_search" in disabled_tool_types and "web_search=disabled" not in overrides:
|
|
632
|
+
overrides.append("web_search=disabled")
|
|
633
|
+
return dataclasses.replace(cfg, config_overrides=tuple(overrides))
|
|
634
|
+
|
|
635
|
+
@staticmethod
|
|
636
|
+
def _start_proxy_and_inject(
|
|
637
|
+
config: Any,
|
|
638
|
+
*,
|
|
639
|
+
proxy_observer: Any = None,
|
|
640
|
+
) -> tuple[Any, Any]:
|
|
202
641
|
"""起进程内 ProxyServer + 注入 codex provider(opt-in/探测判定走代理时)。"""
|
|
203
642
|
import dataclasses
|
|
204
643
|
|
|
@@ -210,20 +649,39 @@ class AsyncCodexClient(CodexClient):
|
|
|
210
649
|
"KSADK_CODEX_USE_PROXY 与 CodexConfig.launch_args_override 互斥:"
|
|
211
650
|
"代理注入靠 config_overrides,而 launch_args_override 会整体覆盖命令行"
|
|
212
651
|
)
|
|
652
|
+
runtime_env = {**os.environ, **(cfg.env or {})}
|
|
213
653
|
upstream = (
|
|
214
|
-
|
|
215
|
-
or
|
|
216
|
-
or
|
|
654
|
+
runtime_env.get("KSADK_PROXY_UPSTREAM_BASE")
|
|
655
|
+
or runtime_env.get("OPENAI_BASE_URL")
|
|
656
|
+
or runtime_env.get("OPENAI_API_BASE")
|
|
217
657
|
or "https://kspmas.ksyun.com/v1"
|
|
218
658
|
)
|
|
219
659
|
upstream = _upgrade_http_to_https(upstream)
|
|
220
660
|
api_key = (
|
|
221
|
-
|
|
661
|
+
runtime_env.get("KSADK_PROXY_UPSTREAM_KEY") or runtime_env.get("OPENAI_API_KEY") or ""
|
|
222
662
|
)
|
|
223
663
|
token = secrets.token_hex(16)
|
|
224
|
-
proxy = ProxyServer(
|
|
664
|
+
proxy = ProxyServer(
|
|
665
|
+
ProxyConfig(
|
|
666
|
+
upstream_base=upstream,
|
|
667
|
+
api_key=api_key,
|
|
668
|
+
local_token=token,
|
|
669
|
+
upstream_model=runtime_env.get("OPENAI_MODEL_NAME")
|
|
670
|
+
or runtime_env.get("MODEL_NAME")
|
|
671
|
+
or "",
|
|
672
|
+
event_callback=proxy_observer,
|
|
673
|
+
)
|
|
674
|
+
)
|
|
225
675
|
proxy.start()
|
|
226
676
|
overrides = list(cfg.config_overrides or ())
|
|
677
|
+
# 默认模型必须注入:codex 未配置 model= 时会发自家默认名(如 gpt-5.6-terra),
|
|
678
|
+
# 上游(kspmas)按未知模型 403。注入后 thread 级 model 覆盖仍优先生效
|
|
679
|
+
# (实测 codex thread model > config model=),RunAgent 的 Model 透传靠它。
|
|
680
|
+
default_model = (
|
|
681
|
+
runtime_env.get("OPENAI_MODEL_NAME") or runtime_env.get("MODEL_NAME") or ""
|
|
682
|
+
).strip()
|
|
683
|
+
if default_model and not any(str(o).startswith("model=") for o in overrides):
|
|
684
|
+
overrides.append(f"model={default_model}")
|
|
227
685
|
overrides += [
|
|
228
686
|
"model_provider=ksadk_proxy",
|
|
229
687
|
"model_providers.ksadk_proxy.name=ksadk_proxy",
|
|
@@ -235,13 +693,24 @@ class AsyncCodexClient(CodexClient):
|
|
|
235
693
|
"features.multi_agent=false",
|
|
236
694
|
"features.multi_agent_v2=false",
|
|
237
695
|
]
|
|
238
|
-
|
|
696
|
+
# The local proxy owns the upstream credential. The Codex child only
|
|
697
|
+
# receives a random loopback token, so prompt-driven commands cannot
|
|
698
|
+
# read the real provider key from their environment.
|
|
699
|
+
env = dict(runtime_env)
|
|
700
|
+
for secret_name in (
|
|
701
|
+
"OPENAI_API_KEY",
|
|
702
|
+
"AGENTKIT_MODEL_API_KEY",
|
|
703
|
+
"KSADK_PROXY_UPSTREAM_KEY",
|
|
704
|
+
):
|
|
705
|
+
env.pop(secret_name, None)
|
|
239
706
|
env["KSADK_PROXY_TOKEN"] = token
|
|
240
707
|
return dataclasses.replace(cfg, config_overrides=tuple(overrides), env=env), proxy
|
|
241
708
|
|
|
242
|
-
|
|
243
709
|
async def start_thread(self, config: Optional[dict[str, Any]] = None) -> str:
|
|
244
|
-
|
|
710
|
+
if self._uses_manual_approval(config):
|
|
711
|
+
thread = await self._start_manual_thread(config)
|
|
712
|
+
else:
|
|
713
|
+
thread = await self._codex.thread_start(**self._thread_kwargs(config))
|
|
245
714
|
self._threads[thread.id] = thread
|
|
246
715
|
return str(thread.id)
|
|
247
716
|
|
|
@@ -251,13 +720,91 @@ class AsyncCodexClient(CodexClient):
|
|
|
251
720
|
cached = self._threads.get(thread_id)
|
|
252
721
|
if cached is not None:
|
|
253
722
|
return str(cached.id)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
723
|
+
if self._uses_manual_approval(config):
|
|
724
|
+
thread = await self._resume_manual_thread(thread_id, config)
|
|
725
|
+
else:
|
|
726
|
+
kwargs = self._thread_kwargs(config)
|
|
727
|
+
# AsyncCodex.thread_resume 0.144.4 has no ephemeral parameter.
|
|
728
|
+
kwargs.pop("ephemeral", None)
|
|
729
|
+
thread = await self._codex.thread_resume(thread_id, **kwargs)
|
|
258
730
|
self._threads[thread.id] = thread
|
|
259
731
|
return str(thread.id)
|
|
260
732
|
|
|
733
|
+
@staticmethod
|
|
734
|
+
def _uses_manual_approval(config: Optional[dict[str, Any]]) -> bool:
|
|
735
|
+
return str((config or {}).get("approval_mode") or "").strip().lower() == "manual"
|
|
736
|
+
|
|
737
|
+
async def _start_manual_thread(self, config: Optional[dict[str, Any]]) -> Any:
|
|
738
|
+
"""Start a thread whose native approvals are reviewed by Studio users.
|
|
739
|
+
|
|
740
|
+
``openai-codex==0.147.0`` exposes ``ApprovalsReviewer.user`` on the
|
|
741
|
+
generated app-server contract but omits it from the public
|
|
742
|
+
``ApprovalMode`` enum. Use that pinned wire contract explicitly rather
|
|
743
|
+
than falling back to ``auto_review``.
|
|
744
|
+
"""
|
|
745
|
+
|
|
746
|
+
from openai_codex import AsyncThread # type: ignore[import-not-found]
|
|
747
|
+
from openai_codex._sandbox import _sandbox_mode # type: ignore[import-not-found]
|
|
748
|
+
from openai_codex.generated.v2_all import ( # type: ignore[import-not-found]
|
|
749
|
+
ApprovalsReviewer,
|
|
750
|
+
AskForApproval,
|
|
751
|
+
AskForApprovalValue,
|
|
752
|
+
ThreadStartParams,
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
kwargs = self._thread_kwargs(self._without_manual_approval(config))
|
|
756
|
+
kwargs.pop("approval_mode", None)
|
|
757
|
+
sandbox = kwargs.pop("sandbox", None)
|
|
758
|
+
await self._codex._ensure_initialized()
|
|
759
|
+
started = await self._codex._client.thread_start(
|
|
760
|
+
ThreadStartParams(
|
|
761
|
+
approval_policy=AskForApproval(root=AskForApprovalValue.on_request),
|
|
762
|
+
approvals_reviewer=ApprovalsReviewer.user,
|
|
763
|
+
sandbox=_sandbox_mode(sandbox),
|
|
764
|
+
**kwargs,
|
|
765
|
+
)
|
|
766
|
+
)
|
|
767
|
+
return AsyncThread(self._codex, started.thread.id)
|
|
768
|
+
|
|
769
|
+
async def _resume_manual_thread(
|
|
770
|
+
self,
|
|
771
|
+
thread_id: str,
|
|
772
|
+
config: Optional[dict[str, Any]],
|
|
773
|
+
) -> Any:
|
|
774
|
+
from openai_codex import AsyncThread # type: ignore[import-not-found]
|
|
775
|
+
from openai_codex._sandbox import _sandbox_mode # type: ignore[import-not-found]
|
|
776
|
+
from openai_codex.generated.v2_all import ( # type: ignore[import-not-found]
|
|
777
|
+
ApprovalsReviewer,
|
|
778
|
+
AskForApproval,
|
|
779
|
+
AskForApprovalValue,
|
|
780
|
+
ThreadResumeParams,
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
kwargs = self._thread_kwargs(self._without_manual_approval(config))
|
|
784
|
+
kwargs.pop("approval_mode", None)
|
|
785
|
+
kwargs.pop("ephemeral", None)
|
|
786
|
+
sandbox = kwargs.pop("sandbox", None)
|
|
787
|
+
await self._codex._ensure_initialized()
|
|
788
|
+
resumed = await self._codex._client.thread_resume(
|
|
789
|
+
thread_id,
|
|
790
|
+
ThreadResumeParams(
|
|
791
|
+
thread_id=thread_id,
|
|
792
|
+
approval_policy=AskForApproval(root=AskForApprovalValue.on_request),
|
|
793
|
+
approvals_reviewer=ApprovalsReviewer.user,
|
|
794
|
+
sandbox=_sandbox_mode(sandbox),
|
|
795
|
+
**kwargs,
|
|
796
|
+
),
|
|
797
|
+
)
|
|
798
|
+
return AsyncThread(self._codex, resumed.thread.id)
|
|
799
|
+
|
|
800
|
+
@staticmethod
|
|
801
|
+
def _without_manual_approval(
|
|
802
|
+
config: Optional[dict[str, Any]],
|
|
803
|
+
) -> dict[str, Any]:
|
|
804
|
+
result = dict(config or {})
|
|
805
|
+
result.pop("approval_mode", None)
|
|
806
|
+
return result
|
|
807
|
+
|
|
261
808
|
def run_turn(
|
|
262
809
|
self,
|
|
263
810
|
thread_id: str,
|
|
@@ -267,6 +814,15 @@ class AsyncCodexClient(CodexClient):
|
|
|
267
814
|
) -> AsyncIterator[dict[str, Any]]:
|
|
268
815
|
return self._run_turn_gen(thread_id, prompt, config)
|
|
269
816
|
|
|
817
|
+
def run_goal(
|
|
818
|
+
self,
|
|
819
|
+
thread_id: str,
|
|
820
|
+
objective: str,
|
|
821
|
+
*,
|
|
822
|
+
config: Optional[dict[str, Any]] = None,
|
|
823
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
824
|
+
return self._run_goal_gen(thread_id, objective, config)
|
|
825
|
+
|
|
270
826
|
async def _run_turn_gen(
|
|
271
827
|
self, thread_id: str, prompt: Any, config: Optional[dict[str, Any]]
|
|
272
828
|
) -> AsyncIterator[dict[str, Any]]:
|
|
@@ -275,15 +831,157 @@ class AsyncCodexClient(CodexClient):
|
|
|
275
831
|
# 未显式 start/resume 的 thread_id:按 resume 语义接入真实后端。
|
|
276
832
|
await self.resume_thread(thread_id, config)
|
|
277
833
|
thread = self._threads[thread_id]
|
|
278
|
-
|
|
834
|
+
approval_queue: queue.Queue[Any] = queue.Queue()
|
|
835
|
+
with self._approval_lock:
|
|
836
|
+
self._approval_queues[thread_id] = approval_queue
|
|
837
|
+
handle = await self._start_turn(thread, self._coerce_input(prompt), config)
|
|
279
838
|
self._active_handles[thread_id] = handle
|
|
839
|
+
notifications = handle.stream()
|
|
840
|
+
notification_task: Any = None
|
|
841
|
+
approval_task: Any = None
|
|
280
842
|
try:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
843
|
+
while True:
|
|
844
|
+
if notification_task is None:
|
|
845
|
+
notification_task = asyncio.create_task(notifications.__anext__())
|
|
846
|
+
if approval_task is None:
|
|
847
|
+
approval_task = asyncio.create_task(asyncio.to_thread(approval_queue.get))
|
|
848
|
+
done, _pending = await asyncio.wait(
|
|
849
|
+
{notification_task, approval_task},
|
|
850
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
851
|
+
)
|
|
852
|
+
if approval_task in done:
|
|
853
|
+
approval_event = approval_task.result()
|
|
854
|
+
approval_task = None
|
|
855
|
+
if approval_event is not None:
|
|
856
|
+
yield approval_event
|
|
857
|
+
if notification_task in done:
|
|
858
|
+
try:
|
|
859
|
+
notification = notification_task.result()
|
|
860
|
+
except StopAsyncIteration:
|
|
861
|
+
break
|
|
862
|
+
notification_task = None
|
|
863
|
+
event = self._notification_to_event_dict(notification)
|
|
864
|
+
if event is not None:
|
|
865
|
+
yield event
|
|
285
866
|
finally:
|
|
867
|
+
approval_queue.put(None)
|
|
868
|
+
for task in (notification_task, approval_task):
|
|
869
|
+
if task is not None and not task.done():
|
|
870
|
+
task.cancel()
|
|
871
|
+
await asyncio.gather(
|
|
872
|
+
*(task for task in (notification_task, approval_task) if task is not None),
|
|
873
|
+
return_exceptions=True,
|
|
874
|
+
)
|
|
286
875
|
self._active_handles.pop(thread_id, None)
|
|
876
|
+
with self._approval_lock:
|
|
877
|
+
self._approval_queues.pop(thread_id, None)
|
|
878
|
+
|
|
879
|
+
async def _run_goal_gen(
|
|
880
|
+
self,
|
|
881
|
+
thread_id: str,
|
|
882
|
+
objective: str,
|
|
883
|
+
config: Optional[dict[str, Any]],
|
|
884
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
885
|
+
if thread_id not in self._threads:
|
|
886
|
+
await self.resume_thread(thread_id, config)
|
|
887
|
+
from openai_codex._goal import _AsyncGoalNotificationStream
|
|
888
|
+
|
|
889
|
+
low_level = self._codex._client
|
|
890
|
+
approval_queue: queue.Queue[Any] = queue.Queue()
|
|
891
|
+
with self._approval_lock:
|
|
892
|
+
self._approval_queues[thread_id] = approval_queue
|
|
893
|
+
state, _logical_turn_id = await low_level.start_goal_operation(thread_id, objective)
|
|
894
|
+
self._goal_states[thread_id] = state
|
|
895
|
+
notifications = _AsyncGoalNotificationStream(
|
|
896
|
+
state=state,
|
|
897
|
+
next_notification=lambda: low_level.next_goal_notification(state),
|
|
898
|
+
unregister=lambda: low_level.unregister_goal_operation(state),
|
|
899
|
+
cancel_goal=lambda: low_level.cancel_goal_operation(state),
|
|
900
|
+
)
|
|
901
|
+
notification_task: Any = None
|
|
902
|
+
approval_task: Any = None
|
|
903
|
+
try:
|
|
904
|
+
while True:
|
|
905
|
+
if notification_task is None:
|
|
906
|
+
notification_task = asyncio.create_task(notifications.__anext__())
|
|
907
|
+
if approval_task is None:
|
|
908
|
+
approval_task = asyncio.create_task(asyncio.to_thread(approval_queue.get))
|
|
909
|
+
done, _pending = await asyncio.wait(
|
|
910
|
+
{notification_task, approval_task},
|
|
911
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
912
|
+
)
|
|
913
|
+
if approval_task in done:
|
|
914
|
+
approval_event = approval_task.result()
|
|
915
|
+
approval_task = None
|
|
916
|
+
if approval_event is not None:
|
|
917
|
+
yield approval_event
|
|
918
|
+
if notification_task in done:
|
|
919
|
+
try:
|
|
920
|
+
notification = notification_task.result()
|
|
921
|
+
except StopAsyncIteration:
|
|
922
|
+
break
|
|
923
|
+
notification_task = None
|
|
924
|
+
event = self._notification_to_event_dict(notification)
|
|
925
|
+
if event is not None:
|
|
926
|
+
yield event
|
|
927
|
+
finally:
|
|
928
|
+
approval_queue.put(None)
|
|
929
|
+
for task in (notification_task, approval_task):
|
|
930
|
+
if task is not None and not task.done():
|
|
931
|
+
task.cancel()
|
|
932
|
+
await asyncio.gather(
|
|
933
|
+
*(task for task in (notification_task, approval_task) if task is not None),
|
|
934
|
+
return_exceptions=True,
|
|
935
|
+
)
|
|
936
|
+
await notifications.aclose()
|
|
937
|
+
self._goal_states.pop(thread_id, None)
|
|
938
|
+
with self._approval_lock:
|
|
939
|
+
self._approval_queues.pop(thread_id, None)
|
|
940
|
+
|
|
941
|
+
async def _start_turn(
|
|
942
|
+
self,
|
|
943
|
+
thread: Any,
|
|
944
|
+
prompt: Any,
|
|
945
|
+
config: Optional[dict[str, Any]],
|
|
946
|
+
) -> Any:
|
|
947
|
+
mode = str((config or {}).get("collaboration_mode") or "").strip().lower()
|
|
948
|
+
if not mode or mode == "default":
|
|
949
|
+
return await thread.turn(prompt, **self._turn_kwargs(config))
|
|
950
|
+
if mode != "plan":
|
|
951
|
+
raise ValueError("collaboration_mode must be default or plan")
|
|
952
|
+
|
|
953
|
+
# openai-codex 0.144.4 predates the public collaboration_mode argument,
|
|
954
|
+
# while the bundled app-server already accepts it. Keep this private
|
|
955
|
+
# compatibility seam small and covered by a wire-payload test.
|
|
956
|
+
from openai_codex import AsyncTurnHandle
|
|
957
|
+
from openai_codex._approval_mode import _approval_mode_override_settings
|
|
958
|
+
from openai_codex._inputs import _normalize_run_input, _to_wire_input
|
|
959
|
+
from openai_codex._sandbox import _sandbox_policy
|
|
960
|
+
from openai_codex.generated.v2_all import TurnStartParams
|
|
961
|
+
|
|
962
|
+
turn_config = self._turn_kwargs(config)
|
|
963
|
+
approval_mode = turn_config.pop("approval_mode", None)
|
|
964
|
+
sandbox = turn_config.pop("sandbox", None)
|
|
965
|
+
approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
|
|
966
|
+
wire_input = _to_wire_input(_normalize_run_input(prompt))
|
|
967
|
+
params = TurnStartParams(
|
|
968
|
+
thread_id=thread.id,
|
|
969
|
+
input=wire_input,
|
|
970
|
+
approval_policy=approval_policy,
|
|
971
|
+
approvals_reviewer=approvals_reviewer,
|
|
972
|
+
sandbox_policy=_sandbox_policy(sandbox),
|
|
973
|
+
**turn_config,
|
|
974
|
+
).model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
975
|
+
params["collaborationMode"] = {
|
|
976
|
+
"mode": "plan",
|
|
977
|
+
"settings": {
|
|
978
|
+
"model": str((config or {}).get("model") or ""),
|
|
979
|
+
"reasoning_effort": None,
|
|
980
|
+
"developer_instructions": None,
|
|
981
|
+
},
|
|
982
|
+
}
|
|
983
|
+
started = await self._codex._client.turn_start(thread.id, wire_input, params=params)
|
|
984
|
+
return AsyncTurnHandle(self._codex, thread.id, started.turn.id)
|
|
287
985
|
|
|
288
986
|
async def interrupt_active_turn(self, thread_id: str) -> bool:
|
|
289
987
|
handle = self._active_handles.get(thread_id)
|
|
@@ -292,7 +990,85 @@ class AsyncCodexClient(CodexClient):
|
|
|
292
990
|
await handle.interrupt()
|
|
293
991
|
return True
|
|
294
992
|
|
|
993
|
+
async def pause_goal(self, thread_id: str) -> bool:
|
|
994
|
+
state = self._goal_states.get(thread_id)
|
|
995
|
+
if state is None:
|
|
996
|
+
return False
|
|
997
|
+
await self._codex._client.pause_goal(thread_id)
|
|
998
|
+
return True
|
|
999
|
+
|
|
1000
|
+
async def cancel_goal(self, thread_id: str) -> bool:
|
|
1001
|
+
state = self._goal_states.get(thread_id)
|
|
1002
|
+
if state is None:
|
|
1003
|
+
return False
|
|
1004
|
+
await self._codex._client.cancel_goal_operation(state)
|
|
1005
|
+
return True
|
|
1006
|
+
|
|
1007
|
+
async def resolve_approval(self, approval_id: str, decision: str) -> bool:
|
|
1008
|
+
normalized = {
|
|
1009
|
+
"approve": "accept",
|
|
1010
|
+
"accept": "accept",
|
|
1011
|
+
"approve_session": "acceptForSession",
|
|
1012
|
+
"accept_for_session": "acceptForSession",
|
|
1013
|
+
"deny": "decline",
|
|
1014
|
+
"decline": "decline",
|
|
1015
|
+
"cancel": "cancel",
|
|
1016
|
+
}.get(str(decision).strip().lower())
|
|
1017
|
+
if normalized is None:
|
|
1018
|
+
raise ValueError("approval decision must be approve, approve_session, deny, or cancel")
|
|
1019
|
+
with self._approval_lock:
|
|
1020
|
+
pending = self._pending_approvals.get(approval_id)
|
|
1021
|
+
if pending is None:
|
|
1022
|
+
return False
|
|
1023
|
+
pending.response = {"decision": normalized}
|
|
1024
|
+
pending.resolved.set()
|
|
1025
|
+
return True
|
|
1026
|
+
|
|
1027
|
+
async def resolve_interaction(
|
|
1028
|
+
self,
|
|
1029
|
+
interaction_id: str,
|
|
1030
|
+
data: dict[str, Any],
|
|
1031
|
+
) -> bool:
|
|
1032
|
+
answers: dict[str, dict[str, list[str]]] = {}
|
|
1033
|
+
for key, value in data.items():
|
|
1034
|
+
if key in {"decision", "name", "action"} or value is None:
|
|
1035
|
+
continue
|
|
1036
|
+
raw_answers = value if isinstance(value, list) else [value]
|
|
1037
|
+
normalized = [str(answer) for answer in raw_answers if str(answer)]
|
|
1038
|
+
answers[str(key)] = {"answers": normalized}
|
|
1039
|
+
with self._approval_lock:
|
|
1040
|
+
pending = self._pending_interactions.get(interaction_id)
|
|
1041
|
+
if pending is None:
|
|
1042
|
+
return False
|
|
1043
|
+
pending.response = {"answers": answers}
|
|
1044
|
+
pending.resolved.set()
|
|
1045
|
+
return True
|
|
1046
|
+
|
|
295
1047
|
async def close(self) -> None:
|
|
1048
|
+
approval_lock = getattr(self, "_approval_lock", None)
|
|
1049
|
+
if approval_lock is None:
|
|
1050
|
+
pending: list[_PendingApproval] = []
|
|
1051
|
+
else:
|
|
1052
|
+
with approval_lock:
|
|
1053
|
+
pending = list(getattr(self, "_pending_approvals", {}).values())
|
|
1054
|
+
for approval in pending:
|
|
1055
|
+
approval.response = {"decision": "cancel"}
|
|
1056
|
+
approval.resolved.set()
|
|
1057
|
+
if approval_lock is None:
|
|
1058
|
+
interactions: list[_PendingInteraction] = []
|
|
1059
|
+
else:
|
|
1060
|
+
with approval_lock:
|
|
1061
|
+
interactions = list(getattr(self, "_pending_interactions", {}).values())
|
|
1062
|
+
for interaction in interactions:
|
|
1063
|
+
interaction.response = {"answers": {}}
|
|
1064
|
+
interaction.resolved.set()
|
|
1065
|
+
for thread_id, state in list(getattr(self, "_goal_states", {}).items()):
|
|
1066
|
+
try:
|
|
1067
|
+
await self._codex._client.cancel_goal_operation(state)
|
|
1068
|
+
except Exception:
|
|
1069
|
+
pass
|
|
1070
|
+
finally:
|
|
1071
|
+
self._goal_states.pop(thread_id, None)
|
|
296
1072
|
try:
|
|
297
1073
|
await self._codex.close()
|
|
298
1074
|
finally:
|
|
@@ -373,7 +1149,7 @@ class AsyncCodexClient(CodexClient):
|
|
|
373
1149
|
"""
|
|
374
1150
|
payload = notification.payload
|
|
375
1151
|
if hasattr(payload, "model_dump"):
|
|
376
|
-
params = payload.model_dump(mode="json")
|
|
1152
|
+
params = payload.model_dump(mode="json", by_alias=True)
|
|
377
1153
|
else:
|
|
378
1154
|
params = getattr(payload, "params", None)
|
|
379
1155
|
if not isinstance(params, dict):
|
|
@@ -386,7 +1162,15 @@ class AsyncCodexClient(CodexClient):
|
|
|
386
1162
|
"item/agentMessage/delta",
|
|
387
1163
|
"item/autoApprovalReview/started",
|
|
388
1164
|
"item/autoApprovalReview/completed",
|
|
1165
|
+
"thread/tokenUsage/updated",
|
|
1166
|
+
"turn/started",
|
|
1167
|
+
"turn/completed",
|
|
389
1168
|
"error",
|
|
1169
|
+
"item/reasoning/summaryTextDelta",
|
|
1170
|
+
"item/reasoning/textDelta",
|
|
1171
|
+
"item/reasoning/summaryPartAdded",
|
|
1172
|
+
"thread/goal/updated",
|
|
1173
|
+
"thread/goal/cleared",
|
|
390
1174
|
}
|
|
391
1175
|
if method in supported_methods:
|
|
392
1176
|
return {"method": method, "params": params}
|