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/model_proxy/server.py
CHANGED
|
@@ -18,7 +18,13 @@ from fastapi import FastAPI, Request
|
|
|
18
18
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
19
19
|
|
|
20
20
|
from .config import _LOOPBACK_HOSTS, ProxyConfig
|
|
21
|
-
from .transform import
|
|
21
|
+
from .transform import (
|
|
22
|
+
Streamer,
|
|
23
|
+
UnsupportedToolsError,
|
|
24
|
+
chat_to_response,
|
|
25
|
+
clamp_reasoning_effort,
|
|
26
|
+
responses_to_chat,
|
|
27
|
+
)
|
|
22
28
|
|
|
23
29
|
logger = logging.getLogger(__name__)
|
|
24
30
|
|
|
@@ -147,7 +153,7 @@ def create_app(config: ProxyConfig) -> FastAPI:
|
|
|
147
153
|
try:
|
|
148
154
|
chat_req, restore_map = responses_to_chat(body)
|
|
149
155
|
except UnsupportedToolsError:
|
|
150
|
-
logger.info("responses request uses unsupported tools")
|
|
156
|
+
logger.info("responses request uses unsupported tools", exc_info=True)
|
|
151
157
|
return JSONResponse(
|
|
152
158
|
status_code=400,
|
|
153
159
|
content={
|
|
@@ -159,13 +165,26 @@ def create_app(config: ProxyConfig) -> FastAPI:
|
|
|
159
165
|
)
|
|
160
166
|
# codex 会对内建能力发内部伪模型名(如 auto_review guardian 用 codex-auto-review),
|
|
161
167
|
# 单上游代理必须落回配置的真实模型,否则上游按未知模型 403。
|
|
162
|
-
|
|
168
|
+
# 真实模型名(codex 端已配置 model= 或 thread 级 model 覆盖)必须原样透传:
|
|
169
|
+
# RunAgent 的 Model 覆盖靠它生效,整体改写会把覆盖吞掉(终验 403 根因)。
|
|
170
|
+
requested_model = str(chat_req.get("model") or "")
|
|
171
|
+
if (
|
|
172
|
+
config.upstream_model
|
|
173
|
+
and requested_model.startswith("codex-")
|
|
174
|
+
and requested_model != config.upstream_model
|
|
175
|
+
):
|
|
163
176
|
logger.debug(
|
|
164
|
-
"responses model rewrite: %s -> %s",
|
|
165
|
-
|
|
177
|
+
"responses pseudo model rewrite: %s -> %s",
|
|
178
|
+
requested_model,
|
|
166
179
|
config.upstream_model,
|
|
167
180
|
)
|
|
168
181
|
chat_req["model"] = config.upstream_model
|
|
182
|
+
# 上游模型族的 reasoning_effort 上限不同(qwen3.7 对 xhigh 400),
|
|
183
|
+
# 按改写后的真实上游模型钳位(codex 对自家模型默认发 xhigh)。
|
|
184
|
+
if chat_req.get("reasoning_effort"):
|
|
185
|
+
chat_req["reasoning_effort"] = clamp_reasoning_effort(
|
|
186
|
+
chat_req["model"], chat_req["reasoning_effort"]
|
|
187
|
+
)
|
|
169
188
|
rid = "resp_" + uuid.uuid4().hex[:24]
|
|
170
189
|
model = body.get("model")
|
|
171
190
|
started = time.monotonic()
|
ksadk/model_proxy/transform.py
CHANGED
|
@@ -198,6 +198,7 @@ def convert_tools(tools):
|
|
|
198
198
|
else:
|
|
199
199
|
t = t or {}
|
|
200
200
|
import json as _json
|
|
201
|
+
|
|
201
202
|
try:
|
|
202
203
|
unsupported.append(_json.dumps(t, ensure_ascii=False)[:300])
|
|
203
204
|
except Exception:
|
|
@@ -224,6 +225,32 @@ def convert_tool_choice(tc):
|
|
|
224
225
|
return tc
|
|
225
226
|
|
|
226
227
|
|
|
228
|
+
# 各上游模型族的 reasoning_effort 上限(qwen3.7 系实测 xhigh 400;报错文案会列出
|
|
229
|
+
# xhigh 但 DashScope 后端实际只认到 high,故不能用报错文案反推)。未列出的族原样透传。
|
|
230
|
+
_EFFORT_RANK = {"none": 0, "minimal": 1, "low": 2, "medium": 3, "high": 4, "xhigh": 5}
|
|
231
|
+
_EFFORT_CAP_PREFIXES = (
|
|
232
|
+
("qwen3.7", "high"),
|
|
233
|
+
("qwen3.6", "high"),
|
|
234
|
+
("qwen3.5", "high"),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def clamp_reasoning_effort(model, effort):
|
|
239
|
+
"""把超出上游模型族支持上限的 reasoning_effort 钳到最高合法档。
|
|
240
|
+
|
|
241
|
+
codex 对自家模型默认发 xhigh;qwen3.7 系上游 400,需钳到 high。
|
|
242
|
+
未知档位/未知模型族原样返回(不做发明式映射)。
|
|
243
|
+
"""
|
|
244
|
+
if not isinstance(effort, str) or effort not in _EFFORT_RANK:
|
|
245
|
+
return effort
|
|
246
|
+
for prefix, cap in _EFFORT_CAP_PREFIXES:
|
|
247
|
+
if isinstance(model, str) and model.startswith(prefix):
|
|
248
|
+
if _EFFORT_RANK[effort] > _EFFORT_RANK[cap]:
|
|
249
|
+
return cap
|
|
250
|
+
return effort
|
|
251
|
+
return effort
|
|
252
|
+
|
|
253
|
+
|
|
227
254
|
def _convert_text_format(fmt):
|
|
228
255
|
"""responses text.format -> chat response_format(structured output 结构重组)。
|
|
229
256
|
|
|
@@ -243,6 +270,35 @@ def _convert_text_format(fmt):
|
|
|
243
270
|
return None
|
|
244
271
|
|
|
245
272
|
|
|
273
|
+
def _promote_additional_tools(body):
|
|
274
|
+
"""Promote Codex Harness dynamic tool input items to Responses tools.
|
|
275
|
+
|
|
276
|
+
Codex 0.147 no longer always sends tool declarations in the top-level
|
|
277
|
+
``tools`` field. It can prepend one or more developer input items shaped
|
|
278
|
+
as ``{"type": "additional_tools", "tools": [...]}``. Chat Completions
|
|
279
|
+
has no equivalent input item, so the proxy must merge those declarations
|
|
280
|
+
into the canonical Responses tool list before namespace flattening.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
inp = body.get("input")
|
|
284
|
+
if not isinstance(inp, list):
|
|
285
|
+
return
|
|
286
|
+
promoted = []
|
|
287
|
+
retained = []
|
|
288
|
+
for item in inp:
|
|
289
|
+
if isinstance(item, dict) and item.get("type") == "additional_tools":
|
|
290
|
+
tools = item.get("tools")
|
|
291
|
+
if isinstance(tools, list):
|
|
292
|
+
promoted.extend(tool for tool in tools if isinstance(tool, dict))
|
|
293
|
+
continue
|
|
294
|
+
retained.append(item)
|
|
295
|
+
if not promoted:
|
|
296
|
+
return
|
|
297
|
+
existing = body.get("tools")
|
|
298
|
+
body["tools"] = (list(existing) if isinstance(existing, list) else []) + promoted
|
|
299
|
+
body["input"] = retained
|
|
300
|
+
|
|
301
|
+
|
|
246
302
|
def responses_to_chat(body):
|
|
247
303
|
"""responses 请求 -> chat 请求;返回 (chat_req, restore_map)。
|
|
248
304
|
|
|
@@ -251,6 +307,7 @@ def responses_to_chat(body):
|
|
|
251
307
|
"""
|
|
252
308
|
from .namespace import flatten_request_namespaces
|
|
253
309
|
|
|
310
|
+
_promote_additional_tools(body)
|
|
254
311
|
restore_map = flatten_request_namespaces(body)
|
|
255
312
|
out = {"model": body.get("model")}
|
|
256
313
|
msgs = []
|
|
@@ -273,8 +330,9 @@ def responses_to_chat(body):
|
|
|
273
330
|
rf = _convert_text_format(text.get("format"))
|
|
274
331
|
if rf is not None:
|
|
275
332
|
out["response_format"] = rf
|
|
276
|
-
|
|
277
|
-
|
|
333
|
+
# text.verbosity("low"/"high")不转发:chat completions 无标准字段,
|
|
334
|
+
# kspmas 把顶层 verbosity 反序列化为 i32,字符串值直接 400(glm-5.2 实测;
|
|
335
|
+
# cc-switch 也不透传该字段)。
|
|
278
336
|
if body.get("prompt_cache_key"):
|
|
279
337
|
out["prompt_cache_key"] = body["prompt_cache_key"]
|
|
280
338
|
tools = convert_tools(body.get("tools"))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Local observability exports and trajectory projections."""
|
|
2
|
+
|
|
3
|
+
from ksadk.observability.session_log import (
|
|
4
|
+
SESSION_LOG_SCHEMA,
|
|
5
|
+
SessionLogError,
|
|
6
|
+
SessionLogResult,
|
|
7
|
+
export_session_log,
|
|
8
|
+
verify_session_log,
|
|
9
|
+
)
|
|
10
|
+
from ksadk.observability.trajectory import (
|
|
11
|
+
PROJECTION_VERSION,
|
|
12
|
+
encode_sse,
|
|
13
|
+
project_trajectory_event,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"SESSION_LOG_SCHEMA",
|
|
18
|
+
"PROJECTION_VERSION",
|
|
19
|
+
"SessionLogError",
|
|
20
|
+
"SessionLogResult",
|
|
21
|
+
"export_session_log",
|
|
22
|
+
"encode_sse",
|
|
23
|
+
"project_trajectory_event",
|
|
24
|
+
"verify_session_log",
|
|
25
|
+
]
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"""Versioned, fixed-watermark JSONL exports for canonical RuntimeEvents.
|
|
2
|
+
|
|
3
|
+
New exports use the schema-v2 RuntimeEvent envelope. The legacy v1 log is
|
|
4
|
+
still accepted by :func:`verify_session_log` so existing diagnostic files stay
|
|
5
|
+
readable, but a v2 Store must never be coerced back into a v1 write model just
|
|
6
|
+
to produce an export.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import tempfile
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, TextIO
|
|
17
|
+
|
|
18
|
+
from ksadk.events.canonical import dump_runtime_event, parse_runtime_event
|
|
19
|
+
from ksadk.events.store import RuntimeEventStore
|
|
20
|
+
from ksadk.events.v1_compat import EventTypeV1, RuntimeEventV1
|
|
21
|
+
from ksadk.sessions.base import BaseSessionService
|
|
22
|
+
|
|
23
|
+
SESSION_LOG_SCHEMA = "ksadk.session-log/v2"
|
|
24
|
+
_SESSION_LOG_VERSION = 2
|
|
25
|
+
_LEGACY_SESSION_LOG_SCHEMA = "ksadk.session-log/v1"
|
|
26
|
+
_LEGACY_SESSION_LOG_VERSION = 1
|
|
27
|
+
_PAGE_SIZE = 500
|
|
28
|
+
_LEGACY_PACKED_EVENT_TYPES = {
|
|
29
|
+
EventTypeV1.TEXT_DELTA: "text-chunks",
|
|
30
|
+
EventTypeV1.REASONING_DELTA: "reasoning-chunks",
|
|
31
|
+
}
|
|
32
|
+
_LEGACY_PACKED_RECORD_TYPES = {value: key for key, value in _LEGACY_PACKED_EVENT_TYPES.items()}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SessionLogError(ValueError):
|
|
36
|
+
"""A stable Session Log export or validation failure."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class SessionLogResult:
|
|
41
|
+
path: Path
|
|
42
|
+
event_count: int
|
|
43
|
+
first_seq_id: int | None
|
|
44
|
+
last_seq_id: int | None
|
|
45
|
+
exported_through_seq_id: int | None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _raise(code: str, message: str) -> None:
|
|
49
|
+
raise SessionLogError(f"{code}: {message}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _write_json_line(stream: TextIO, value: dict[str, Any]) -> None:
|
|
53
|
+
stream.write(
|
|
54
|
+
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _legacy_packed_base(event: RuntimeEventV1) -> dict[str, Any]:
|
|
59
|
+
value = event.to_dict()
|
|
60
|
+
for key in ("event_id", "seq_id", "timestamp"):
|
|
61
|
+
value.pop(key)
|
|
62
|
+
payload = dict(value["payload"])
|
|
63
|
+
payload.pop("text")
|
|
64
|
+
value["payload"] = payload
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _write_legacy_event_run(stream: TextIO, events: list[RuntimeEventV1]) -> None:
|
|
69
|
+
if len(events) < 3:
|
|
70
|
+
for event in events:
|
|
71
|
+
_write_json_line(stream, event.to_dict())
|
|
72
|
+
return
|
|
73
|
+
_write_json_line(
|
|
74
|
+
stream,
|
|
75
|
+
{
|
|
76
|
+
"type": _LEGACY_PACKED_EVENT_TYPES[events[0].event_type],
|
|
77
|
+
"seq0": events[0].seq_id,
|
|
78
|
+
"data": {
|
|
79
|
+
"base": _legacy_packed_base(events[0]),
|
|
80
|
+
"event_ids": [event.event_id for event in events],
|
|
81
|
+
"timestamps": [event.timestamp for event in events],
|
|
82
|
+
"texts": [event.payload["text"] for event in events],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _same_legacy_event_run(events: list[RuntimeEventV1], event: RuntimeEventV1) -> bool:
|
|
89
|
+
return (
|
|
90
|
+
bool(events)
|
|
91
|
+
and event.event_type == events[0].event_type
|
|
92
|
+
and event.seq_id == events[-1].seq_id + 1
|
|
93
|
+
and _legacy_packed_base(event) == _legacy_packed_base(events[0])
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def export_session_log(
|
|
98
|
+
session_service: BaseSessionService,
|
|
99
|
+
session_id: str,
|
|
100
|
+
target: Path | str,
|
|
101
|
+
*,
|
|
102
|
+
invocation_id: str | None = None,
|
|
103
|
+
) -> SessionLogResult:
|
|
104
|
+
"""Export committed RuntimeEvents through a fixed session cursor."""
|
|
105
|
+
session = await session_service.get_session_metadata(session_id)
|
|
106
|
+
if session is None:
|
|
107
|
+
_raise("SESSION_LOG_SESSION_NOT_FOUND", f"session {session_id!r} not found")
|
|
108
|
+
|
|
109
|
+
target_path = Path(target)
|
|
110
|
+
if target_path.exists():
|
|
111
|
+
_raise("SESSION_LOG_TARGET_EXISTS", f"target {target_path} already exists")
|
|
112
|
+
|
|
113
|
+
store = RuntimeEventStore(session_service)
|
|
114
|
+
tail = await store.list(session_id, limit=1)
|
|
115
|
+
cutoff = tail[-1].seq if tail else None
|
|
116
|
+
header: dict[str, Any] = {
|
|
117
|
+
"type": "session",
|
|
118
|
+
"schema": SESSION_LOG_SCHEMA,
|
|
119
|
+
"version": _SESSION_LOG_VERSION,
|
|
120
|
+
"session_id": session.id,
|
|
121
|
+
"agent_id": session.agent_id,
|
|
122
|
+
"user_id": session.user_id,
|
|
123
|
+
"created_at": session.created_at,
|
|
124
|
+
"updated_at": session.updated_at,
|
|
125
|
+
"exported_through_seq_id": cutoff,
|
|
126
|
+
"event_schema_version": 2,
|
|
127
|
+
}
|
|
128
|
+
if invocation_id is not None:
|
|
129
|
+
header["invocation_id"] = invocation_id
|
|
130
|
+
|
|
131
|
+
temporary_path: Path | None = None
|
|
132
|
+
published = False
|
|
133
|
+
event_count = 0
|
|
134
|
+
first_seq_id: int | None = None
|
|
135
|
+
last_seq_id: int | None = None
|
|
136
|
+
try:
|
|
137
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
138
|
+
dir=target_path.parent,
|
|
139
|
+
prefix=".session-log-",
|
|
140
|
+
suffix=".tmp",
|
|
141
|
+
)
|
|
142
|
+
temporary_path = Path(temporary_name)
|
|
143
|
+
os.fchmod(descriptor, 0o600)
|
|
144
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
|
|
145
|
+
_write_json_line(stream, header)
|
|
146
|
+
cursor = 0
|
|
147
|
+
while cutoff is not None and cursor < cutoff:
|
|
148
|
+
window_end = min(cursor + _PAGE_SIZE, cutoff)
|
|
149
|
+
events = await store.page(
|
|
150
|
+
session_id,
|
|
151
|
+
after_seq=cursor,
|
|
152
|
+
before_seq=window_end + 1,
|
|
153
|
+
limit=_PAGE_SIZE,
|
|
154
|
+
)
|
|
155
|
+
for event in events:
|
|
156
|
+
if invocation_id is not None and event.run_id != invocation_id:
|
|
157
|
+
continue
|
|
158
|
+
# A v2 fact is the durable source of truth. The v1
|
|
159
|
+
# packed delta format cannot losslessly encode all v2
|
|
160
|
+
# item operations, so v2 logs retain one canonical event
|
|
161
|
+
# per row instead of silently projecting/dropping facts.
|
|
162
|
+
_write_json_line(stream, dump_runtime_event(event))
|
|
163
|
+
event_count += 1
|
|
164
|
+
first_seq_id = first_seq_id or event.seq
|
|
165
|
+
last_seq_id = event.seq
|
|
166
|
+
cursor = window_end
|
|
167
|
+
stream.flush()
|
|
168
|
+
os.fsync(stream.fileno())
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
os.link(temporary_path, target_path)
|
|
172
|
+
published = True
|
|
173
|
+
except FileExistsError as exc:
|
|
174
|
+
raise SessionLogError(
|
|
175
|
+
f"SESSION_LOG_TARGET_EXISTS: target {target_path} already exists"
|
|
176
|
+
) from exc
|
|
177
|
+
except OSError as exc:
|
|
178
|
+
raise SessionLogError(
|
|
179
|
+
"SESSION_LOG_ATOMIC_PUBLISH_UNSUPPORTED: "
|
|
180
|
+
f"cannot atomically publish {target_path}"
|
|
181
|
+
) from exc
|
|
182
|
+
|
|
183
|
+
directory_fd = os.open(target_path.parent, os.O_RDONLY)
|
|
184
|
+
try:
|
|
185
|
+
os.fsync(directory_fd)
|
|
186
|
+
finally:
|
|
187
|
+
os.close(directory_fd)
|
|
188
|
+
except SessionLogError:
|
|
189
|
+
if published:
|
|
190
|
+
target_path.unlink(missing_ok=True)
|
|
191
|
+
raise
|
|
192
|
+
except OSError as exc:
|
|
193
|
+
if published:
|
|
194
|
+
target_path.unlink(missing_ok=True)
|
|
195
|
+
raise SessionLogError(f"SESSION_LOG_WRITE_FAILED: {target_path}") from exc
|
|
196
|
+
finally:
|
|
197
|
+
if temporary_path is not None:
|
|
198
|
+
temporary_path.unlink(missing_ok=True)
|
|
199
|
+
|
|
200
|
+
return SessionLogResult(
|
|
201
|
+
path=target_path,
|
|
202
|
+
event_count=event_count,
|
|
203
|
+
first_seq_id=first_seq_id,
|
|
204
|
+
last_seq_id=last_seq_id,
|
|
205
|
+
exported_through_seq_id=cutoff,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _read_json_line(raw: str, line_number: int) -> dict[str, Any]:
|
|
210
|
+
try:
|
|
211
|
+
value = json.loads(raw)
|
|
212
|
+
except json.JSONDecodeError as exc:
|
|
213
|
+
raise SessionLogError(f"SESSION_LOG_INVALID: line {line_number} is not valid JSON") from exc
|
|
214
|
+
if not isinstance(value, dict):
|
|
215
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} must be an object")
|
|
216
|
+
return value
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _legacy_events_from_record(
|
|
220
|
+
value: dict[str, Any], line_number: int, *, allow_packed: bool
|
|
221
|
+
) -> list[RuntimeEventV1]:
|
|
222
|
+
record_type = value.get("type")
|
|
223
|
+
if record_type not in _LEGACY_PACKED_RECORD_TYPES:
|
|
224
|
+
try:
|
|
225
|
+
return [RuntimeEventV1.from_dict(value)]
|
|
226
|
+
except (TypeError, ValueError) as exc:
|
|
227
|
+
raise SessionLogError(
|
|
228
|
+
f"SESSION_LOG_INVALID: line {line_number} is not a RuntimeEvent"
|
|
229
|
+
) from exc
|
|
230
|
+
if not allow_packed:
|
|
231
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} uses packed rows in v1")
|
|
232
|
+
|
|
233
|
+
seq0 = value.get("seq0")
|
|
234
|
+
data = value.get("data")
|
|
235
|
+
if isinstance(seq0, bool) or not isinstance(seq0, int) or seq0 < 0:
|
|
236
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} packed seq0 is invalid")
|
|
237
|
+
if not isinstance(data, dict) or not isinstance(data.get("base"), dict):
|
|
238
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} packed data is invalid")
|
|
239
|
+
event_ids = data.get("event_ids")
|
|
240
|
+
timestamps = data.get("timestamps")
|
|
241
|
+
texts = data.get("texts")
|
|
242
|
+
if not all(isinstance(items, list) for items in (event_ids, timestamps, texts)):
|
|
243
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} packed arrays are invalid")
|
|
244
|
+
if len(event_ids) < 3 or len(event_ids) != len(timestamps) or len(event_ids) != len(texts):
|
|
245
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} packed arrays do not align")
|
|
246
|
+
|
|
247
|
+
base = dict(data["base"])
|
|
248
|
+
expected_event_type = _LEGACY_PACKED_RECORD_TYPES[record_type]
|
|
249
|
+
if base.get("event_type") != expected_event_type:
|
|
250
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} packed event type does not match")
|
|
251
|
+
payload = base.get("payload")
|
|
252
|
+
if not isinstance(payload, dict) or "text" in payload:
|
|
253
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} packed payload is invalid")
|
|
254
|
+
|
|
255
|
+
events: list[RuntimeEventV1] = []
|
|
256
|
+
for index, (event_id, timestamp, text) in enumerate(
|
|
257
|
+
zip(event_ids, timestamps, texts, strict=True)
|
|
258
|
+
):
|
|
259
|
+
value = {
|
|
260
|
+
**base,
|
|
261
|
+
"event_id": event_id,
|
|
262
|
+
"seq_id": seq0 + index,
|
|
263
|
+
"timestamp": timestamp,
|
|
264
|
+
"payload": {**payload, "text": text},
|
|
265
|
+
}
|
|
266
|
+
try:
|
|
267
|
+
events.append(RuntimeEventV1.from_dict(value))
|
|
268
|
+
except (TypeError, ValueError) as exc:
|
|
269
|
+
raise SessionLogError(
|
|
270
|
+
f"SESSION_LOG_INVALID: line {line_number} contains an invalid packed event"
|
|
271
|
+
) from exc
|
|
272
|
+
return events
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _validate_header(
|
|
276
|
+
header: dict[str, Any], *, schema: str, version: int
|
|
277
|
+
) -> tuple[int | None, str | None]:
|
|
278
|
+
if header.get("type") != "session":
|
|
279
|
+
_raise("SESSION_LOG_INVALID", "first line must be a session header")
|
|
280
|
+
if header.get("schema") != schema or header.get("version") != version:
|
|
281
|
+
_raise("SESSION_LOG_INVALID", "unsupported schema")
|
|
282
|
+
session_id = header.get("session_id")
|
|
283
|
+
if not isinstance(session_id, str) or not session_id:
|
|
284
|
+
_raise("SESSION_LOG_INVALID", "header session id is required")
|
|
285
|
+
cutoff = header.get("exported_through_seq_id")
|
|
286
|
+
if cutoff is not None and (
|
|
287
|
+
isinstance(cutoff, bool) or not isinstance(cutoff, int) or cutoff < 0
|
|
288
|
+
):
|
|
289
|
+
_raise("SESSION_LOG_INVALID", "exported watermark must be null or non-negative")
|
|
290
|
+
return cutoff, header.get("invocation_id")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _finish_verification(
|
|
294
|
+
*,
|
|
295
|
+
source: Path,
|
|
296
|
+
event_count: int,
|
|
297
|
+
first_seq_id: int | None,
|
|
298
|
+
last_seq_id: int | None,
|
|
299
|
+
cutoff: int | None,
|
|
300
|
+
filtered: bool,
|
|
301
|
+
) -> SessionLogResult:
|
|
302
|
+
if not filtered:
|
|
303
|
+
if cutoff is None and event_count:
|
|
304
|
+
_raise("SESSION_LOG_INVALID", "empty watermark cannot contain events")
|
|
305
|
+
if cutoff is not None and last_seq_id != cutoff:
|
|
306
|
+
_raise("SESSION_LOG_INVALID", "full session must end at exported watermark")
|
|
307
|
+
return SessionLogResult(
|
|
308
|
+
path=source,
|
|
309
|
+
event_count=event_count,
|
|
310
|
+
first_seq_id=first_seq_id,
|
|
311
|
+
last_seq_id=last_seq_id,
|
|
312
|
+
exported_through_seq_id=cutoff,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _verify_v2(stream: TextIO, *, source: Path, header: dict[str, Any]) -> SessionLogResult:
|
|
317
|
+
cutoff, invocation_id = _validate_header(
|
|
318
|
+
header, schema=SESSION_LOG_SCHEMA, version=_SESSION_LOG_VERSION
|
|
319
|
+
)
|
|
320
|
+
filtered = invocation_id is not None
|
|
321
|
+
event_count = 0
|
|
322
|
+
first_seq_id: int | None = None
|
|
323
|
+
last_seq_id: int | None = None
|
|
324
|
+
for line_number, raw in enumerate(stream, start=2):
|
|
325
|
+
if not raw.strip():
|
|
326
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} is empty")
|
|
327
|
+
try:
|
|
328
|
+
event = parse_runtime_event(_read_json_line(raw, line_number))
|
|
329
|
+
except (TypeError, ValueError) as exc:
|
|
330
|
+
raise SessionLogError(
|
|
331
|
+
f"SESSION_LOG_INVALID: line {line_number} is not a RuntimeEvent/v2"
|
|
332
|
+
) from exc
|
|
333
|
+
if filtered and event.run_id != invocation_id:
|
|
334
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} run id does not match")
|
|
335
|
+
if last_seq_id is not None and event.seq <= last_seq_id:
|
|
336
|
+
_raise("SESSION_LOG_INVALID", "event seq must be strictly increasing")
|
|
337
|
+
if cutoff is None or event.seq > cutoff:
|
|
338
|
+
_raise("SESSION_LOG_INVALID", "event seq exceeds exported watermark")
|
|
339
|
+
if not filtered:
|
|
340
|
+
expected = 1 if last_seq_id is None else last_seq_id + 1
|
|
341
|
+
if event.seq != expected:
|
|
342
|
+
_raise("SESSION_LOG_INVALID", "full session seq must be continuous")
|
|
343
|
+
event_count += 1
|
|
344
|
+
first_seq_id = first_seq_id or event.seq
|
|
345
|
+
last_seq_id = event.seq
|
|
346
|
+
return _finish_verification(
|
|
347
|
+
source=source,
|
|
348
|
+
event_count=event_count,
|
|
349
|
+
first_seq_id=first_seq_id,
|
|
350
|
+
last_seq_id=last_seq_id,
|
|
351
|
+
cutoff=cutoff,
|
|
352
|
+
filtered=filtered,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _verify_v1(stream: TextIO, *, source: Path, header: dict[str, Any]) -> SessionLogResult:
|
|
357
|
+
cutoff, invocation_id = _validate_header(
|
|
358
|
+
header, schema=_LEGACY_SESSION_LOG_SCHEMA, version=_LEGACY_SESSION_LOG_VERSION
|
|
359
|
+
)
|
|
360
|
+
session_id = str(header["session_id"])
|
|
361
|
+
filtered = invocation_id is not None
|
|
362
|
+
event_count = 0
|
|
363
|
+
first_seq_id: int | None = None
|
|
364
|
+
last_seq_id: int | None = None
|
|
365
|
+
for line_number, raw in enumerate(stream, start=2):
|
|
366
|
+
if not raw.strip():
|
|
367
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} is empty")
|
|
368
|
+
value = _read_json_line(raw, line_number)
|
|
369
|
+
for event in _legacy_events_from_record(value, line_number, allow_packed=True):
|
|
370
|
+
if event.session_id != session_id:
|
|
371
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} session id does not match")
|
|
372
|
+
if filtered and event.invocation_id != invocation_id:
|
|
373
|
+
_raise("SESSION_LOG_INVALID", f"line {line_number} invocation id does not match")
|
|
374
|
+
if last_seq_id is not None and event.seq_id <= last_seq_id:
|
|
375
|
+
_raise("SESSION_LOG_INVALID", "event seq_id must be strictly increasing")
|
|
376
|
+
if cutoff is None or event.seq_id > cutoff:
|
|
377
|
+
_raise("SESSION_LOG_INVALID", "event seq_id exceeds exported watermark")
|
|
378
|
+
if not filtered:
|
|
379
|
+
expected = 1 if last_seq_id is None else last_seq_id + 1
|
|
380
|
+
if event.seq_id != expected:
|
|
381
|
+
_raise("SESSION_LOG_INVALID", "full session seq_id must be continuous")
|
|
382
|
+
event_count += 1
|
|
383
|
+
first_seq_id = first_seq_id or event.seq_id
|
|
384
|
+
last_seq_id = event.seq_id
|
|
385
|
+
return _finish_verification(
|
|
386
|
+
source=source,
|
|
387
|
+
event_count=event_count,
|
|
388
|
+
first_seq_id=first_seq_id,
|
|
389
|
+
last_seq_id=last_seq_id,
|
|
390
|
+
cutoff=cutoff,
|
|
391
|
+
filtered=filtered,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def verify_session_log(path: Path | str) -> SessionLogResult:
|
|
396
|
+
"""Stream and validate a v2 Session Log or a legacy v1 diagnostic file."""
|
|
397
|
+
source = Path(path)
|
|
398
|
+
try:
|
|
399
|
+
stream = source.open(encoding="utf-8")
|
|
400
|
+
except OSError as exc:
|
|
401
|
+
raise SessionLogError(f"SESSION_LOG_READ_FAILED: {source}") from exc
|
|
402
|
+
with stream:
|
|
403
|
+
first_line = stream.readline()
|
|
404
|
+
if not first_line:
|
|
405
|
+
_raise("SESSION_LOG_INVALID", "missing session header")
|
|
406
|
+
header = _read_json_line(first_line, 1)
|
|
407
|
+
if header.get("schema") == SESSION_LOG_SCHEMA:
|
|
408
|
+
return _verify_v2(stream, source=source, header=header)
|
|
409
|
+
if header.get("schema") == _LEGACY_SESSION_LOG_SCHEMA:
|
|
410
|
+
return _verify_v1(stream, source=source, header=header)
|
|
411
|
+
_raise("SESSION_LOG_INVALID", "unsupported schema")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
__all__ = [
|
|
415
|
+
"SESSION_LOG_SCHEMA",
|
|
416
|
+
"SessionLogError",
|
|
417
|
+
"SessionLogResult",
|
|
418
|
+
"export_session_log",
|
|
419
|
+
"verify_session_log",
|
|
420
|
+
]
|