agentengine-sdk-python 0.8.1__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.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +179 -63
- ksadk/a2a/space_client.py +6 -333
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +443 -259
- ksadk/api/client.py +525 -27
- ksadk/builders/code_builder.py +284 -8
- ksadk/builders/container_builder.py +8 -1
- ksadk/builders/managed_runtime_builder.py +39 -35
- ksadk/cli/__init__.py +17 -0
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +10 -2
- ksadk/cli/cmd_eval.py +66 -9
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +1 -126
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +1 -435
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_studio.py +99 -8
- ksadk/cli/cmd_web.py +10 -2
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/codex/client.py +180 -30
- ksadk/codex/runtime.py +428 -326
- ksadk/configs/env_registry.py +66 -12
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +5 -0
- ksadk/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/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 +184 -2
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +141 -0
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +272 -2
- ksadk/conversations/runtime_resume.py +20 -1
- ksadk/conversations/runtime_stream_events.py +94 -9
- ksadk/conversations/semantic_summary.py +311 -1
- ksadk/conversations/session_lock.py +51 -0
- ksadk/deployment/env_forward.py +72 -0
- ksadk/deployment/managed_runtime.py +5 -0
- ksadk/deployment/providers/serverless.py +143 -69
- ksadk/evaluation/__init__.py +32 -0
- ksadk/evaluation/a2a_adapter.py +9 -1
- ksadk/evaluation/adapters.py +18 -5
- ksadk/evaluation/agent_eval_client.py +395 -0
- ksadk/evaluation/cloud_binding.py +80 -0
- ksadk/evaluation/cloud_converter.py +194 -0
- ksadk/evaluation/cloud_service.py +199 -0
- ksadk/evaluation/contracts.py +73 -4
- ksadk/evaluation/evaluators.py +183 -17
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +31 -6
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +15 -6
- ksadk/events/__init__.py +13 -13
- ksadk/events/_v1_compat/__init__.py +1 -0
- ksadk/events/_v1_compat/models.py +299 -0
- ksadk/events/_v1_compat/parser.py +247 -0
- ksadk/events/_v1_compat/projection.py +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 -265
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/runtime.py +160 -57
- 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 +7 -0
- ksadk/knowledge_base/service.py +32 -5
- ksadk/memory/__init__.py +61 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
- ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
- ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- ksadk/memory/coordinator.py +461 -0
- ksadk/memory/events.py +202 -0
- ksadk/memory/extraction.py +228 -0
- ksadk/memory/ltm_backend_factory.py +16 -3
- ksadk/memory/models.py +316 -0
- ksadk/memory/policy.py +215 -0
- ksadk/memory/provider.py +47 -0
- ksadk/memory/provider_adapter.py +159 -0
- ksadk/memory/provider_resolver.py +71 -0
- ksadk/memory/providers/__init__.py +5 -0
- ksadk/memory/providers/local_sqlite.py +490 -0
- ksadk/memory/resolved_policy.py +145 -0
- ksadk/memory/service.py +188 -6
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +39 -15
- ksadk/model_proxy/server.py +24 -5
- ksadk/model_proxy/transform.py +60 -2
- ksadk/observability/__init__.py +25 -0
- ksadk/observability/session_log.py +420 -0
- ksadk/observability/trajectory.py +105 -0
- ksadk/prompts/__init__.py +71 -0
- ksadk/prompts/compiler.py +215 -0
- ksadk/prompts/models.py +82 -0
- ksadk/prompts/projection.py +89 -0
- ksadk/prompts/resolved.py +137 -0
- ksadk/prompts/sources.py +233 -0
- ksadk/runners/_langgraph_runner_streams.py +822 -0
- ksadk/runners/adk_runner.py +5 -5
- ksadk/runners/base_runner.py +16 -0
- ksadk/runners/factory.py +1 -2
- ksadk/runners/langgraph_runner.py +198 -437
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +118 -2
- ksadk/runtime/conversation_execution.py +316 -182
- ksadk/runtime/executor.py +144 -3
- ksadk/runtime/factory.py +193 -7
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +14 -1
- ksadk/runtime/preprocessing.py +25 -3
- ksadk/runtime/runner_adapter.py +288 -508
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +35 -2
- ksadk/server/composition.py +8 -0
- ksadk/server/factory.py +69 -6
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/openai_compat.py +88 -8
- ksadk/server/routes/projection.py +35 -20
- ksadk/server/routes/run.py +94 -0
- ksadk/server/routes/sessions.py +2 -0
- ksadk/server/routes/streaming.py +5 -2
- ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.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-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.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-ChNs_Mdk.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-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-CmZHzlwZ.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-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-hdBcYzM8.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-Bjy_2v4_.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-BmkRbwCg.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-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.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-DEz24vV0.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/studio/api.py +577 -25
- ksadk/studio/api_contracts.py +114 -5
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +252 -27
- ksadk/studio/authoring_coordinator.py +799 -67
- ksadk/studio/builder.py +88 -11
- ksadk/studio/capabilities.py +17 -0
- ksadk/studio/cloud.py +1916 -95
- ksadk/studio/codex_agent_service.py +125 -14
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +138 -25
- ksadk/studio/codex_manifest.py +51 -22
- ksadk/studio/codex_run.py +46 -3
- ksadk/studio/compiler.py +1 -0
- ksadk/studio/contracts.py +114 -3
- ksadk/studio/event_store.py +25 -95
- ksadk/studio/framework_run.py +231 -3
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/model_client.py +95 -9
- ksadk/studio/model_profile_service.py +1 -1
- ksadk/studio/operations.py +37 -5
- ksadk/studio/otel_trace.py +348 -42
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/repository.py +23 -2
- ksadk/studio/resource_catalog.py +43 -0
- ksadk/studio/run_service.py +563 -137
- ksadk/studio/runtime_source.py +42 -16
- ksadk/studio/service.py +1180 -72
- ksadk/studio/shared_web.py +32 -22
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/index.html +2 -2
- ksadk/studio/templates.py +100 -8
- ksadk/studio/validator.py +19 -0
- ksadk/studio/workspace.py +30 -32
- ksadk/tracing/setup.py +304 -3
- ksadk/version.py +1 -1
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk/events/parser.py +0 -191
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
- ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
- ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
- ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-R52pJJvg.js +0 -177
- ksadk/server/static/assets/index-jJr3_955.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
- ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
- ksadk/studio/evaluation.py +0 -192
- ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
- ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
ksadk/codex/runtime.py
CHANGED
|
@@ -19,14 +19,33 @@
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import asyncio
|
|
22
|
+
import base64
|
|
23
|
+
import binascii
|
|
24
|
+
import hashlib
|
|
22
25
|
import json
|
|
23
26
|
import logging
|
|
27
|
+
import re
|
|
28
|
+
import time
|
|
29
|
+
from collections.abc import Mapping
|
|
24
30
|
from dataclasses import dataclass, field
|
|
31
|
+
from pathlib import Path
|
|
25
32
|
from typing import Any, AsyncIterator, Optional
|
|
26
33
|
|
|
27
34
|
from ksadk.codex.client import CodexClient
|
|
28
|
-
from ksadk.codex
|
|
29
|
-
from ksadk.events.
|
|
35
|
+
from ksadk.events.adapters.codex import CodexAdapterContext, CodexEventAdapter
|
|
36
|
+
from ksadk.events.canonical import (
|
|
37
|
+
ErrorInfo,
|
|
38
|
+
InteractionRequested,
|
|
39
|
+
InteractionResolved,
|
|
40
|
+
RunCanceled,
|
|
41
|
+
RunCompleted,
|
|
42
|
+
RunFailed,
|
|
43
|
+
RunInterrupted,
|
|
44
|
+
RuntimeEvent,
|
|
45
|
+
SourceRef,
|
|
46
|
+
)
|
|
47
|
+
from ksadk.events.identity import stable_event_id, stable_item_id, stable_scope_id
|
|
48
|
+
from ksadk.kernel.contracts import RuntimeCapability, RuntimeCapabilityMatrix
|
|
30
49
|
from ksadk.runtime.adapter import (
|
|
31
50
|
BaseRuntime,
|
|
32
51
|
CancelResult,
|
|
@@ -76,6 +95,7 @@ class _CodexThread:
|
|
|
76
95
|
completed_at: int | None = None
|
|
77
96
|
duration_ms: int | None = None
|
|
78
97
|
goal_mode: bool = False
|
|
98
|
+
continuation_preexisting: bool = False
|
|
79
99
|
|
|
80
100
|
|
|
81
101
|
class CodexRuntimeAdapter(RuntimeAdapter):
|
|
@@ -101,6 +121,33 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
101
121
|
# 可观测:最近一次 cancel 级联丢弃的审批集(contract test 断言用)。
|
|
102
122
|
self.last_cancel_dropped_approvals: set[str] = set()
|
|
103
123
|
self._seq = 0
|
|
124
|
+
self._closed = False
|
|
125
|
+
|
|
126
|
+
# ---- capability matrix(v1,诚实声明) ----
|
|
127
|
+
|
|
128
|
+
def capabilities(self) -> RuntimeCapabilityMatrix:
|
|
129
|
+
"""Codex 真实矩阵:thread 级 cancel/pause/resume + 审批 submit + snapshot
|
|
130
|
+
checkpoint 均为后端原生能力;attach/durable_restore 未实现(线程表在本进程,
|
|
131
|
+
attach seam 缺失),steer/inject 无原生通道。
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def _unavailable(reason: str) -> RuntimeCapability:
|
|
135
|
+
return RuntimeCapability(supported=False, mode="unavailable", reason=reason)
|
|
136
|
+
|
|
137
|
+
return RuntimeCapabilityMatrix(
|
|
138
|
+
cancel=RuntimeCapability(supported=True, mode="native"),
|
|
139
|
+
pause=RuntimeCapability(supported=True, mode="native"),
|
|
140
|
+
resume=RuntimeCapability(supported=True, mode="native"),
|
|
141
|
+
submit_interaction=RuntimeCapability(supported=True, mode="native"),
|
|
142
|
+
attach=_unavailable("codex_process_local_thread_table"),
|
|
143
|
+
steer=_unavailable("runtime_no_native_steer"),
|
|
144
|
+
inject=_unavailable("runtime_no_native_inject"),
|
|
145
|
+
checkpoint=RuntimeCapability(supported=True, mode="native"),
|
|
146
|
+
durable_restore=_unavailable("codex_durable_restore_requires_attach_seam"),
|
|
147
|
+
goal=RuntimeCapability(supported=True, mode="native"),
|
|
148
|
+
loop=_unavailable("codex_loop_requires_run_control_spec"),
|
|
149
|
+
plan=RuntimeCapability(supported=True, mode="native"),
|
|
150
|
+
)
|
|
104
151
|
|
|
105
152
|
# ---- 六动词 ----
|
|
106
153
|
|
|
@@ -128,9 +175,18 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
128
175
|
cwd = request.config.get("cwd")
|
|
129
176
|
if cwd:
|
|
130
177
|
thread_config["cwd"] = str(cwd)
|
|
178
|
+
# AgentKernel creates one adapter/transport per durable turn and
|
|
179
|
+
# closes it after the canonical terminal event. The next turn
|
|
180
|
+
# therefore resumes the native thread from a new app-server
|
|
181
|
+
# process; an ephemeral Codex thread has no rollout and cannot be
|
|
182
|
+
# resumed across that transport boundary.
|
|
183
|
+
thread_config.setdefault("ephemeral", False)
|
|
131
184
|
thread_id = await self._client.start_thread(thread_config)
|
|
132
185
|
self._known_threads.add(thread_id)
|
|
133
|
-
thread = _CodexThread(
|
|
186
|
+
thread = _CodexThread(
|
|
187
|
+
thread_id=thread_id,
|
|
188
|
+
continuation_preexisting=bool(provided),
|
|
189
|
+
)
|
|
134
190
|
thread.__dict__["_start_request"] = request
|
|
135
191
|
self._threads[thread_id] = thread
|
|
136
192
|
self._requests[thread_id] = request
|
|
@@ -245,7 +301,10 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
245
301
|
raise ValueError(f"thread {handle.run_id} 已被中断/杀进程,不持久化,不可 resume")
|
|
246
302
|
self._pending_cancels.discard(handle.run_id)
|
|
247
303
|
self._known_threads.add(target.id)
|
|
248
|
-
thread = _CodexThread(
|
|
304
|
+
thread = _CodexThread(
|
|
305
|
+
thread_id=target.id,
|
|
306
|
+
continuation_preexisting=True,
|
|
307
|
+
)
|
|
249
308
|
thread.__dict__["_resume"] = {"target": target, "payload": payload}
|
|
250
309
|
request = self._requests.get(handle.run_id)
|
|
251
310
|
if request is not None:
|
|
@@ -294,13 +353,17 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
294
353
|
)
|
|
295
354
|
|
|
296
355
|
async def close(self, handle: RunHandle) -> None:
|
|
356
|
+
if self._closed:
|
|
357
|
+
return
|
|
358
|
+
self._closed = True
|
|
297
359
|
thread = self._threads.pop(handle.run_id, None)
|
|
298
360
|
self._requests.pop(handle.run_id, None)
|
|
299
|
-
|
|
361
|
+
active = thread is not None and thread.streaming and not thread.done
|
|
362
|
+
if active:
|
|
300
363
|
thread.interrupt_event.set()
|
|
301
364
|
try:
|
|
302
|
-
|
|
303
|
-
|
|
365
|
+
if active:
|
|
366
|
+
await self._client.interrupt_active_turn(thread.thread_id)
|
|
304
367
|
finally:
|
|
305
368
|
# AsyncCodex.close owns terminate/wait/kill for the app-server child.
|
|
306
369
|
await self._client.close()
|
|
@@ -326,18 +389,12 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
326
389
|
|
|
327
390
|
if handle.run_id in self._pending_cancels:
|
|
328
391
|
self._pending_cancels.discard(handle.run_id)
|
|
329
|
-
yield self.
|
|
392
|
+
yield self._make_run_canceled(
|
|
330
393
|
handle,
|
|
331
|
-
|
|
332
|
-
{
|
|
333
|
-
"status": "cancelled",
|
|
334
|
-
"cancel_result": CancelResult.PENDING_CANCEL_RECORDED.value,
|
|
335
|
-
},
|
|
394
|
+
reason=f"pending_cancel:{CancelResult.PENDING_CANCEL_RECORDED.value}",
|
|
336
395
|
)
|
|
337
396
|
return
|
|
338
397
|
|
|
339
|
-
yield self._event(handle, EventType.RUN_STARTED, {"status": "in_progress"})
|
|
340
|
-
tracker = CodexPhaseTracker()
|
|
341
398
|
request = thread.__dict__.get("_start_request")
|
|
342
399
|
resume_state = thread.__dict__.get("_resume")
|
|
343
400
|
if request is not None:
|
|
@@ -350,21 +407,8 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
350
407
|
thread.streaming = True
|
|
351
408
|
thread.turn_id = thread.turn_id or f"turn_{thread.thread_id}"
|
|
352
409
|
try:
|
|
353
|
-
async for event in self._map_codex_stream(handle, thread,
|
|
410
|
+
async for event in self._map_codex_stream(handle, thread, run_input):
|
|
354
411
|
yield event
|
|
355
|
-
# 正常结束(非 interrupt):补 RUN_COMPLETED(AGUI 投射器据此发 RunFinished success)
|
|
356
|
-
if not thread.interrupted:
|
|
357
|
-
completed_payload: dict[str, Any] = {
|
|
358
|
-
"status": "completed",
|
|
359
|
-
"source": "codex",
|
|
360
|
-
}
|
|
361
|
-
if thread.started_at is not None:
|
|
362
|
-
completed_payload["started_at"] = thread.started_at
|
|
363
|
-
if thread.completed_at is not None:
|
|
364
|
-
completed_payload["completed_at"] = thread.completed_at
|
|
365
|
-
if thread.duration_ms is not None:
|
|
366
|
-
completed_payload["duration_ms"] = thread.duration_ms
|
|
367
|
-
yield self._event(handle, EventType.RUN_COMPLETED, completed_payload)
|
|
368
412
|
except asyncio.CancelledError:
|
|
369
413
|
thread.interrupted = True
|
|
370
414
|
self._do_not_persist.add(handle.run_id)
|
|
@@ -377,18 +421,10 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
377
421
|
# Closing the SDK transport terminates and waits for the app-server
|
|
378
422
|
# child even when the stream is stuck between notifications.
|
|
379
423
|
await self._client.close()
|
|
380
|
-
yield self.
|
|
381
|
-
|
|
382
|
-
EventType.RUN_FAILED,
|
|
383
|
-
{"status": "failed", "error": "codex turn timed out"},
|
|
384
|
-
)
|
|
385
|
-
except Exception as exc: # noqa: BLE001 通用兜底:任何异常都发 RUN_FAILED
|
|
424
|
+
yield self._make_run_failed(handle, "codex turn timed out")
|
|
425
|
+
except Exception as exc: # noqa: BLE001 通用兜底:任何异常都发 RunFailed
|
|
386
426
|
self._do_not_persist.add(handle.run_id)
|
|
387
|
-
yield self.
|
|
388
|
-
handle,
|
|
389
|
-
EventType.RUN_FAILED,
|
|
390
|
-
{"status": "failed", "error": str(exc)},
|
|
391
|
-
)
|
|
427
|
+
yield self._make_run_failed(handle, str(exc))
|
|
392
428
|
finally:
|
|
393
429
|
thread.streaming = False
|
|
394
430
|
thread.done = True
|
|
@@ -398,10 +434,15 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
398
434
|
self,
|
|
399
435
|
handle: RunHandle,
|
|
400
436
|
thread: _CodexThread,
|
|
401
|
-
tracker: CodexPhaseTracker,
|
|
402
437
|
prompt: Any,
|
|
403
438
|
) -> AsyncIterator[RuntimeEvent]:
|
|
404
439
|
request = thread.__dict__.get("_start_request") or thread.__dict__.get("_request_config")
|
|
440
|
+
adapter = CodexEventAdapter(
|
|
441
|
+
known_thread_ids=(thread.thread_id,)
|
|
442
|
+
if thread.continuation_preexisting
|
|
443
|
+
else (),
|
|
444
|
+
)
|
|
445
|
+
context = CodexAdapterContext(run_id=self._event_run_id(handle))
|
|
405
446
|
run_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
|
|
406
447
|
if request is not None and request.config:
|
|
407
448
|
for key in ("sandbox", "approval_mode", "summary", "collaboration_mode"):
|
|
@@ -496,14 +537,11 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
496
537
|
chunk_task = asyncio.ensure_future(_anext_or_stop(codex_gen))
|
|
497
538
|
chunk_task = None
|
|
498
539
|
thread.interrupted = True
|
|
499
|
-
#
|
|
500
|
-
|
|
540
|
+
# Runtime interrupt (user pause) — adapter doesn't know;
|
|
541
|
+
# emit canonical RunInterrupted explicitly.
|
|
542
|
+
yield self._make_run_interrupted(
|
|
501
543
|
handle,
|
|
502
|
-
|
|
503
|
-
{
|
|
504
|
-
"status": "paused" if thread.paused else "interrupted",
|
|
505
|
-
"reason": "user_pause" if thread.paused else "runtime_interrupt",
|
|
506
|
-
},
|
|
544
|
+
reason="user_pause" if thread.paused else "runtime_interrupt",
|
|
507
545
|
)
|
|
508
546
|
return
|
|
509
547
|
for task in pending:
|
|
@@ -513,8 +551,58 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
513
551
|
chunk = chunk_task.result()
|
|
514
552
|
if chunk is _STREAM_STOP:
|
|
515
553
|
return
|
|
516
|
-
event
|
|
517
|
-
|
|
554
|
+
# TODO(runtime-event-v2): use real native cursor from chunk if
|
|
555
|
+
# available; fallback to thread:seq for now.
|
|
556
|
+
native_cursor = f"{thread.thread_id}:{self._next_seq()}"
|
|
557
|
+
# autoApprovalReview 不产生 canonical 事件(adapter 静默),但
|
|
558
|
+
# cancel 级联丢弃审批的契约依赖 runtime 的 pending 跟踪。
|
|
559
|
+
chunk_method = (
|
|
560
|
+
str((chunk or {}).get("method") or "")
|
|
561
|
+
if isinstance(chunk, dict)
|
|
562
|
+
else ""
|
|
563
|
+
)
|
|
564
|
+
if chunk_method in {
|
|
565
|
+
"item/autoApprovalReview/started",
|
|
566
|
+
"item/autoApprovalReview/completed",
|
|
567
|
+
}:
|
|
568
|
+
review_params = chunk.get("params") or {}
|
|
569
|
+
review_id = str(
|
|
570
|
+
review_params.get("reviewId")
|
|
571
|
+
or review_params.get("review_id")
|
|
572
|
+
or ""
|
|
573
|
+
)
|
|
574
|
+
if review_id:
|
|
575
|
+
if chunk_method.endswith("started"):
|
|
576
|
+
thread.pending_approvals.add(review_id)
|
|
577
|
+
else:
|
|
578
|
+
thread.pending_approvals.discard(review_id)
|
|
579
|
+
for event in adapter.map_protocol_message(
|
|
580
|
+
chunk,
|
|
581
|
+
context,
|
|
582
|
+
native_cursor=native_cursor,
|
|
583
|
+
timestamp=time.time(),
|
|
584
|
+
):
|
|
585
|
+
event = self._with_caller_scope(event, request)
|
|
586
|
+
# 跟踪 pending 审批(cancel 级联丢弃契约依赖该集合)。
|
|
587
|
+
if isinstance(event, InteractionRequested):
|
|
588
|
+
if event.interaction_id:
|
|
589
|
+
thread.pending_approvals.add(event.interaction_id)
|
|
590
|
+
call_id = getattr(event.request, "call_id", None)
|
|
591
|
+
if call_id:
|
|
592
|
+
thread.pending_approvals.add(str(call_id))
|
|
593
|
+
elif isinstance(event, InteractionResolved):
|
|
594
|
+
thread.pending_approvals.discard(event.interaction_id)
|
|
595
|
+
call_id = getattr(event.response, "call_id", None)
|
|
596
|
+
if call_id:
|
|
597
|
+
thread.pending_approvals.discard(str(call_id))
|
|
598
|
+
if isinstance(event, (RunCompleted, RunFailed, RunCanceled)):
|
|
599
|
+
# The Kernel stops consuming as soon as it persists a
|
|
600
|
+
# canonical terminal fact, so generator ``finally`` may
|
|
601
|
+
# not run before worker cleanup calls ``close``. Mark the
|
|
602
|
+
# native turn terminal before yielding that fact; close
|
|
603
|
+
# must terminate the transport without sending a stale
|
|
604
|
+
# turn/interrupt RPC to an already-completed app-server.
|
|
605
|
+
thread.done = True
|
|
518
606
|
yield event
|
|
519
607
|
finally:
|
|
520
608
|
waiter_tasks = [task for task in (chunk_task, interrupt_task) if task is not None]
|
|
@@ -530,285 +618,155 @@ class CodexRuntimeAdapter(RuntimeAdapter):
|
|
|
530
618
|
except Exception: # noqa: BLE001
|
|
531
619
|
pass
|
|
532
620
|
|
|
533
|
-
|
|
621
|
+
# ---- canonical run.* helpers (for runtime-owned lifecycle) ----
|
|
622
|
+
|
|
623
|
+
def _event_run_id(self, handle: RunHandle) -> str:
|
|
624
|
+
"""事件的 canonical run_id:调用方 invocation_id 优先,退回 thread id。
|
|
625
|
+
|
|
626
|
+
``handle.run_id`` 是 codex 原生 thread id(resume/cancel 按 thread 寻址);
|
|
627
|
+
但 canonical RuntimeEvent 的 run_id 必须与调用方
|
|
628
|
+
``StartRequest.metadata['invocation_id']`` 一致(conversation kernel 的
|
|
629
|
+
event scope 校验),否则 hosted/web 执行路径会在首个事件上 fail。
|
|
630
|
+
"""
|
|
631
|
+
request = self._requests.get(handle.run_id)
|
|
632
|
+
if request is not None:
|
|
633
|
+
invocation_id = str(
|
|
634
|
+
(getattr(request, "metadata", None) or {}).get("invocation_id") or ""
|
|
635
|
+
).strip()
|
|
636
|
+
if invocation_id:
|
|
637
|
+
return invocation_id
|
|
638
|
+
return handle.run_id
|
|
639
|
+
|
|
640
|
+
def _make_source(self, handle: RunHandle) -> SourceRef:
|
|
641
|
+
request = self._requests.get(handle.run_id)
|
|
642
|
+
return SourceRef(
|
|
643
|
+
framework="codex",
|
|
644
|
+
native_run_id=handle.run_id,
|
|
645
|
+
metadata={
|
|
646
|
+
"agent_id": (
|
|
647
|
+
str(request.agent_id or "codex") if request is not None else "codex"
|
|
648
|
+
),
|
|
649
|
+
"user_id": (
|
|
650
|
+
request.user_id
|
|
651
|
+
if request is not None
|
|
652
|
+
else str(handle.native_ref.get("user_id") or "user")
|
|
653
|
+
),
|
|
654
|
+
"session_id": handle.session_id,
|
|
655
|
+
"invocation_id": (
|
|
656
|
+
str(request.metadata.get("invocation_id") or handle.run_id)
|
|
657
|
+
if request is not None
|
|
658
|
+
else handle.run_id
|
|
659
|
+
),
|
|
660
|
+
},
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
def _with_caller_scope(self, event: RuntimeEvent, request: Any) -> RuntimeEvent:
|
|
664
|
+
"""把调用方 scope(request 的 agent/user/session/invocation)并入事件 source。"""
|
|
665
|
+
|
|
666
|
+
if request is None:
|
|
667
|
+
return event
|
|
668
|
+
caller_scope = {
|
|
669
|
+
"agent_id": str(getattr(request, "agent_id", "") or "codex"),
|
|
670
|
+
"user_id": str(getattr(request, "user_id", "") or "user"),
|
|
671
|
+
"session_id": str(getattr(request, "session_id", "") or ""),
|
|
672
|
+
"invocation_id": str(
|
|
673
|
+
(getattr(request, "metadata", None) or {}).get("invocation_id")
|
|
674
|
+
or ""
|
|
675
|
+
),
|
|
676
|
+
}
|
|
677
|
+
merged = {**caller_scope, **dict(event.source.metadata or {})}
|
|
678
|
+
# adapter 自身字段优先;仅补齐缺失的调用方 scope 键。
|
|
679
|
+
for key, value in caller_scope.items():
|
|
680
|
+
if not merged.get(key):
|
|
681
|
+
merged[key] = value
|
|
682
|
+
source = event.source.model_copy(update={"metadata": merged})
|
|
683
|
+
return event.model_copy(update={"source": source})
|
|
684
|
+
|
|
685
|
+
def _canonical_kwargs(
|
|
534
686
|
self,
|
|
535
687
|
handle: RunHandle,
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return None
|
|
557
|
-
|
|
558
|
-
if method == "thread/tokenUsage/updated":
|
|
559
|
-
token_usage = params.get("token_usage") or params.get("tokenUsage") or {}
|
|
560
|
-
last = token_usage.get("last") if isinstance(token_usage, dict) else {}
|
|
561
|
-
if not isinstance(last, dict):
|
|
562
|
-
last = {}
|
|
563
|
-
return self._event(
|
|
564
|
-
handle,
|
|
565
|
-
EventType.USAGE_REPORTED,
|
|
566
|
-
{
|
|
567
|
-
"input_tokens": int(last.get("input_tokens", last.get("inputTokens", 0)) or 0),
|
|
568
|
-
"cached_tokens": int(
|
|
569
|
-
last.get("cached_input_tokens", last.get("cachedInputTokens", 0)) or 0
|
|
570
|
-
),
|
|
571
|
-
"output_tokens": int(
|
|
572
|
-
last.get("output_tokens", last.get("outputTokens", 0)) or 0
|
|
573
|
-
),
|
|
574
|
-
"reasoning_tokens": int(
|
|
575
|
-
last.get(
|
|
576
|
-
"reasoning_output_tokens",
|
|
577
|
-
last.get("reasoningOutputTokens", 0),
|
|
578
|
-
)
|
|
579
|
-
or 0
|
|
580
|
-
),
|
|
581
|
-
"total_tokens": int(last.get("total_tokens", last.get("totalTokens", 0)) or 0),
|
|
582
|
-
"source": "codex",
|
|
583
|
-
},
|
|
584
|
-
)
|
|
585
|
-
if method == "thread/goal/updated":
|
|
586
|
-
goal = params.get("goal") if isinstance(params, dict) else {}
|
|
587
|
-
goal = goal if isinstance(goal, dict) else {}
|
|
588
|
-
status = str(goal.get("status") or "").lower()
|
|
589
|
-
if status in {"paused", "blocked", "usage_limited", "budget_limited"}:
|
|
590
|
-
thread.paused = status == "paused"
|
|
591
|
-
thread.interrupted = True
|
|
592
|
-
return self._event(
|
|
593
|
-
handle,
|
|
594
|
-
EventType.RUN_INTERRUPTED,
|
|
595
|
-
{"status": status, "reason": "goal_status", "goal": goal},
|
|
596
|
-
)
|
|
597
|
-
return self._event(
|
|
598
|
-
handle,
|
|
599
|
-
EventType.RUN_PROGRESS,
|
|
600
|
-
{"native_event": "goal.updated", "native_data": goal},
|
|
601
|
-
)
|
|
602
|
-
if method in {"turn/started", "turn/completed"}:
|
|
603
|
-
raw_turn = params.get("turn")
|
|
604
|
-
turn: dict[str, Any] = raw_turn if isinstance(raw_turn, dict) else {}
|
|
605
|
-
started_at = turn.get("started_at", turn.get("startedAt"))
|
|
606
|
-
completed_at = turn.get("completed_at", turn.get("completedAt"))
|
|
607
|
-
duration_ms = turn.get("duration_ms", turn.get("durationMs"))
|
|
608
|
-
if started_at is not None:
|
|
609
|
-
thread.started_at = int(started_at)
|
|
610
|
-
if completed_at is not None:
|
|
611
|
-
thread.completed_at = int(completed_at)
|
|
612
|
-
if duration_ms is not None:
|
|
613
|
-
thread.duration_ms = max(0, int(duration_ms))
|
|
614
|
-
return None
|
|
615
|
-
|
|
616
|
-
if method == "a2ui/surface":
|
|
617
|
-
surface_id = str(params.get("surface_id") or params.get("surfaceId") or "")
|
|
618
|
-
return self._event(
|
|
619
|
-
handle,
|
|
620
|
-
EventType.A2UI_SURFACE_BEGIN,
|
|
621
|
-
{
|
|
622
|
-
"surface_id": surface_id,
|
|
623
|
-
"surface": params.get("surface")
|
|
624
|
-
if isinstance(params.get("surface"), dict)
|
|
625
|
-
else {},
|
|
626
|
-
},
|
|
627
|
-
)
|
|
628
|
-
if method == "a2ui/interaction":
|
|
629
|
-
interaction_id = str(params.get("interaction_id") or params.get("interactionId") or "")
|
|
630
|
-
if interaction_id:
|
|
631
|
-
thread.pending_approvals.add(interaction_id)
|
|
632
|
-
return self._event(
|
|
633
|
-
handle,
|
|
634
|
-
EventType.A2UI_INTERACTION,
|
|
635
|
-
{
|
|
636
|
-
"surface_id": str(params.get("surface_id") or params.get("surfaceId") or ""),
|
|
637
|
-
"interaction_id": interaction_id,
|
|
638
|
-
"kind": str(params.get("kind") or "form"),
|
|
639
|
-
"input_schema": params.get("input_schema")
|
|
640
|
-
if isinstance(params.get("input_schema"), dict)
|
|
641
|
-
else {},
|
|
642
|
-
"is_blocking": bool(params.get("is_blocking", True)),
|
|
643
|
-
},
|
|
644
|
-
)
|
|
688
|
+
*,
|
|
689
|
+
scope_id: str,
|
|
690
|
+
item_id: str,
|
|
691
|
+
event_type: str,
|
|
692
|
+
part_id: str,
|
|
693
|
+
) -> dict[str, Any]:
|
|
694
|
+
framework = "codex"
|
|
695
|
+
run_id = self._event_run_id(handle)
|
|
696
|
+
n = self._next_seq()
|
|
697
|
+
return {
|
|
698
|
+
"schema_version": 2,
|
|
699
|
+
"event_id": stable_event_id(
|
|
700
|
+
framework, scope_id, item_id, event_type, part_id, run_id, n
|
|
701
|
+
),
|
|
702
|
+
"seq": n,
|
|
703
|
+
"timestamp": time.time(),
|
|
704
|
+
"run_id": run_id,
|
|
705
|
+
"scope_id": scope_id,
|
|
706
|
+
"source": self._make_source(handle),
|
|
707
|
+
}
|
|
645
708
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
"call_id": call_id,
|
|
656
|
-
"name": "codex.command",
|
|
657
|
-
"args": {
|
|
658
|
-
"command": str(item.get("command") or ""),
|
|
659
|
-
"cwd": str(item.get("cwd") or ""),
|
|
660
|
-
"command_actions": item.get("commandActions")
|
|
661
|
-
or item.get("command_actions")
|
|
662
|
-
or [],
|
|
663
|
-
},
|
|
664
|
-
},
|
|
665
|
-
)
|
|
666
|
-
if item.get("type") == "mcpToolCall":
|
|
667
|
-
call_id = str(item.get("id") or "")
|
|
668
|
-
server = str(item.get("server") or "")
|
|
669
|
-
tool = str(item.get("tool") or "")
|
|
670
|
-
return self._event(
|
|
671
|
-
handle,
|
|
672
|
-
EventType.TOOL_CALL_BEGIN,
|
|
673
|
-
{
|
|
674
|
-
"call_id": call_id,
|
|
675
|
-
"name": f"mcp.{server}.{tool}" if server else f"mcp.{tool}",
|
|
676
|
-
"args": {
|
|
677
|
-
"server": server,
|
|
678
|
-
"tool": tool,
|
|
679
|
-
"arguments": item.get("arguments"),
|
|
680
|
-
},
|
|
681
|
-
},
|
|
682
|
-
)
|
|
683
|
-
return None
|
|
684
|
-
if method == "item/completed":
|
|
685
|
-
item = params.get("item") or params
|
|
686
|
-
item_type = item.get("type")
|
|
687
|
-
if item_type == "commandExecution":
|
|
688
|
-
tracker.forget_item(params)
|
|
689
|
-
call_id = str(item.get("id") or "")
|
|
690
|
-
return self._event(
|
|
691
|
-
handle,
|
|
692
|
-
EventType.TOOL_CALL_END,
|
|
693
|
-
{
|
|
694
|
-
"call_id": call_id,
|
|
695
|
-
"name": "codex.command",
|
|
696
|
-
"result": {
|
|
697
|
-
"status": str(item.get("status") or "completed"),
|
|
698
|
-
"exit_code": item.get("exitCode", item.get("exit_code")),
|
|
699
|
-
"duration_ms": item.get("durationMs", item.get("duration_ms")),
|
|
700
|
-
"output": str(
|
|
701
|
-
item.get("aggregatedOutput") or item.get("aggregated_output") or ""
|
|
702
|
-
),
|
|
703
|
-
},
|
|
704
|
-
},
|
|
705
|
-
)
|
|
706
|
-
if item_type == "mcpToolCall":
|
|
707
|
-
tracker.forget_item(params)
|
|
708
|
-
call_id = str(item.get("id") or "")
|
|
709
|
-
server = str(item.get("server") or "")
|
|
710
|
-
tool = str(item.get("tool") or "")
|
|
711
|
-
raw_result = item.get("result")
|
|
712
|
-
result_obj: dict[str, Any] = raw_result if isinstance(raw_result, dict) else {}
|
|
713
|
-
raw_error = item.get("error")
|
|
714
|
-
error_obj: dict[str, Any] = raw_error if isinstance(raw_error, dict) else {}
|
|
715
|
-
output = self._mcp_result_text(result_obj)
|
|
716
|
-
error_message = str(error_obj.get("message") or "")
|
|
717
|
-
if not output and error_message:
|
|
718
|
-
output = error_message
|
|
719
|
-
return self._event(
|
|
720
|
-
handle,
|
|
721
|
-
EventType.TOOL_CALL_END,
|
|
722
|
-
{
|
|
723
|
-
"call_id": call_id,
|
|
724
|
-
"name": f"mcp.{server}.{tool}" if server else f"mcp.{tool}",
|
|
725
|
-
"result": {
|
|
726
|
-
"status": str(item.get("status") or "completed"),
|
|
727
|
-
"duration_ms": item.get("durationMs", item.get("duration_ms")),
|
|
728
|
-
"output": output,
|
|
729
|
-
**({"error": error_message} if error_message else {}),
|
|
730
|
-
},
|
|
731
|
-
},
|
|
732
|
-
)
|
|
733
|
-
if item_type != "agentMessage":
|
|
734
|
-
tracker.forget_item(params)
|
|
735
|
-
return None
|
|
736
|
-
phase = tracker.runtime_phase_for_item(params)
|
|
737
|
-
tracker.forget_item(params)
|
|
738
|
-
text = str(item.get("text") or "")
|
|
739
|
-
return self._event(
|
|
709
|
+
def _make_run_canceled(
|
|
710
|
+
self, handle: RunHandle, *, reason: str | None = None
|
|
711
|
+
) -> RunCanceled:
|
|
712
|
+
framework = "codex"
|
|
713
|
+
run_id = self._event_run_id(handle)
|
|
714
|
+
scope_id = stable_scope_id(framework, run_id)
|
|
715
|
+
item_id = stable_item_id(framework, run_id, "$run")
|
|
716
|
+
return RunCanceled(
|
|
717
|
+
**self._canonical_kwargs(
|
|
740
718
|
handle,
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
review_id = str(params.get("review_id") or params.get("reviewId") or "")
|
|
760
|
-
thread.pending_approvals.discard(review_id)
|
|
761
|
-
return None
|
|
762
|
-
if (
|
|
763
|
-
"approval" in method.lower()
|
|
764
|
-
or "requestPermission" in method
|
|
765
|
-
or "approval" in str(chunk.get("type") or "").lower()
|
|
766
|
-
):
|
|
767
|
-
call_id = str(
|
|
768
|
-
params.get("id") or params.get("call_id") or params.get("requestId") or ""
|
|
769
|
-
)
|
|
770
|
-
if call_id:
|
|
771
|
-
thread.pending_approvals.add(call_id)
|
|
772
|
-
return self._event(
|
|
719
|
+
scope_id=scope_id,
|
|
720
|
+
item_id=item_id,
|
|
721
|
+
event_type="run.canceled",
|
|
722
|
+
part_id="run",
|
|
723
|
+
),
|
|
724
|
+
status="canceled",
|
|
725
|
+
reason=reason,
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
def _make_run_interrupted(
|
|
729
|
+
self, handle: RunHandle, *, reason: str | None = None
|
|
730
|
+
) -> RunInterrupted:
|
|
731
|
+
framework = "codex"
|
|
732
|
+
run_id = self._event_run_id(handle)
|
|
733
|
+
scope_id = stable_scope_id(framework, run_id)
|
|
734
|
+
item_id = stable_item_id(framework, run_id, "$run")
|
|
735
|
+
return RunInterrupted(
|
|
736
|
+
**self._canonical_kwargs(
|
|
773
737
|
handle,
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
},
|
|
783
|
-
)
|
|
784
|
-
return None
|
|
738
|
+
scope_id=scope_id,
|
|
739
|
+
item_id=item_id,
|
|
740
|
+
event_type="run.interrupted",
|
|
741
|
+
part_id="run",
|
|
742
|
+
),
|
|
743
|
+
status="interrupted",
|
|
744
|
+
reason=reason,
|
|
745
|
+
)
|
|
785
746
|
|
|
786
|
-
def
|
|
787
|
-
self,
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if request is not None
|
|
801
|
-
else str(handle.native_ref.get("user_id") or "user")
|
|
747
|
+
def _make_run_failed(
|
|
748
|
+
self, handle: RunHandle, error_message: str
|
|
749
|
+
) -> RunFailed:
|
|
750
|
+
framework = "codex"
|
|
751
|
+
run_id = self._event_run_id(handle)
|
|
752
|
+
scope_id = stable_scope_id(framework, run_id)
|
|
753
|
+
item_id = stable_item_id(framework, run_id, "$run")
|
|
754
|
+
return RunFailed(
|
|
755
|
+
**self._canonical_kwargs(
|
|
756
|
+
handle,
|
|
757
|
+
scope_id=scope_id,
|
|
758
|
+
item_id=item_id,
|
|
759
|
+
event_type="run.failed",
|
|
760
|
+
part_id="run",
|
|
802
761
|
),
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
762
|
+
status="failed",
|
|
763
|
+
error=ErrorInfo(
|
|
764
|
+
code="codex_runtime_failed",
|
|
765
|
+
message=error_message,
|
|
766
|
+
source="codex",
|
|
767
|
+
scope_id=scope_id,
|
|
768
|
+
source_ref=self._make_source(handle),
|
|
808
769
|
),
|
|
809
|
-
seq_id=self._next_seq(),
|
|
810
|
-
phase=phase,
|
|
811
|
-
payload=payload,
|
|
812
770
|
)
|
|
813
771
|
|
|
814
772
|
@staticmethod
|
|
@@ -850,6 +808,34 @@ def _resume_prompt(payload: Optional[ResumePayload]) -> Any:
|
|
|
850
808
|
return json.dumps(payload.data, ensure_ascii=False, sort_keys=True)
|
|
851
809
|
|
|
852
810
|
|
|
811
|
+
def _coerce_prompt_text(value: Any) -> Any:
|
|
812
|
+
"""把 canonical message 形态的 input 压成 SDK 可接受的文本。
|
|
813
|
+
|
|
814
|
+
``openai-codex`` 0.147 的 run input 只接受 TextInput/str;请求侧没有
|
|
815
|
+
conversation preprocessing 时 ``request.input`` 可能是
|
|
816
|
+
``[{role, content}]`` 历史列表,直接透传会 ``unsupported input item``。
|
|
817
|
+
"""
|
|
818
|
+
if isinstance(value, str) or value is None:
|
|
819
|
+
return value
|
|
820
|
+
if isinstance(value, dict):
|
|
821
|
+
content = value.get("content") if "role" in value else value.get("text")
|
|
822
|
+
if isinstance(content, str):
|
|
823
|
+
return content
|
|
824
|
+
if isinstance(content, dict):
|
|
825
|
+
text = content.get("text") or content.get("content")
|
|
826
|
+
if isinstance(text, str):
|
|
827
|
+
return text
|
|
828
|
+
return str(value)
|
|
829
|
+
if isinstance(value, list):
|
|
830
|
+
texts = [
|
|
831
|
+
text
|
|
832
|
+
for text in (_coerce_prompt_text(item) for item in value)
|
|
833
|
+
if isinstance(text, str) and text
|
|
834
|
+
]
|
|
835
|
+
return "\n".join(texts) if texts else str(value)
|
|
836
|
+
return str(value)
|
|
837
|
+
|
|
838
|
+
|
|
853
839
|
def _request_prompt(request: StartRequest) -> Any:
|
|
854
840
|
"""Render canonical conversation history for a native Codex turn.
|
|
855
841
|
|
|
@@ -859,11 +845,21 @@ def _request_prompt(request: StartRequest) -> Any:
|
|
|
859
845
|
# A resumed Codex thread already owns its transcript. Re-sending Studio's
|
|
860
846
|
# transport-neutral history would duplicate every prior turn after refresh.
|
|
861
847
|
if str(request.metadata.get("thread_id") or "").strip():
|
|
862
|
-
return
|
|
848
|
+
return (
|
|
849
|
+
request.input
|
|
850
|
+
if _is_structured_turn_input(request.input)
|
|
851
|
+
else _coerce_prompt_text(request.input)
|
|
852
|
+
)
|
|
863
853
|
|
|
864
854
|
conversation = request.conversation_preprocessing()
|
|
865
855
|
if conversation is None or not conversation.messages:
|
|
866
|
-
|
|
856
|
+
# Keep native text/image/mention parts intact for _build_run_input().
|
|
857
|
+
# Flattening this list turns an image dict into user-visible text.
|
|
858
|
+
return (
|
|
859
|
+
request.input
|
|
860
|
+
if _is_structured_turn_input(request.input)
|
|
861
|
+
else _coerce_prompt_text(request.input)
|
|
862
|
+
)
|
|
867
863
|
|
|
868
864
|
lines: list[str] = []
|
|
869
865
|
for message in conversation.messages:
|
|
@@ -880,6 +876,13 @@ def _request_prompt(request: StartRequest) -> Any:
|
|
|
880
876
|
return "\n".join(lines) or request.input
|
|
881
877
|
|
|
882
878
|
|
|
879
|
+
def _is_structured_turn_input(value: Any) -> bool:
|
|
880
|
+
return isinstance(value, list) and any(
|
|
881
|
+
isinstance(item, dict) and isinstance(item.get("type"), str)
|
|
882
|
+
for item in value
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
|
|
883
886
|
def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
|
|
884
887
|
"""Compose Codex skills and native text/image/mention turn input."""
|
|
885
888
|
try:
|
|
@@ -905,7 +908,11 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
|
|
|
905
908
|
if not isinstance(item, dict):
|
|
906
909
|
continue
|
|
907
910
|
kind = str(item.get("type") or "")
|
|
908
|
-
if kind
|
|
911
|
+
if not kind and "role" in item:
|
|
912
|
+
# canonical conversation message({role, content});当前 input
|
|
913
|
+
# 已由 prompt(或 conversation preprocessing)承载,跳过历史项。
|
|
914
|
+
continue
|
|
915
|
+
if kind in {"text", "input_text"}:
|
|
909
916
|
text = str(
|
|
910
917
|
prompt
|
|
911
918
|
if not text_replaced and isinstance(prompt, str)
|
|
@@ -914,10 +921,35 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
|
|
|
914
921
|
text_replaced = True
|
|
915
922
|
if text:
|
|
916
923
|
native_items.append(TextInput(text=text))
|
|
917
|
-
elif kind
|
|
918
|
-
|
|
924
|
+
elif kind in {"image", "input_image"} and (
|
|
925
|
+
item.get("url") or item.get("image_url")
|
|
926
|
+
):
|
|
927
|
+
native_items.append(
|
|
928
|
+
ImageInput(url=str(item.get("url") or item.get("image_url")))
|
|
929
|
+
)
|
|
919
930
|
elif kind == "localImage" and item.get("path"):
|
|
920
931
|
native_items.append(LocalImageInput(path=str(item["path"])))
|
|
932
|
+
elif kind == "input_file" and (
|
|
933
|
+
item.get("file_data")
|
|
934
|
+
or str(item.get("file_url") or "").startswith("data:")
|
|
935
|
+
):
|
|
936
|
+
file_path = _materialize_inline_file(
|
|
937
|
+
str(item.get("file_data") or item.get("file_url")),
|
|
938
|
+
str(item.get("filename") or "attachment"),
|
|
939
|
+
)
|
|
940
|
+
if file_path is not None:
|
|
941
|
+
# App Server's ``mention`` input is presentation metadata:
|
|
942
|
+
# current Codex versions do not include it in the model's
|
|
943
|
+
# user message. Always add an explicit model-visible
|
|
944
|
+
# attachment context as well. Small textual files are
|
|
945
|
+
# inlined deterministically; binary/large files expose a
|
|
946
|
+
# sandbox-readable path that Codex can inspect with tools.
|
|
947
|
+
native_items.append(
|
|
948
|
+
TextInput(text=_attachment_context_text(file_path, item))
|
|
949
|
+
)
|
|
950
|
+
native_items.append(
|
|
951
|
+
MentionInput(name=file_path.name, path=str(file_path))
|
|
952
|
+
)
|
|
921
953
|
elif kind == "mention" and item.get("path"):
|
|
922
954
|
native_items.append(
|
|
923
955
|
MentionInput(
|
|
@@ -937,4 +969,74 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
|
|
|
937
969
|
return combined
|
|
938
970
|
|
|
939
971
|
|
|
972
|
+
def _materialize_inline_file(data_url: str, filename: str) -> Path | None:
|
|
973
|
+
"""Materialize a bounded Studio inline attachment for native Codex."""
|
|
974
|
+
|
|
975
|
+
match = re.fullmatch(r"data:([^;,]+)?;base64,([A-Za-z0-9+/=\s]+)", data_url)
|
|
976
|
+
encoded = match.group(2) if match is not None else data_url.strip()
|
|
977
|
+
try:
|
|
978
|
+
payload = base64.b64decode(encoded, validate=True)
|
|
979
|
+
except (ValueError, binascii.Error):
|
|
980
|
+
return None
|
|
981
|
+
if not payload or len(payload) > 10 * 1024 * 1024:
|
|
982
|
+
return None
|
|
983
|
+
safe_name = re.sub(r"[^A-Za-z0-9._-]+", "-", Path(filename).name).strip(".-")
|
|
984
|
+
safe_name = safe_name[:120] or "attachment"
|
|
985
|
+
digest = hashlib.sha256(payload).hexdigest()
|
|
986
|
+
path = Path("/tmp/ksadk-codex-attachments") / digest / safe_name
|
|
987
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
988
|
+
if not path.exists():
|
|
989
|
+
path.write_bytes(payload)
|
|
990
|
+
return path
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
_MAX_INLINE_ATTACHMENT_TEXT_BYTES = 64 * 1024
|
|
994
|
+
_TEXT_ATTACHMENT_SUFFIXES = {
|
|
995
|
+
".csv",
|
|
996
|
+
".html",
|
|
997
|
+
".htm",
|
|
998
|
+
".ini",
|
|
999
|
+
".json",
|
|
1000
|
+
".jsonl",
|
|
1001
|
+
".log",
|
|
1002
|
+
".md",
|
|
1003
|
+
".py",
|
|
1004
|
+
".rst",
|
|
1005
|
+
".toml",
|
|
1006
|
+
".tsv",
|
|
1007
|
+
".txt",
|
|
1008
|
+
".xml",
|
|
1009
|
+
".yaml",
|
|
1010
|
+
".yml",
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
def _attachment_context_text(path: Path, item: Mapping[str, Any]) -> str:
|
|
1015
|
+
"""Build model-visible context for a materialized Responses input file."""
|
|
1016
|
+
|
|
1017
|
+
name = str(item.get("filename") or path.name).replace('"', "'")
|
|
1018
|
+
inline_data = item.get("inlineData")
|
|
1019
|
+
inline_mime = inline_data.get("mimeType") if isinstance(inline_data, Mapping) else None
|
|
1020
|
+
mime_type = str(item.get("mime_type") or inline_mime or "").strip().lower()
|
|
1021
|
+
source = str(item.get("file_data") or item.get("file_url") or "")
|
|
1022
|
+
data_url_match = re.match(r"data:([^;,]+)", source)
|
|
1023
|
+
if not mime_type and data_url_match is not None:
|
|
1024
|
+
mime_type = data_url_match.group(1).strip().lower()
|
|
1025
|
+
is_text = mime_type.startswith("text/") or path.suffix.lower() in _TEXT_ATTACHMENT_SUFFIXES
|
|
1026
|
+
header = f'<uploaded_attachment name="{name}" path="{path}">'
|
|
1027
|
+
if not is_text:
|
|
1028
|
+
return (
|
|
1029
|
+
f"{header}\n"
|
|
1030
|
+
"The uploaded file is available at the path above. Read it with an appropriate "
|
|
1031
|
+
"tool before answering questions about its contents.\n"
|
|
1032
|
+
"</uploaded_attachment>"
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
raw = path.read_bytes()
|
|
1036
|
+
truncated = len(raw) > _MAX_INLINE_ATTACHMENT_TEXT_BYTES
|
|
1037
|
+
text = raw[:_MAX_INLINE_ATTACHMENT_TEXT_BYTES].decode("utf-8", errors="replace")
|
|
1038
|
+
suffix = "\n[attachment content truncated]" if truncated else ""
|
|
1039
|
+
return f"{header}\n{text}{suffix}\n</uploaded_attachment>"
|
|
1040
|
+
|
|
1041
|
+
|
|
940
1042
|
__all__ = ["CodexRuntimeAdapter"]
|