agentengine-sdk-python 0.8.2__py3-none-any.whl → 0.8.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +18 -25
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +224 -169
- ksadk/_build_provenance.json +1 -0
- ksadk/agui/_agent_helpers.py +4 -0
- ksadk/api/client.py +3 -0
- ksadk/builders/managed_runtime_builder.py +3 -0
- ksadk/cli/__init__.py +6 -0
- ksadk/cli/cmd_plugin.py +657 -0
- ksadk/codex/runtime.py +50 -8
- ksadk/configs/env_registry.py +31 -3
- ksadk/configs/settings.py +9 -0
- ksadk/context_engine/contributors.py +73 -8
- ksadk/conversations/__init__.py +19 -0
- ksadk/conversations/context.py +104 -24
- ksadk/conversations/contracts.py +266 -0
- ksadk/conversations/message_projection.py +19 -1
- ksadk/conversations/projector.py +616 -0
- ksadk/conversations/reducer.py +98 -0
- ksadk/events/_v1_compat/projection.py +2 -0
- ksadk/events/adapters/codex.py +190 -0
- ksadk/harness/__init__.py +45 -35
- ksadk/harness/runtime.py +60 -1
- ksadk/kernel/contract_fingerprints.py +5 -4
- ksadk/kernel/contracts.py +8 -0
- ksadk/kernel/control.py +1 -0
- ksadk/kernel/ingress.py +21 -0
- ksadk/kernel/memory_store.py +1 -0
- ksadk/kernel/postgres_store.py +1 -0
- ksadk/kernel/sqlite_store.py +1 -0
- ksadk/kernel/worker.py +11 -0
- ksadk/memory/providers/local_sqlite.py +7 -0
- ksadk/plugins/__init__.py +167 -0
- ksadk/plugins/bridges/__init__.py +45 -0
- ksadk/plugins/bridges/codex.py +552 -0
- ksadk/plugins/bridges/dsh.py +1116 -0
- ksadk/plugins/builtins.py +747 -0
- ksadk/plugins/bundle.py +246 -0
- ksadk/plugins/bundle_security.py +167 -0
- ksadk/plugins/composition.py +529 -0
- ksadk/plugins/context_contributor.py +437 -0
- ksadk/plugins/contracts.py +451 -0
- ksadk/plugins/dsh_toolchain.py +935 -0
- ksadk/plugins/ecosystem_bridge.py +1105 -0
- ksadk/plugins/ecosystem_probe.py +191 -0
- ksadk/plugins/host.py +724 -0
- ksadk/plugins/providers/__init__.py +85 -0
- ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
- ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
- ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
- ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
- ksadk/plugins/providers/codex.py +722 -0
- ksadk/plugins/providers/codex_dsh.py +83 -0
- ksadk/plugins/providers/dsh.py +951 -0
- ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
- ksadk/plugins/providers/harness.py +627 -0
- ksadk/plugins/providers/harness_dsh.py +91 -0
- ksadk/plugins/providers/legacy.py +215 -0
- ksadk/plugins/providers/legacy_catalog.py +100 -0
- ksadk/plugins/providers/shipped_dsh.py +370 -0
- ksadk/plugins/resolver.py +331 -0
- ksadk/plugins/subagent_providers/__init__.py +12 -0
- ksadk/plugins/subagent_providers/codex.py +443 -0
- ksadk/plugins/subagents.py +333 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +51 -42
- ksadk/runtime/adapter.py +1 -0
- ksadk/runtime/factory.py +76 -2
- ksadk/runtime/framework_adapters.py +18 -0
- ksadk/runtime/runner_adapter.py +5 -0
- ksadk/sandbox/__init__.py +2 -1
- ksadk/sandbox/factory.py +72 -0
- ksadk/scheduler/__init__.py +18 -0
- ksadk/scheduler/calendar.py +102 -0
- ksadk/scheduler/contracts.py +254 -0
- ksadk/scheduler/dispatcher.py +129 -0
- ksadk/scheduler/engine.py +416 -0
- ksadk/scheduler/sqlite_store.py +563 -0
- ksadk/server/static/assets/{ArtifactsPanel-DSU1sWD_.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-DOH6MVcn.js → CodeBlock-BXk1l-G1.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-BL8my1R2.js → MathMessageMarkdown-D9uB55Oh.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-Dz4IP-Tx.js → MermaidBlock--OEYoXIJ.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-DUrK0JpZ.js → NativeTerminalPanel-DrEe1syq.js} +1 -1
- ksadk/server/static/assets/{abnfDiagram-VCTEODGH-g20pFzNV.js → abnfDiagram-VCTEODGH-eeTFJLvo.js} +1 -1
- ksadk/server/static/assets/{arc-C4FzinUA.js → arc-S3ZNbIAd.js} +1 -1
- ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-5GKGNRK7-DpIqL5h4.js → architectureDiagram-5GKGNRK7-CRjgEJDX.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-NRAW4CY4-BjkljTNz.js → blockDiagram-NRAW4CY4-C7bsBoNH.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-UCG6FXSJ-Dk_ieq2X.js → c4Diagram-UCG6FXSJ-BX3c33Ob.js} +1 -1
- ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
- ksadk/server/static/assets/{chunk-5VM5RSS4-BVHAchFb.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
- ksadk/server/static/assets/{chunk-F27PBJKO-C-ipQzuS.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
- ksadk/server/static/assets/{chunk-G27WJ6UU-C2EyJbIK.js → chunk-G27WJ6UU-CZwF3oUP.js} +1 -1
- ksadk/server/static/assets/{chunk-JWPE2WC7-vYvVJb_M.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
- ksadk/server/static/assets/{chunk-LCL6LL3I-BcI6ysPT.js → chunk-LCL6LL3I-CnorGV5q.js} +1 -1
- ksadk/server/static/assets/{chunk-POPQ4Y6H-C030x_Z1.js → chunk-POPQ4Y6H-CexntQA-.js} +1 -1
- ksadk/server/static/assets/{chunk-RHFEMEQ7-yyGpsz4n.js → chunk-RHFEMEQ7-CHTC7UxA.js} +1 -1
- ksadk/server/static/assets/{chunk-SVP7TREG-BLTlmMU7.js → chunk-SVP7TREG-BC1EWt_-.js} +1 -1
- ksadk/server/static/assets/{chunk-XXDRQBXY-C_32ArgP.js → chunk-XXDRQBXY-BTG24xN0.js} +1 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-JH36ORCC-9YQYwdl0.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
- ksadk/server/static/assets/{cynefinDiagram-5FMLGOSQ-ErLF5N13.js → cynefinDiagram-5FMLGOSQ-DEOCBe15.js} +1 -1
- ksadk/server/static/assets/{dagre-3AP2YEHR-DFiQF-6f.js → dagre-3AP2YEHR-DBThPuLa.js} +1 -1
- ksadk/server/static/assets/{dagre-BuhGnRI1.js → dagre-ClTPrTht.js} +1 -1
- ksadk/server/static/assets/{diagram-S7CK7UJ4-Bt1v8-GC.js → diagram-S7CK7UJ4-DsZHrnpJ.js} +1 -1
- ksadk/server/static/assets/{diagram-UQ7AKVKN-DSCxJdBK.js → diagram-UQ7AKVKN-BJR4nhVr.js} +1 -1
- ksadk/server/static/assets/{diagram-VSXAHHWV-DHfYwp_9.js → diagram-VSXAHHWV-xzcERX0a.js} +1 -1
- ksadk/server/static/assets/{diagram-VX7I27RA-DdzD-4Le.js → diagram-VX7I27RA-tn2xM1xu.js} +1 -1
- ksadk/server/static/assets/{diagram-Z3DM3KII-Du-nAJ9F.js → diagram-Z3DM3KII-BZPGBkw5.js} +1 -1
- ksadk/server/static/assets/{dist-ClxGviKw.js → dist-4zN1_tq5.js} +1 -1
- ksadk/server/static/assets/{dist-BE7bu-DL.js → dist-8VWI-2bZ.js} +1 -1
- ksadk/server/static/assets/{dist-BX8z72IC.js → dist-B-46q3Hk.js} +1 -1
- ksadk/server/static/assets/{dist-BjU6y-A2.js → dist-B0gdk4rl.js} +1 -1
- ksadk/server/static/assets/{dist-C_wsv-Qd.js → dist-B1oWRmrH.js} +1 -1
- ksadk/server/static/assets/{dist-ngtN0DJB.js → dist-BI1qQH4n.js} +1 -1
- ksadk/server/static/assets/{dist-dydu68Fl.js → dist-BJLUwH6W.js} +1 -1
- ksadk/server/static/assets/{dist-Dq9gLD7L.js → dist-Bbwqa8TG.js} +1 -1
- ksadk/server/static/assets/{dist-CttYUqzS.js → dist-Btyo4vX4.js} +1 -1
- ksadk/server/static/assets/{dist-CXYYBw3_.js → dist-C71CsujO.js} +1 -1
- ksadk/server/static/assets/{dist-dDdADKFA.js → dist-CXpH_Rdd.js} +1 -1
- ksadk/server/static/assets/{dist-CSh_DE14.js → dist-Cq4OL6nO.js} +1 -1
- ksadk/server/static/assets/{dist-8hqcx0sU.js → dist-D1V3f1B4.js} +1 -1
- ksadk/server/static/assets/{dist-BVDffZ0U.js → dist-DJmCPRVB.js} +1 -1
- ksadk/server/static/assets/{dist-DnfXs8Vn.js → dist-DQAB0qn_.js} +1 -1
- ksadk/server/static/assets/{dist-DNp6rLRA.js → dist-DUwKa_l6.js} +1 -1
- ksadk/server/static/assets/{dist-B7seoj3d.js → dist-DdRBIceA.js} +1 -1
- ksadk/server/static/assets/{dist-BytlxIDT.js → dist-DfGFrb0v.js} +1 -1
- ksadk/server/static/assets/{dist-Bf2M8m3N.js → dist-DjhwGUgZ.js} +1 -1
- ksadk/server/static/assets/{dist-W-TIVntx.js → dist-DyqV4fL4.js} +1 -1
- ksadk/server/static/assets/{dist-DUX-id_F.js → dist-M67Hwu0n.js} +1 -1
- ksadk/server/static/assets/{dist-DmldrHd_.js → dist-u8tVH5Ak.js} +1 -1
- ksadk/server/static/assets/{ebnfDiagram-PWID7BFC-Da9C9NO1.js → ebnfDiagram-PWID7BFC-DVfMsLge.js} +1 -1
- ksadk/server/static/assets/{erDiagram-SSCWMZ5O-EAGqqR79.js → erDiagram-SSCWMZ5O-CWEZSYaw.js} +1 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
- ksadk/server/static/assets/{ganttDiagram-EL5Y4UJY-DqJsKb59.js → ganttDiagram-EL5Y4UJY-TWIxoyYL.js} +1 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-WWUBYQGX-B6g4dtDi.js → gitGraphDiagram-WWUBYQGX-DDh2hd2S.js} +1 -1
- ksadk/server/static/assets/index-B2k_urY8.js +241 -0
- ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
- ksadk/server/static/assets/{infoDiagram-RXCK75RN-Djm_nbd5.js → infoDiagram-RXCK75RN-CgoyWiDR.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-5VMMS53U-DWya9SG2.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-EYS64GPL-DutxhSNI.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-3QL26DDD-DJdOF8Fm.js → kanban-definition-3QL26DDD-CNP3ui66.js} +1 -1
- ksadk/server/static/assets/{katex-vFWytM5c.js → katex-DWEzQy1d.js} +1 -1
- ksadk/server/static/assets/{linear-BHjG8b-J.js → linear-C-qHp7d2.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-KGSy4jWT.js → mermaid-parser.core-Cl-K943T.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-FBJOCRG2-9-HC88D4.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +1 -1
- ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
- ksadk/server/static/assets/{pegDiagram-XKGWAZYB-DrEZd4fD.js → pegDiagram-XKGWAZYB-BQVc9ia_.js} +1 -1
- ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
- ksadk/server/static/assets/{pieDiagram-E7YTZNPT-DpjqOFFp.js → pieDiagram-E7YTZNPT-B6UCAvtY.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-AXDQQJYC-BiNhgOXX.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +1 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
- ksadk/server/static/assets/{railroadDiagram-O6MQD6OU-_Y3Ojg6G.js → railroadDiagram-O6MQD6OU-Dt3FFDmK.js} +1 -1
- ksadk/server/static/assets/{requirementDiagram-EFPCY7ZU-DeCFdg9K.js → requirementDiagram-EFPCY7ZU-Cz_vQtgF.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-P5KCCOFB-CPH75rhw.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-WJ2MYXX4-OEpQQdr4.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +1 -1
- ksadk/server/static/assets/{sizeCapture-X5ZJPWSS-heUErzhI.js → sizeCapture-X5ZJPWSS-DXX54iyO.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-HBIQ2CUA-DvA3jSMB.js → stateDiagram-HBIQ2CUA-Dp_lw9II.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
- ksadk/server/static/assets/{swimlanes-XN3QIQJK-CR-dfN_t.js → swimlanes-XN3QIQJK-D8O5UlLG.js} +1 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
- ksadk/server/static/assets/{timeline-definition-24CTP7MA-DAVJf1TX.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +1 -1
- ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
- ksadk/server/static/assets/{vennDiagram-4TSXK5OY-DwM8eDkh.js → vennDiagram-4TSXK5OY-EnNl0DE7.js} +1 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-VM6X3IG4-DZL-Zz2t.js → wardleyDiagram-VM6X3IG4-DBu5fbYr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js → xychartDiagram-S5SC5T6Z-BSVXs4IY.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/skills/service_client.py +48 -0
- ksadk/studio/agent_lifecycle.py +26 -20
- ksadk/studio/api.py +513 -71
- ksadk/studio/api_catalog_routes.py +5 -0
- ksadk/studio/api_contracts.py +34 -1
- ksadk/studio/api_plugin_routes.py +480 -0
- ksadk/studio/attachment_store.py +187 -0
- ksadk/studio/authoring.py +10 -4
- ksadk/studio/authoring_coordinator.py +12 -2
- ksadk/studio/builder.py +123 -43
- ksadk/studio/cloud.py +4 -0
- ksadk/studio/codex_agent_service.py +12 -1
- ksadk/studio/codex_builder.py +54 -15
- ksadk/studio/codex_manifest.py +21 -1
- ksadk/studio/codex_run.py +16 -4
- ksadk/studio/compatibility_report.py +445 -0
- ksadk/studio/compiler.py +6 -1
- ksadk/studio/contracts.py +123 -4
- ksadk/studio/dsh_provider_registration.py +768 -0
- ksadk/studio/event_store.py +54 -6
- ksadk/studio/framework_run.py +37 -4
- ksadk/studio/hosted_kernel.py +1 -1
- ksadk/studio/operations.py +12 -2
- ksadk/studio/plugin_composition.py +301 -0
- ksadk/studio/plugin_kernel_adapter.py +128 -0
- ksadk/studio/plugin_runtime.py +652 -0
- ksadk/studio/resource_catalog.py +6 -1
- ksadk/studio/run_service.py +713 -75
- ksadk/studio/runtime_source.py +9 -1
- ksadk/studio/scheduler_runtime.py +374 -0
- ksadk/studio/scheduler_service.py +211 -0
- ksadk/studio/service.py +672 -82
- ksadk/studio/shared_web.py +167 -28
- ksadk/studio/soul.py +52 -0
- ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
- ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/validator.py +1 -1
- ksadk/studio/workspace.py +1 -0
- ksadk/version.py +1 -1
- ksadk_runtime_common/workspace_files/path_utils.py +41 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +0 -1
- ksadk/server/static/assets/channel-4cQHKtx1.js +0 -1
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +0 -1
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +0 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +0 -1
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +0 -1
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +0 -1
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +0 -1
- ksadk/server/static/assets/index-8ipRcQ-M.js +0 -240
- ksadk/server/static/assets/index-ByFCqjlh.css +0 -2
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +0 -1
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +0 -1
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +0 -1
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +0 -1
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +0 -1
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +0 -1
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +0 -1
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +0 -1
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +0 -8
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +0 -1
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +0 -1
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +0 -1
- ksadk/studio/static/assets/index-BLpcdrgW.css +0 -1
- ksadk/studio/static/assets/index-CYekR2Xv.js +0 -260
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.2.dist-info → agentengine_sdk_python-0.8.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
"""Runtime-native Codex AgentProvider for the controlled PluginHost.
|
|
2
|
+
|
|
3
|
+
The provider only projects an immutable AgentBundle into the existing Codex
|
|
4
|
+
RuntimeAdapter/RuntimeExecutor conversation path. Canonical SessionEvents and
|
|
5
|
+
native thread continuation remain owned by that path; no provider-local event
|
|
6
|
+
stream or transcript is created here.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path, PurePosixPath
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from ksadk.plugins.bundle import ResolvedPluginBundle
|
|
18
|
+
from ksadk.plugins.contracts import CompositionProfile, PluginManifest
|
|
19
|
+
from ksadk.plugins.host import PluginExecutionContext, PluginHostError
|
|
20
|
+
from ksadk.runtime import (
|
|
21
|
+
RuntimeExecutor,
|
|
22
|
+
RuntimeLaunchContext,
|
|
23
|
+
RuntimeServices,
|
|
24
|
+
build_default_runtime_registry,
|
|
25
|
+
)
|
|
26
|
+
from ksadk.runtime.conversation_execution import invoke_runtime_conversation_once
|
|
27
|
+
from ksadk.sessions import create_session_service
|
|
28
|
+
from ksadk.sessions.base import BaseSessionService
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class CodexProviderInventory:
|
|
33
|
+
provider: str
|
|
34
|
+
model: str
|
|
35
|
+
mcp_servers: tuple[str, ...]
|
|
36
|
+
skills: tuple[str, ...]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class CodexTurnRequest:
|
|
41
|
+
user_id: str
|
|
42
|
+
session_id: str | None
|
|
43
|
+
messages: tuple[Mapping[str, Any], ...]
|
|
44
|
+
request_metadata: Mapping[str, Any] | None = None
|
|
45
|
+
invocation_id: str | None = None
|
|
46
|
+
model: str | None = None
|
|
47
|
+
collaboration_mode: str | None = None
|
|
48
|
+
goal_objective: str | None = None
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def parse(cls, value: Any) -> "CodexTurnRequest":
|
|
52
|
+
if isinstance(value, cls):
|
|
53
|
+
return value
|
|
54
|
+
if not isinstance(value, Mapping):
|
|
55
|
+
raise PluginHostError("codex_input_invalid", "Codex input must be an object")
|
|
56
|
+
allowed_fields = {
|
|
57
|
+
"user_id",
|
|
58
|
+
"userId",
|
|
59
|
+
"session_id",
|
|
60
|
+
"sessionId",
|
|
61
|
+
"messages",
|
|
62
|
+
"input",
|
|
63
|
+
"request_metadata",
|
|
64
|
+
"requestMetadata",
|
|
65
|
+
"invocation_id",
|
|
66
|
+
"invocationId",
|
|
67
|
+
"model",
|
|
68
|
+
"collaboration_mode",
|
|
69
|
+
"collaborationMode",
|
|
70
|
+
"goal_objective",
|
|
71
|
+
"goalObjective",
|
|
72
|
+
}
|
|
73
|
+
unsupported = sorted(str(field) for field in value if field not in allowed_fields)
|
|
74
|
+
if unsupported:
|
|
75
|
+
raise PluginHostError(
|
|
76
|
+
"codex_input_unsupported",
|
|
77
|
+
"Codex input contains undeclared fields: " + ", ".join(unsupported),
|
|
78
|
+
)
|
|
79
|
+
_reject_aliased_duplicates(
|
|
80
|
+
value,
|
|
81
|
+
("user_id", "userId"),
|
|
82
|
+
("session_id", "sessionId"),
|
|
83
|
+
("request_metadata", "requestMetadata"),
|
|
84
|
+
("invocation_id", "invocationId"),
|
|
85
|
+
("collaboration_mode", "collaborationMode"),
|
|
86
|
+
("goal_objective", "goalObjective"),
|
|
87
|
+
)
|
|
88
|
+
if "messages" in value and "input" in value:
|
|
89
|
+
raise PluginHostError(
|
|
90
|
+
"codex_input_invalid",
|
|
91
|
+
"Codex input must use exactly one of messages or input",
|
|
92
|
+
)
|
|
93
|
+
user_id = str(value.get("user_id") or value.get("userId") or "").strip()
|
|
94
|
+
if not user_id:
|
|
95
|
+
raise PluginHostError("codex_input_invalid", "Codex input requires user_id")
|
|
96
|
+
raw_messages = value.get("messages")
|
|
97
|
+
if raw_messages is None and value.get("input") is not None:
|
|
98
|
+
raw_messages = ({"role": "user", "content": value.get("input")},)
|
|
99
|
+
if not isinstance(raw_messages, Sequence) or isinstance(
|
|
100
|
+
raw_messages, (str, bytes)
|
|
101
|
+
):
|
|
102
|
+
raise PluginHostError("codex_input_invalid", "Codex input requires messages")
|
|
103
|
+
messages: list[Mapping[str, Any]] = []
|
|
104
|
+
for index, message in enumerate(raw_messages):
|
|
105
|
+
if not isinstance(message, Mapping):
|
|
106
|
+
raise PluginHostError(
|
|
107
|
+
"codex_input_invalid", f"Codex messages[{index}] must be an object"
|
|
108
|
+
)
|
|
109
|
+
role = str(message.get("role") or "").strip()
|
|
110
|
+
if role not in {"system", "user", "assistant", "tool"}:
|
|
111
|
+
raise PluginHostError(
|
|
112
|
+
"codex_input_invalid",
|
|
113
|
+
f"Codex messages[{index}] has unsupported role {role!r}",
|
|
114
|
+
)
|
|
115
|
+
messages.append(dict(message))
|
|
116
|
+
if not messages or messages[-1].get("role") != "user":
|
|
117
|
+
raise PluginHostError(
|
|
118
|
+
"codex_input_invalid", "Codex turn must end with a user message"
|
|
119
|
+
)
|
|
120
|
+
metadata = value.get("request_metadata") or value.get("requestMetadata")
|
|
121
|
+
if metadata is not None and not isinstance(metadata, Mapping):
|
|
122
|
+
raise PluginHostError(
|
|
123
|
+
"codex_input_invalid", "request_metadata must be an object"
|
|
124
|
+
)
|
|
125
|
+
session_id = str(
|
|
126
|
+
value.get("session_id") or value.get("sessionId") or ""
|
|
127
|
+
).strip()
|
|
128
|
+
invocation_id = str(
|
|
129
|
+
value.get("invocation_id") or value.get("invocationId") or ""
|
|
130
|
+
).strip()
|
|
131
|
+
if len(invocation_id) > 256:
|
|
132
|
+
raise PluginHostError(
|
|
133
|
+
"codex_input_invalid", "Codex invocation_id exceeds 256 characters"
|
|
134
|
+
)
|
|
135
|
+
model = str(value.get("model") or "").strip()
|
|
136
|
+
if len(model) > 256:
|
|
137
|
+
raise PluginHostError(
|
|
138
|
+
"codex_input_invalid", "Codex model exceeds 256 characters"
|
|
139
|
+
)
|
|
140
|
+
collaboration_mode = (
|
|
141
|
+
str(value.get("collaboration_mode") or value.get("collaborationMode") or "")
|
|
142
|
+
.strip()
|
|
143
|
+
.lower()
|
|
144
|
+
)
|
|
145
|
+
if collaboration_mode and collaboration_mode not in {"default", "plan"}:
|
|
146
|
+
raise PluginHostError(
|
|
147
|
+
"codex_input_unsupported",
|
|
148
|
+
"Codex collaboration_mode must be default or plan",
|
|
149
|
+
)
|
|
150
|
+
goal_objective = str(
|
|
151
|
+
value.get("goal_objective") or value.get("goalObjective") or ""
|
|
152
|
+
).strip()
|
|
153
|
+
if len(goal_objective) > 4096:
|
|
154
|
+
raise PluginHostError(
|
|
155
|
+
"codex_input_invalid", "Codex goal_objective exceeds 4096 characters"
|
|
156
|
+
)
|
|
157
|
+
return cls(
|
|
158
|
+
user_id=user_id,
|
|
159
|
+
session_id=session_id or None,
|
|
160
|
+
messages=tuple(messages),
|
|
161
|
+
request_metadata=dict(metadata) if metadata is not None else None,
|
|
162
|
+
invocation_id=invocation_id or None,
|
|
163
|
+
model=model or None,
|
|
164
|
+
collaboration_mode=collaboration_mode or None,
|
|
165
|
+
goal_objective=goal_objective or None,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class CodexTurnResult:
|
|
171
|
+
session_id: str
|
|
172
|
+
output_text: str
|
|
173
|
+
usage: Mapping[str, Any]
|
|
174
|
+
metadata: Mapping[str, Any]
|
|
175
|
+
inventory: CodexProviderInventory
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass(frozen=True)
|
|
179
|
+
class _CodexBundleConfig:
|
|
180
|
+
model: str
|
|
181
|
+
allowed_models: tuple[str, ...]
|
|
182
|
+
prompt: str
|
|
183
|
+
project_dir: Path
|
|
184
|
+
launch_config: Mapping[str, Any]
|
|
185
|
+
inventory: CodexProviderInventory
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class CodexAgentProviderRuntime:
|
|
189
|
+
"""ExecutableAgentProvider that preserves Codex native loop ownership."""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
*,
|
|
194
|
+
plugin_id: str,
|
|
195
|
+
session_service: BaseSessionService,
|
|
196
|
+
codex_client_factory: Callable[..., Any] | None,
|
|
197
|
+
credential_resolver: Any = None,
|
|
198
|
+
) -> None:
|
|
199
|
+
self._plugin_id = plugin_id
|
|
200
|
+
self._session_service = session_service
|
|
201
|
+
self._client_factory = codex_client_factory
|
|
202
|
+
self._credentials = credential_resolver
|
|
203
|
+
self._ready = False
|
|
204
|
+
self._disposed = False
|
|
205
|
+
self._last_activation: CodexAgentActivation | None = None
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def disposed(self) -> bool:
|
|
209
|
+
return self._disposed
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def last_activation(self) -> "CodexAgentActivation | None":
|
|
213
|
+
return self._last_activation
|
|
214
|
+
|
|
215
|
+
async def start(self) -> None:
|
|
216
|
+
if self._disposed:
|
|
217
|
+
raise RuntimeError("Codex provider is disposed")
|
|
218
|
+
self._ready = True
|
|
219
|
+
|
|
220
|
+
async def health(self) -> bool:
|
|
221
|
+
return self._ready and not self._disposed
|
|
222
|
+
|
|
223
|
+
async def drain(self) -> None:
|
|
224
|
+
self._ready = False
|
|
225
|
+
|
|
226
|
+
async def dispose(self) -> None:
|
|
227
|
+
self._ready = False
|
|
228
|
+
self._disposed = True
|
|
229
|
+
|
|
230
|
+
async def prepare(
|
|
231
|
+
self,
|
|
232
|
+
bundle: ResolvedPluginBundle,
|
|
233
|
+
*,
|
|
234
|
+
capabilities: PluginExecutionContext,
|
|
235
|
+
) -> "CodexAgentActivation":
|
|
236
|
+
if not self._ready or self._disposed:
|
|
237
|
+
raise PluginHostError(
|
|
238
|
+
"codex_provider_unavailable", "Codex provider is not ready"
|
|
239
|
+
)
|
|
240
|
+
_reject_external_execution(bundle, capabilities)
|
|
241
|
+
config = _resolve_bundle_config(
|
|
242
|
+
bundle,
|
|
243
|
+
plugin_id=self._plugin_id,
|
|
244
|
+
credential_resolver=self._credentials,
|
|
245
|
+
)
|
|
246
|
+
activation = CodexAgentActivation(
|
|
247
|
+
bundle=bundle,
|
|
248
|
+
config=config,
|
|
249
|
+
session_service=self._session_service,
|
|
250
|
+
codex_client_factory=self._client_factory,
|
|
251
|
+
)
|
|
252
|
+
self._last_activation = activation
|
|
253
|
+
return activation
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class CodexAgentProviderFactory:
|
|
257
|
+
def __init__(
|
|
258
|
+
self,
|
|
259
|
+
*,
|
|
260
|
+
session_service: BaseSessionService | None = None,
|
|
261
|
+
codex_client_factory: Callable[..., Any] | None = None,
|
|
262
|
+
credential_resolver: Any = None,
|
|
263
|
+
) -> None:
|
|
264
|
+
self._session_service = session_service
|
|
265
|
+
self._client_factory = codex_client_factory
|
|
266
|
+
self._credentials = credential_resolver
|
|
267
|
+
self.runtime: CodexAgentProviderRuntime | None = None
|
|
268
|
+
|
|
269
|
+
async def stage(
|
|
270
|
+
self,
|
|
271
|
+
manifest: PluginManifest,
|
|
272
|
+
*,
|
|
273
|
+
profile: CompositionProfile,
|
|
274
|
+
services: Mapping[str, Any],
|
|
275
|
+
) -> CodexAgentProviderRuntime:
|
|
276
|
+
del profile
|
|
277
|
+
service = self._session_service or services.get("session_service")
|
|
278
|
+
if service is None:
|
|
279
|
+
service = create_session_service(backend="memory")
|
|
280
|
+
if not isinstance(service, BaseSessionService):
|
|
281
|
+
raise PluginHostError(
|
|
282
|
+
"codex_session_service_invalid",
|
|
283
|
+
"Codex provider requires a BaseSessionService",
|
|
284
|
+
)
|
|
285
|
+
client_factory = self._client_factory or services.get("codex_client_factory")
|
|
286
|
+
credentials = self._credentials or services.get("credential_resolver")
|
|
287
|
+
self.runtime = CodexAgentProviderRuntime(
|
|
288
|
+
plugin_id=manifest.metadata.id,
|
|
289
|
+
session_service=service,
|
|
290
|
+
codex_client_factory=client_factory,
|
|
291
|
+
credential_resolver=credentials,
|
|
292
|
+
)
|
|
293
|
+
return self.runtime
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class CodexAgentActivation:
|
|
297
|
+
def __init__(
|
|
298
|
+
self,
|
|
299
|
+
*,
|
|
300
|
+
bundle: ResolvedPluginBundle,
|
|
301
|
+
config: _CodexBundleConfig,
|
|
302
|
+
session_service: BaseSessionService,
|
|
303
|
+
codex_client_factory: Callable[..., Any] | None,
|
|
304
|
+
) -> None:
|
|
305
|
+
self._bundle = bundle
|
|
306
|
+
self._config = config
|
|
307
|
+
self._session_service = session_service
|
|
308
|
+
self._executor = RuntimeExecutor(build_default_runtime_registry())
|
|
309
|
+
self._launch_context = RuntimeLaunchContext(
|
|
310
|
+
runtime_type="codex",
|
|
311
|
+
project_dir=config.project_dir,
|
|
312
|
+
config=config.launch_config,
|
|
313
|
+
services=RuntimeServices(codex_client_factory=codex_client_factory),
|
|
314
|
+
)
|
|
315
|
+
self._ready = False
|
|
316
|
+
self._disposed = False
|
|
317
|
+
self._kernel_adapters: list[Any] = []
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def disposed(self) -> bool:
|
|
321
|
+
return self._disposed
|
|
322
|
+
|
|
323
|
+
async def start(self) -> None:
|
|
324
|
+
if self._disposed:
|
|
325
|
+
raise RuntimeError("Codex activation is disposed")
|
|
326
|
+
self._ready = True
|
|
327
|
+
|
|
328
|
+
async def health(self) -> bool:
|
|
329
|
+
return self._ready and not self._disposed
|
|
330
|
+
|
|
331
|
+
async def execute(self, request: Any) -> CodexTurnResult:
|
|
332
|
+
if not self._ready or self._disposed:
|
|
333
|
+
raise PluginHostError(
|
|
334
|
+
"codex_activation_unavailable", "Codex activation is not ready"
|
|
335
|
+
)
|
|
336
|
+
turn = CodexTurnRequest.parse(request)
|
|
337
|
+
selected_model = turn.model or self._config.model
|
|
338
|
+
if selected_model not in self._config.allowed_models:
|
|
339
|
+
raise PluginHostError(
|
|
340
|
+
"codex_model_unsupported",
|
|
341
|
+
f"Codex model {selected_model!r} is not declared by the immutable Bundle",
|
|
342
|
+
)
|
|
343
|
+
launch_context = self._turn_launch_context(turn)
|
|
344
|
+
preparation = await self._executor.prepare_start(launch_context)
|
|
345
|
+
session_id, result = await invoke_runtime_conversation_once(
|
|
346
|
+
executor=self._executor,
|
|
347
|
+
launch_context=launch_context,
|
|
348
|
+
agent_id=self._bundle.manifest.agent_id,
|
|
349
|
+
user_id=turn.user_id,
|
|
350
|
+
messages=[dict(item) for item in turn.messages],
|
|
351
|
+
session_id=turn.session_id,
|
|
352
|
+
model=selected_model,
|
|
353
|
+
instructions=self._config.prompt,
|
|
354
|
+
request_metadata=turn.request_metadata,
|
|
355
|
+
invocation_id=turn.invocation_id,
|
|
356
|
+
session_service_provider=lambda: self._session_service,
|
|
357
|
+
runtime_preparation=preparation,
|
|
358
|
+
)
|
|
359
|
+
return CodexTurnResult(
|
|
360
|
+
session_id=session_id,
|
|
361
|
+
output_text=str(result.get("output_text") or ""),
|
|
362
|
+
usage=dict(result.get("usage") or {}),
|
|
363
|
+
metadata=dict(result.get("metadata") or {}),
|
|
364
|
+
inventory=CodexProviderInventory(
|
|
365
|
+
provider=self._config.inventory.provider,
|
|
366
|
+
model=selected_model,
|
|
367
|
+
mcp_servers=self._config.inventory.mcp_servers,
|
|
368
|
+
skills=self._config.inventory.skills,
|
|
369
|
+
),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def runtime_adapter(self) -> Any:
|
|
373
|
+
"""Create one activation-owned native adapter for AgentKernel.
|
|
374
|
+
|
|
375
|
+
Each Kernel command receives a fresh App Server transport. Durable
|
|
376
|
+
SessionEvent metadata reconnects the next command to the same Codex
|
|
377
|
+
Thread, while retaining adapters here lets activation disposal close a
|
|
378
|
+
command that is still active during Profile drain.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
if not self._ready or self._disposed:
|
|
382
|
+
raise PluginHostError(
|
|
383
|
+
"codex_activation_unavailable", "Codex activation is not ready"
|
|
384
|
+
)
|
|
385
|
+
adapter = self._executor.create_adapter(self._launch_context)
|
|
386
|
+
self._kernel_adapters.append(adapter)
|
|
387
|
+
return adapter
|
|
388
|
+
|
|
389
|
+
def _turn_launch_context(self, turn: CodexTurnRequest) -> RuntimeLaunchContext:
|
|
390
|
+
config = dict(self._config.launch_config)
|
|
391
|
+
if turn.model:
|
|
392
|
+
config["model"] = turn.model
|
|
393
|
+
if turn.collaboration_mode:
|
|
394
|
+
config["collaboration_mode"] = turn.collaboration_mode
|
|
395
|
+
if turn.goal_objective:
|
|
396
|
+
config["goal_objective"] = turn.goal_objective
|
|
397
|
+
return RuntimeLaunchContext(
|
|
398
|
+
runtime_type=self._launch_context.runtime_type,
|
|
399
|
+
project_dir=self._launch_context.project_dir,
|
|
400
|
+
config=config,
|
|
401
|
+
services=self._launch_context.services,
|
|
402
|
+
deployment_mode=self._launch_context.deployment_mode,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
async def drain(self) -> None:
|
|
406
|
+
self._ready = False
|
|
407
|
+
|
|
408
|
+
async def dispose(self) -> None:
|
|
409
|
+
self._ready = False
|
|
410
|
+
first_error: BaseException | None = None
|
|
411
|
+
for adapter in reversed(self._kernel_adapters):
|
|
412
|
+
close_all = getattr(adapter, "close_all", None)
|
|
413
|
+
if not callable(close_all):
|
|
414
|
+
continue
|
|
415
|
+
try:
|
|
416
|
+
await close_all()
|
|
417
|
+
except BaseException as error: # cleanup must continue
|
|
418
|
+
if first_error is None:
|
|
419
|
+
first_error = error
|
|
420
|
+
self._kernel_adapters.clear()
|
|
421
|
+
try:
|
|
422
|
+
await self._executor.close_all()
|
|
423
|
+
except BaseException as error: # cleanup must continue
|
|
424
|
+
if first_error is None:
|
|
425
|
+
first_error = error
|
|
426
|
+
self._disposed = True
|
|
427
|
+
if first_error is not None:
|
|
428
|
+
raise first_error
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _reject_aliased_duplicates(
|
|
432
|
+
value: Mapping[str, Any],
|
|
433
|
+
*aliases: tuple[str, str],
|
|
434
|
+
) -> None:
|
|
435
|
+
for snake_case, camel_case in aliases:
|
|
436
|
+
if snake_case in value and camel_case in value:
|
|
437
|
+
raise PluginHostError(
|
|
438
|
+
"codex_input_invalid",
|
|
439
|
+
f"Codex input cannot contain both {snake_case} and {camel_case}",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _reject_external_execution(
|
|
444
|
+
bundle: ResolvedPluginBundle,
|
|
445
|
+
capabilities: PluginExecutionContext,
|
|
446
|
+
) -> None:
|
|
447
|
+
del capabilities
|
|
448
|
+
profile_config = bundle.composition.profile.agent_provider.config
|
|
449
|
+
allowed_config = {"runtimeType", "runtimeVersion"}
|
|
450
|
+
unsupported_config = sorted(set(profile_config) - allowed_config)
|
|
451
|
+
execution = bundle.resolved_agent_spec.get("execution")
|
|
452
|
+
strategy = (
|
|
453
|
+
str(execution.get("strategy") or "direct").strip()
|
|
454
|
+
if isinstance(execution, Mapping)
|
|
455
|
+
else "direct"
|
|
456
|
+
)
|
|
457
|
+
if unsupported_config:
|
|
458
|
+
raise PluginHostError(
|
|
459
|
+
"codex_provider_config_unsupported",
|
|
460
|
+
"Codex AgentProvider received unsupported configuration: "
|
|
461
|
+
+ ", ".join(unsupported_config),
|
|
462
|
+
)
|
|
463
|
+
if strategy != "direct":
|
|
464
|
+
raise PluginHostError(
|
|
465
|
+
"codex_external_execution_unsupported",
|
|
466
|
+
f"Codex AgentProvider owns its execution semantics and does not support {strategy!r}",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _resolve_bundle_config(
|
|
471
|
+
bundle: ResolvedPluginBundle,
|
|
472
|
+
*,
|
|
473
|
+
plugin_id: str,
|
|
474
|
+
credential_resolver: Any,
|
|
475
|
+
) -> _CodexBundleConfig:
|
|
476
|
+
spec = bundle.resolved_agent_spec
|
|
477
|
+
raw_model = spec.get("model")
|
|
478
|
+
if not isinstance(raw_model, Mapping):
|
|
479
|
+
raise PluginHostError(
|
|
480
|
+
"codex_bundle_model_invalid", "Bundle resolved model must be an object"
|
|
481
|
+
)
|
|
482
|
+
model = str(raw_model.get("model") or "").strip()
|
|
483
|
+
if not model:
|
|
484
|
+
raise PluginHostError(
|
|
485
|
+
"codex_bundle_model_missing", "Bundle resolved Agent spec has no model"
|
|
486
|
+
)
|
|
487
|
+
allowed_models = _resolve_allowed_models(bundle, default_model=model)
|
|
488
|
+
instructions = spec.get("instructions")
|
|
489
|
+
if not isinstance(instructions, Mapping):
|
|
490
|
+
raise PluginHostError(
|
|
491
|
+
"codex_bundle_prompt_invalid", "Bundle instructions must be an object"
|
|
492
|
+
)
|
|
493
|
+
prompt = "\n\n".join(
|
|
494
|
+
part
|
|
495
|
+
for part in (
|
|
496
|
+
str(instructions.get("system") or "").strip(),
|
|
497
|
+
str(instructions.get("task") or "").strip(),
|
|
498
|
+
)
|
|
499
|
+
if part
|
|
500
|
+
)
|
|
501
|
+
if not prompt:
|
|
502
|
+
raise PluginHostError(
|
|
503
|
+
"codex_bundle_prompt_missing", "Bundle resolved Agent spec has no instructions"
|
|
504
|
+
)
|
|
505
|
+
capabilities = spec.get("capabilities")
|
|
506
|
+
if not isinstance(capabilities, Mapping):
|
|
507
|
+
raise PluginHostError(
|
|
508
|
+
"codex_bundle_capabilities_invalid", "Bundle capabilities must be an object"
|
|
509
|
+
)
|
|
510
|
+
if capabilities.get("tools"):
|
|
511
|
+
raise PluginHostError(
|
|
512
|
+
"codex_tools_unsupported", "Codex only accepts its native tools, MCP, and Skills"
|
|
513
|
+
)
|
|
514
|
+
skills = _resolve_skills(bundle.root, capabilities.get("skills"))
|
|
515
|
+
mcp_servers, env = _resolve_mcp(
|
|
516
|
+
capabilities.get("mcpServers") or capabilities.get("mcp_servers"),
|
|
517
|
+
credential_resolver=credential_resolver,
|
|
518
|
+
)
|
|
519
|
+
execution = spec.get("execution")
|
|
520
|
+
execution = execution if isinstance(execution, Mapping) else {}
|
|
521
|
+
project_dir = bundle.root / "runtime"
|
|
522
|
+
if not project_dir.is_dir():
|
|
523
|
+
project_dir = bundle.root
|
|
524
|
+
launch_config: dict[str, Any] = {
|
|
525
|
+
"model": model,
|
|
526
|
+
"models": list(allowed_models),
|
|
527
|
+
"prompt": str(instructions.get("system") or "").strip(),
|
|
528
|
+
"task_prompt": str(instructions.get("task") or "").strip(),
|
|
529
|
+
"sandbox": str(execution.get("sandbox") or "read_only"),
|
|
530
|
+
"approval_mode": str(execution.get("approvalMode") or execution.get("approval_mode") or ""),
|
|
531
|
+
"turn_timeout_seconds": int(
|
|
532
|
+
execution.get("timeoutSeconds") or execution.get("timeout_seconds") or 120
|
|
533
|
+
),
|
|
534
|
+
"mcp_servers": mcp_servers,
|
|
535
|
+
"skills": skills,
|
|
536
|
+
"env": env,
|
|
537
|
+
}
|
|
538
|
+
return _CodexBundleConfig(
|
|
539
|
+
model=model,
|
|
540
|
+
allowed_models=allowed_models,
|
|
541
|
+
prompt=prompt,
|
|
542
|
+
project_dir=project_dir,
|
|
543
|
+
launch_config=launch_config,
|
|
544
|
+
inventory=CodexProviderInventory(
|
|
545
|
+
provider=plugin_id,
|
|
546
|
+
model=model,
|
|
547
|
+
mcp_servers=tuple(item["name"] for item in mcp_servers),
|
|
548
|
+
skills=tuple(item["name"] for item in skills),
|
|
549
|
+
),
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _resolve_allowed_models(
|
|
554
|
+
bundle: ResolvedPluginBundle,
|
|
555
|
+
*,
|
|
556
|
+
default_model: str,
|
|
557
|
+
) -> tuple[str, ...]:
|
|
558
|
+
"""Read the immutable model allowlist produced by the Bundle compiler.
|
|
559
|
+
|
|
560
|
+
Older Bundle fixtures without ``runtime-lock.json`` remain pinned to their
|
|
561
|
+
resolved default. A declared lock is already digest-verified by
|
|
562
|
+
``PluginBundleResolver``; malformed model inventory must fail activation
|
|
563
|
+
instead of silently widening run-level model selection.
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
if not any(
|
|
567
|
+
entry.path == "runtime-lock.json" for entry in bundle.manifest.files
|
|
568
|
+
):
|
|
569
|
+
return (default_model,)
|
|
570
|
+
try:
|
|
571
|
+
payload = json.loads(
|
|
572
|
+
(bundle.root / "runtime-lock.json").read_text(encoding="utf-8")
|
|
573
|
+
)
|
|
574
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as error:
|
|
575
|
+
raise PluginHostError(
|
|
576
|
+
"codex_bundle_model_inventory_invalid",
|
|
577
|
+
"Bundle runtime-lock.json is not a valid model inventory",
|
|
578
|
+
) from error
|
|
579
|
+
if not isinstance(payload, Mapping):
|
|
580
|
+
raise PluginHostError(
|
|
581
|
+
"codex_bundle_model_inventory_invalid",
|
|
582
|
+
"Bundle runtime-lock.json must be an object",
|
|
583
|
+
)
|
|
584
|
+
raw_models = payload.get("models")
|
|
585
|
+
if not isinstance(raw_models, Sequence) or isinstance(raw_models, (str, bytes)):
|
|
586
|
+
raise PluginHostError(
|
|
587
|
+
"codex_bundle_model_inventory_invalid",
|
|
588
|
+
"Bundle runtime-lock.json must declare models as a list",
|
|
589
|
+
)
|
|
590
|
+
models = tuple(
|
|
591
|
+
dict.fromkeys(str(item).strip() for item in raw_models if str(item).strip())
|
|
592
|
+
)
|
|
593
|
+
if not models or default_model not in models:
|
|
594
|
+
raise PluginHostError(
|
|
595
|
+
"codex_bundle_model_inventory_invalid",
|
|
596
|
+
"Bundle model inventory must include its resolved default model",
|
|
597
|
+
)
|
|
598
|
+
return models
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _resolve_skills(root: Path, value: Any) -> list[dict[str, str]]:
|
|
602
|
+
if value is None:
|
|
603
|
+
return []
|
|
604
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
605
|
+
raise PluginHostError("codex_skill_invalid", "Bundle Skills must be a list")
|
|
606
|
+
resolved: list[dict[str, str]] = []
|
|
607
|
+
seen: set[str] = set()
|
|
608
|
+
for index, item in enumerate(value):
|
|
609
|
+
if not isinstance(item, Mapping):
|
|
610
|
+
raise PluginHostError(
|
|
611
|
+
"codex_skill_invalid", f"Bundle Skills[{index}] must be an object"
|
|
612
|
+
)
|
|
613
|
+
name = str(item.get("name") or "").strip()
|
|
614
|
+
relative = PurePosixPath(str(item.get("bundlePath") or item.get("bundle_path") or ""))
|
|
615
|
+
if not name or not relative.parts or relative.is_absolute() or ".." in relative.parts:
|
|
616
|
+
raise PluginHostError(
|
|
617
|
+
"codex_skill_invalid", f"Bundle Skills[{index}] has an invalid native path"
|
|
618
|
+
)
|
|
619
|
+
path = (root / Path(*relative.parts)).resolve()
|
|
620
|
+
try:
|
|
621
|
+
path.relative_to(root)
|
|
622
|
+
except ValueError as error:
|
|
623
|
+
raise PluginHostError(
|
|
624
|
+
"codex_skill_invalid", f"Bundle Skill {name!r} escapes the Bundle root"
|
|
625
|
+
) from error
|
|
626
|
+
if not (path / "SKILL.md").is_file():
|
|
627
|
+
raise PluginHostError(
|
|
628
|
+
"codex_skill_missing", f"Bundle Skill {name!r} has no SKILL.md"
|
|
629
|
+
)
|
|
630
|
+
if name in seen:
|
|
631
|
+
raise PluginHostError("codex_skill_invalid", f"duplicate Bundle Skill {name!r}")
|
|
632
|
+
seen.add(name)
|
|
633
|
+
# Codex's native SkillInput expects the concrete SKILL.md source, not
|
|
634
|
+
# its containing directory. A directory is accepted by the Python
|
|
635
|
+
# model but silently omitted by the real App Server turn projection.
|
|
636
|
+
resolved.append({"name": name, "path": str(path / "SKILL.md")})
|
|
637
|
+
return resolved
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _resolve_mcp(
|
|
641
|
+
value: Any,
|
|
642
|
+
*,
|
|
643
|
+
credential_resolver: Any,
|
|
644
|
+
) -> tuple[list[dict[str, str]], dict[str, str]]:
|
|
645
|
+
if value is None:
|
|
646
|
+
return [], {}
|
|
647
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
648
|
+
raise PluginHostError("codex_mcp_invalid", "Bundle MCP servers must be a list")
|
|
649
|
+
servers: list[dict[str, str]] = []
|
|
650
|
+
env: dict[str, str] = {}
|
|
651
|
+
seen: set[str] = set()
|
|
652
|
+
for index, item in enumerate(value):
|
|
653
|
+
if not isinstance(item, Mapping):
|
|
654
|
+
raise PluginHostError(
|
|
655
|
+
"codex_mcp_invalid", f"Bundle MCP servers[{index}] must be an object"
|
|
656
|
+
)
|
|
657
|
+
name = str(item.get("name") or "").strip()
|
|
658
|
+
transport = str(item.get("transport") or "").strip().lower()
|
|
659
|
+
url = str(item.get("endpointUrl") or item.get("endpoint_url") or "").strip()
|
|
660
|
+
if not name or name in seen:
|
|
661
|
+
raise PluginHostError("codex_mcp_invalid", "Bundle MCP names must be unique")
|
|
662
|
+
if transport not in {"http", "sse"} or not url:
|
|
663
|
+
raise PluginHostError(
|
|
664
|
+
"codex_mcp_transport_unsupported",
|
|
665
|
+
f"Codex Bundle MCP {name!r} requires an http/sse endpoint",
|
|
666
|
+
)
|
|
667
|
+
env_refs = item.get("envRefs") or item.get("env_refs") or {}
|
|
668
|
+
if not isinstance(env_refs, Mapping):
|
|
669
|
+
raise PluginHostError(
|
|
670
|
+
"codex_mcp_invalid", f"Bundle MCP {name!r} envRefs must be an object"
|
|
671
|
+
)
|
|
672
|
+
if len(env_refs) > 1:
|
|
673
|
+
raise PluginHostError(
|
|
674
|
+
"codex_mcp_credentials_unsupported",
|
|
675
|
+
f"Codex HTTP MCP {name!r} accepts at most one bearer credential",
|
|
676
|
+
)
|
|
677
|
+
server = {"name": name, "url": url}
|
|
678
|
+
for env_name, reference in env_refs.items():
|
|
679
|
+
env_key = str(env_name).strip()
|
|
680
|
+
ref = str(reference).strip()
|
|
681
|
+
if not env_key or not ref.startswith(
|
|
682
|
+
("env://", "secret://", "credential://", "vault://")
|
|
683
|
+
):
|
|
684
|
+
raise PluginHostError(
|
|
685
|
+
"codex_mcp_credential_invalid",
|
|
686
|
+
f"Bundle MCP {name!r} contains an invalid credential reference",
|
|
687
|
+
)
|
|
688
|
+
if credential_resolver is None:
|
|
689
|
+
raise PluginHostError(
|
|
690
|
+
"codex_mcp_credential_unavailable",
|
|
691
|
+
f"Bundle MCP {name!r} requires a credential resolver",
|
|
692
|
+
)
|
|
693
|
+
try:
|
|
694
|
+
value = (
|
|
695
|
+
credential_resolver.resolve(ref)
|
|
696
|
+
if hasattr(credential_resolver, "resolve")
|
|
697
|
+
else credential_resolver(ref)
|
|
698
|
+
)
|
|
699
|
+
except Exception as error:
|
|
700
|
+
raise PluginHostError(
|
|
701
|
+
"codex_mcp_credential_unavailable",
|
|
702
|
+
f"Bundle MCP {name!r} credential could not be resolved",
|
|
703
|
+
) from error
|
|
704
|
+
if not str(value):
|
|
705
|
+
raise PluginHostError(
|
|
706
|
+
"codex_mcp_credential_unavailable",
|
|
707
|
+
f"Bundle MCP {name!r} credential is empty",
|
|
708
|
+
)
|
|
709
|
+
env[env_key] = str(value)
|
|
710
|
+
server["env_key"] = env_key
|
|
711
|
+
servers.append(server)
|
|
712
|
+
seen.add(name)
|
|
713
|
+
return servers, env
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
__all__ = [
|
|
717
|
+
"CodexAgentProviderFactory",
|
|
718
|
+
"CodexAgentProviderRuntime",
|
|
719
|
+
"CodexProviderInventory",
|
|
720
|
+
"CodexTurnRequest",
|
|
721
|
+
"CodexTurnResult",
|
|
722
|
+
]
|