agentengine-sdk-python 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +43 -7
- agentengine_sdk_python-0.8.2.dist-info/RECORD +778 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +1 -1
- ksadk/a2a/__init__.py +2 -0
- ksadk/a2a/_space_client_events.py +381 -0
- ksadk/a2a/bootstrap.py +0 -2
- ksadk/a2a/card.py +6 -111
- ksadk/a2a/control_plane.py +239 -1
- ksadk/a2a/event_adapter.py +156 -64
- ksadk/a2a/executor.py +195 -172
- ksadk/a2a/routes.py +12 -5
- ksadk/a2a/server.py +1 -8
- ksadk/a2a/service_env.py +33 -0
- ksadk/a2a/space_client.py +43 -349
- ksadk/a2a_card.py +101 -0
- ksadk/a2ui/core.py +222 -53
- ksadk/agui/_agent_helpers.py +243 -0
- ksadk/agui/a2ui_projection.py +12 -5
- ksadk/agui/agent.py +474 -275
- ksadk/agui/config.py +11 -5
- ksadk/agui/routes.py +7 -15
- ksadk/api/client.py +530 -40
- ksadk/api/environment.py +27 -0
- ksadk/builders/code_builder.py +352 -53
- ksadk/builders/container_builder.py +71 -44
- ksadk/builders/framework_requirements.py +2 -1
- ksadk/builders/ks3_uploader.py +2 -2
- ksadk/builders/managed_runtime_builder.py +60 -28
- ksadk/cli/__init__.py +25 -0
- ksadk/cli/cmd_a2a.py +18 -31
- ksadk/cli/cmd_create.py +13 -26
- ksadk/cli/cmd_dashboard.py +15 -1
- ksadk/cli/cmd_deploy.py +28 -3
- ksadk/cli/cmd_eval.py +443 -0
- ksadk/cli/cmd_evalset.py +284 -0
- ksadk/cli/cmd_files.py +5 -1
- ksadk/cli/cmd_hermes.py +137 -163
- ksadk/cli/cmd_invoke.py +28 -38
- ksadk/cli/cmd_launch.py +12 -0
- ksadk/cli/cmd_managed_runtime.py +95 -0
- ksadk/cli/cmd_observe.py +85 -0
- ksadk/cli/cmd_openclaw.py +85 -479
- ksadk/cli/cmd_replay.py +28 -4
- ksadk/cli/cmd_run.py +29 -67
- ksadk/cli/cmd_studio.py +201 -0
- ksadk/cli/cmd_web.py +15 -6
- ksadk/cli/dry_run.py +2 -1
- ksadk/cli/env_options.py +83 -0
- ksadk/cli/global_options.py +3 -1
- ksadk/cli/hermes_env.py +151 -0
- ksadk/cli/invoke_payload.py +49 -0
- ksadk/cli/network_options.py +7 -5
- ksadk/cli/openclaw_env.py +458 -0
- ksadk/cli/runtime_bootstrap.py +51 -0
- ksadk/cli/ui.py +6 -0
- ksadk/cli/workflow_common.py +5 -5
- ksadk/codex/__init__.py +3 -3
- ksadk/codex/client.py +833 -49
- ksadk/codex/phase.py +1 -1
- ksadk/codex/runtime.py +720 -127
- ksadk/common/kop_client.py +232 -0
- ksadk/configs/__init__.py +1 -3
- ksadk/configs/env_registry.py +244 -45
- ksadk/configs/env_registry_pcm.py +181 -0
- ksadk/configs/env_var_spec.py +18 -0
- ksadk/configs/global_config.py +6 -1
- ksadk/configs/settings.py +5 -108
- ksadk/context_engine/__init__.py +98 -0
- ksadk/context_engine/assembler.py +176 -0
- ksadk/context_engine/baseline.py +419 -0
- ksadk/context_engine/cache_observability.py +228 -0
- ksadk/context_engine/capabilities.py +443 -0
- ksadk/context_engine/contributors.py +344 -0
- ksadk/context_engine/hosted_pipeline.py +417 -0
- ksadk/context_engine/models.py +117 -0
- ksadk/context_engine/planner.py +698 -0
- ksadk/context_engine/policies.py +319 -0
- ksadk/context_engine/projection.py +31 -0
- ksadk/context_engine/shadow_plan.py +266 -0
- ksadk/context_engine/tokenizer.py +164 -0
- ksadk/conversations/__init__.py +85 -94
- ksadk/conversations/context.py +126 -6
- ksadk/conversations/message_projection.py +292 -28
- ksadk/conversations/model_context.py +49 -3
- ksadk/conversations/runtime_compaction.py +207 -5
- ksadk/conversations/runtime_input.py +193 -3
- ksadk/conversations/runtime_invocation.py +81 -3
- ksadk/conversations/runtime_observability.py +157 -4
- ksadk/conversations/runtime_payloads.py +42 -0
- ksadk/conversations/runtime_preparation.py +280 -2
- ksadk/conversations/runtime_resume.py +71 -32
- ksadk/conversations/runtime_stream_events.py +94 -9
- ksadk/conversations/runtime_streaming.py +175 -22
- ksadk/conversations/semantic_summary.py +311 -1
- ksadk/conversations/session_lock.py +51 -0
- ksadk/deployment/env_forward.py +72 -0
- ksadk/deployment/managed_runtime.py +38 -0
- ksadk/deployment/manager.py +24 -8
- ksadk/deployment/providers/serverless.py +228 -92
- ksadk/evaluation/__init__.py +116 -0
- ksadk/evaluation/a2a_adapter.py +452 -0
- ksadk/evaluation/adapters.py +67 -0
- ksadk/evaluation/agent_eval_client.py +395 -0
- ksadk/evaluation/cloud_binding.py +80 -0
- ksadk/evaluation/cloud_converter.py +194 -0
- ksadk/evaluation/cloud_service.py +199 -0
- ksadk/evaluation/contracts.py +478 -0
- ksadk/evaluation/evalset.py +351 -0
- ksadk/evaluation/evaluators.py +627 -0
- ksadk/evaluation/evidence.py +258 -0
- ksadk/evaluation/executor.py +143 -0
- ksadk/evaluation/local_adapter.py +550 -0
- ksadk/evaluation/service_env.py +27 -0
- ksadk/evaluation/storage.py +68 -0
- ksadk/evaluation/studio_build_adapter.py +253 -0
- ksadk/evaluation/target.py +82 -0
- ksadk/events/__init__.py +13 -13
- ksadk/events/_v1_compat/__init__.py +1 -0
- ksadk/events/_v1_compat/models.py +299 -0
- ksadk/events/_v1_compat/parser.py +247 -0
- ksadk/events/_v1_compat/projection.py +753 -0
- ksadk/events/adapters/__init__.py +34 -0
- ksadk/events/adapters/_a2a_snapshot.py +622 -0
- ksadk/events/adapters/_a2a_support.py +275 -0
- ksadk/events/adapters/_codex_interactions.py +375 -0
- ksadk/events/adapters/_codex_items.py +801 -0
- ksadk/events/adapters/_codex_validators.py +311 -0
- ksadk/events/adapters/_langgraph_support.py +785 -0
- ksadk/events/adapters/a2a.py +841 -0
- ksadk/events/adapters/adk.py +503 -0
- ksadk/events/adapters/codex.py +717 -0
- ksadk/events/adapters/langgraph.py +745 -0
- ksadk/events/canonical.py +440 -0
- ksadk/events/canonical_replay.py +497 -0
- ksadk/events/canonical_store.py +468 -0
- ksadk/events/cold_recovery.py +257 -0
- ksadk/events/content.py +89 -0
- ksadk/events/identity.py +86 -0
- ksadk/events/pipeline.py +455 -0
- ksadk/events/projections.py +66 -0
- ksadk/events/reducer.py +661 -0
- ksadk/events/replay.py +3 -36
- ksadk/events/runtime_event.py +20 -256
- ksadk/events/session_event.py +324 -0
- ksadk/events/store.py +3 -316
- ksadk/events/v1_compat.py +41 -0
- ksadk/harness/__init__.py +3 -2
- ksadk/harness/app.py +114 -123
- ksadk/harness/config.py +1 -1
- ksadk/harness/runtime.py +467 -0
- ksadk/hermes_terminal.py +5 -0
- ksadk/identity/resolver.py +4 -3
- ksadk/interaction/__init__.py +28 -0
- ksadk/interaction/contracts.py +136 -0
- ksadk/interaction/ledger.py +189 -0
- ksadk/interaction/provider.py +122 -0
- ksadk/interaction/providers/__init__.py +52 -0
- ksadk/interaction/providers/adk.py +40 -0
- ksadk/interaction/providers/codex.py +94 -0
- ksadk/interaction/providers/langgraph.py +58 -0
- ksadk/kernel/__init__.py +117 -0
- ksadk/kernel/authorization.py +258 -0
- ksadk/kernel/bootstrap.py +1074 -0
- ksadk/kernel/contract_fingerprints.py +61 -0
- ksadk/kernel/contracts.py +362 -0
- ksadk/kernel/control.py +219 -0
- ksadk/kernel/errors.py +79 -0
- ksadk/kernel/ingress.py +1102 -0
- ksadk/kernel/mapping.py +111 -0
- ksadk/kernel/memory_store.py +1133 -0
- ksadk/kernel/postgres_store.py +1756 -0
- ksadk/kernel/recovery.py +452 -0
- ksadk/kernel/runtime_identity.py +103 -0
- ksadk/kernel/sql/001_agent_kernel.sql +120 -0
- ksadk/kernel/sqlite_store.py +1410 -0
- ksadk/kernel/state.py +145 -0
- ksadk/kernel/store.py +232 -0
- ksadk/kernel/worker.py +953 -0
- ksadk/knowledge_base/client.py +22 -4
- ksadk/knowledge_base/service.py +32 -5
- ksadk/managed_a2a_card.py +160 -0
- ksadk/managed_runtime.py +16 -9
- ksadk/memory/__init__.py +61 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
- ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
- ksadk/memory/adk/backends/sdk_ltm_backend.py +394 -5
- ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
- ksadk/memory/adk/long_term_memory.py +3 -2
- ksadk/memory/coordinator.py +461 -0
- ksadk/memory/events.py +202 -0
- ksadk/memory/extraction.py +228 -0
- ksadk/memory/ltm_backend_factory.py +16 -3
- ksadk/memory/models.py +316 -0
- ksadk/memory/policy.py +215 -0
- ksadk/memory/provider.py +47 -0
- ksadk/memory/provider_adapter.py +159 -0
- ksadk/memory/provider_resolver.py +71 -0
- ksadk/memory/providers/__init__.py +5 -0
- ksadk/memory/providers/local_sqlite.py +490 -0
- ksadk/memory/resolved_policy.py +145 -0
- ksadk/memory/service.py +198 -8
- ksadk/memory/tool.py +4 -0
- ksadk/model_proxy/bootstrap.py +16 -9
- ksadk/model_proxy/config.py +22 -1
- ksadk/model_proxy/detect.py +202 -9
- ksadk/model_proxy/namespace.py +67 -15
- ksadk/model_proxy/server.py +168 -6
- ksadk/model_proxy/transform.py +105 -3
- ksadk/observability/__init__.py +25 -0
- ksadk/observability/session_log.py +420 -0
- ksadk/observability/trajectory.py +105 -0
- ksadk/prompts/__init__.py +71 -0
- ksadk/prompts/compiler.py +215 -0
- ksadk/prompts/models.py +82 -0
- ksadk/prompts/projection.py +89 -0
- ksadk/prompts/resolved.py +137 -0
- ksadk/prompts/sources.py +233 -0
- ksadk/runners/_langgraph_runner_streams.py +822 -0
- ksadk/runners/adk_runner.py +14 -31
- ksadk/runners/base_runner.py +44 -3
- ksadk/runners/factory.py +3 -3
- ksadk/runners/langgraph_runner.py +252 -433
- ksadk/runners/utils/__init__.py +0 -10
- ksadk/runners/utils/loader.py +16 -0
- ksadk/runtime/__init__.py +67 -41
- ksadk/runtime/_runner_adapter/__init__.py +1 -0
- ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
- ksadk/runtime/adapter.py +199 -16
- ksadk/runtime/conversation_execution.py +687 -0
- ksadk/runtime/executor.py +392 -0
- ksadk/runtime/factory.py +342 -0
- ksadk/runtime/framework_adapters.py +6 -12
- ksadk/runtime/hosted_finalizer.py +268 -0
- ksadk/runtime/launch.py +61 -0
- ksadk/runtime/preprocessing.py +49 -14
- ksadk/runtime/runner_adapter.py +330 -391
- ksadk/runtime/runner_loading.py +36 -0
- ksadk/runtime/usage.py +33 -0
- ksadk/sandbox/backends/e2b.py +36 -3
- ksadk/server/__init__.py +39 -4
- ksadk/server/app.py +18 -492
- ksadk/server/composition.py +36 -2
- ksadk/server/factory.py +210 -149
- ksadk/server/routes/common.py +15 -46
- ksadk/server/routes/control.py +13 -9
- ksadk/server/routes/kernel_ingress.py +258 -0
- ksadk/server/routes/misc.py +10 -33
- ksadk/server/routes/models.py +14 -6
- ksadk/server/routes/openai_compat.py +129 -27
- ksadk/server/routes/projection.py +84 -16
- ksadk/server/routes/run.py +347 -460
- ksadk/server/routes/sessions.py +37 -40
- ksadk/server/routes/streaming.py +52 -7
- ksadk/server/routes/workspace.py +54 -21
- ksadk/server/static/assets/{ArtifactsPanel-Cjnqt-8W.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BqyqZfuB.js → CodeBlock-DOH6MVcn.js} +4 -4
- ksadk/server/static/assets/{MathMessageMarkdown-BpH7jiFM.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
- ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
- ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
- ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
- ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
- ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
- ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
- ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
- ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
- ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
- ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
- ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
- ksadk/server/static/assets/{chunk-FMBD7UC4-BGhvzDYt.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-D98vPd-E.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
- ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
- ksadk/server/static/assets/{chunk-4BX2VUAB-CsWIQRhc.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
- ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
- ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
- ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
- ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
- ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
- ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-CX3yotHq.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
- ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
- ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
- ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
- ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
- ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
- ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
- ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
- ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
- ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
- ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
- ksadk/server/static/assets/{dist-D82mtD2s.js → dist-8hqcx0sU.js} +1 -1
- ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
- ksadk/server/static/assets/{dist-DHn6nBzr.js → dist-BE7bu-DL.js} +1 -1
- ksadk/server/static/assets/{dist-eoW6tZXA.js → dist-BVDffZ0U.js} +1 -1
- ksadk/server/static/assets/{dist-C2xjE7cO.js → dist-BX8z72IC.js} +1 -1
- ksadk/server/static/assets/{dist-CgF3fj15.js → dist-Bf2M8m3N.js} +1 -1
- ksadk/server/static/assets/{dist-Bs-jQcRX.js → dist-BjU6y-A2.js} +1 -1
- ksadk/server/static/assets/{dist-Djgl2wVZ.js → dist-BytlxIDT.js} +1 -1
- ksadk/server/static/assets/{dist-CVQRZjPu.js → dist-CSh_DE14.js} +1 -1
- ksadk/server/static/assets/{dist-6CVKuTAe.js → dist-CXYYBw3_.js} +1 -1
- ksadk/server/static/assets/{dist-DlzCRy0S.js → dist-C_wsv-Qd.js} +1 -1
- ksadk/server/static/assets/{dist-DF06ORQo.js → dist-ClxGviKw.js} +1 -1
- ksadk/server/static/assets/{dist-Cf23NY4L.js → dist-CttYUqzS.js} +1 -1
- ksadk/server/static/assets/{dist-ivOya1LD.js → dist-DNp6rLRA.js} +1 -1
- ksadk/server/static/assets/{dist-BibcG1V9.js → dist-DUX-id_F.js} +1 -1
- ksadk/server/static/assets/{dist-BUHRxvDG.js → dist-DmldrHd_.js} +1 -1
- ksadk/server/static/assets/{dist-BZ9n9AjO.js → dist-DnfXs8Vn.js} +2 -2
- ksadk/server/static/assets/{dist-DqNnNFrw.js → dist-Dq9gLD7L.js} +1 -1
- ksadk/server/static/assets/{dist-CGcp2ex8.js → dist-W-TIVntx.js} +1 -1
- ksadk/server/static/assets/{dist-Cwt7koer.js → dist-dDdADKFA.js} +1 -1
- ksadk/server/static/assets/{dist-C7-3jInC.js → dist-dydu68Fl.js} +1 -1
- ksadk/server/static/assets/{dist-C99THtIA.js → dist-ngtN0DJB.js} +1 -1
- ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
- ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
- ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
- ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
- ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
- ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
- ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
- ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
- ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
- ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
- ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-DLOHGvWw.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-BIzKeejT.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-BWHoFSw2.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
- ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
- ksadk/server/static/assets/{linear-CvMBw9cA.js → linear-BHjG8b-J.js} +1 -1
- ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-CSAQxEAe.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
- ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
- ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
- ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
- ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-CxIR4JXI.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
- ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
- ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
- ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
- ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
- ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
- ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
- ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-DpGa6EAV.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-NTKNGGe5.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
- ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
- ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
- ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
- ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
- ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-jqoYs3jQ.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
- ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
- ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
- ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
- ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
- ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
- ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
- ksadk/server/static/index.html +2 -2
- ksadk/sessions/__init__.py +26 -9
- ksadk/sessions/_local_service_sync.py +688 -0
- ksadk/sessions/_local_tables.py +21 -0
- ksadk/sessions/_postgres_schema.py +305 -0
- ksadk/sessions/_postgres_tables.py +15 -0
- ksadk/sessions/base.py +80 -1
- ksadk/sessions/in_memory.py +53 -2
- ksadk/sessions/local_service.py +63 -604
- ksadk/sessions/postgres_service.py +58 -148
- ksadk/sessions/resilient.py +35 -1
- ksadk/skills/runtime/backends/e2b.py +1 -1
- ksadk/skills/service_client.py +13 -3
- ksadk/studio/__init__.py +5 -0
- ksadk/studio/agent_avatar_assets.py +120 -0
- ksadk/studio/agent_lifecycle.py +51 -0
- ksadk/studio/api.py +1598 -0
- ksadk/studio/api_catalog_routes.py +324 -0
- ksadk/studio/api_contracts.py +286 -0
- ksadk/studio/api_helpers.py +132 -0
- ksadk/studio/api_memory_routes.py +82 -0
- ksadk/studio/authoring.py +749 -0
- ksadk/studio/authoring_coordinator.py +1017 -0
- ksadk/studio/builder.py +397 -0
- ksadk/studio/capabilities.py +368 -0
- ksadk/studio/cloud.py +2099 -0
- ksadk/studio/codex_agent_service.py +694 -0
- ksadk/studio/codex_authoring.py +469 -0
- ksadk/studio/codex_builder.py +437 -0
- ksadk/studio/codex_manifest.py +307 -0
- ksadk/studio/codex_run.py +395 -0
- ksadk/studio/compiler.py +190 -0
- ksadk/studio/contracts.py +755 -0
- ksadk/studio/errors.py +47 -0
- ksadk/studio/event_store.py +214 -0
- ksadk/studio/framework_run.py +342 -0
- ksadk/studio/hosted_kernel.py +317 -0
- ksadk/studio/identifiers.py +33 -0
- ksadk/studio/manifest_resolver.py +131 -0
- ksadk/studio/mcp_runtime.py +229 -0
- ksadk/studio/model_client.py +629 -0
- ksadk/studio/model_profile_service.py +214 -0
- ksadk/studio/operations.py +275 -0
- ksadk/studio/otel_trace.py +1060 -0
- ksadk/studio/pagination.py +182 -0
- ksadk/studio/pcm_memory.py +85 -0
- ksadk/studio/python_tool_inspection.py +183 -0
- ksadk/studio/repository.py +327 -0
- ksadk/studio/resource_catalog.py +1260 -0
- ksadk/studio/run_service.py +1133 -0
- ksadk/studio/runtime_catalog.py +74 -0
- ksadk/studio/runtime_source.py +365 -0
- ksadk/studio/service.py +2249 -0
- ksadk/studio/shared_web.py +1004 -0
- ksadk/studio/skill_discovery.py +628 -0
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
- ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
- ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
- ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
- ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- ksadk/studio/static/index.html +15 -0
- ksadk/studio/templates.py +722 -0
- ksadk/studio/validator.py +265 -0
- ksadk/studio/workspace.py +135 -0
- ksadk/tools/gateway.py +9 -0
- ksadk/toolsets/__init__.py +27 -1
- ksadk/toolsets/a2a.py +426 -0
- ksadk/tracing/__init__.py +19 -21
- ksadk/tracing/setup.py +317 -403
- ksadk/tracing/span_utils.py +0 -44
- ksadk/version.py +1 -1
- ksadk_runtime_common/memory_backend/registry.py +56 -56
- ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
- ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
- ksadk_runtime_common/workspace_files/router.py +8 -2
- agentengine_sdk_python-0.8.0.dist-info/RECORD +0 -578
- ksadk/a2a/card_builder.py +0 -64
- ksadk/a2a/client.py +0 -309
- ksadk/events/parser.py +0 -191
- ksadk/harness/runner.py +0 -211
- ksadk/runners/codex_runner.py +0 -132
- ksadk/runners/utils/langfuse.py +0 -230
- ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +0 -1
- ksadk/server/static/assets/arc-CFKwSk_B.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +0 -1
- ksadk/server/static/assets/architectureDiagram-3BPJPVTR-Dej8jX8c.js +0 -36
- ksadk/server/static/assets/blockDiagram-GPEHLZMM-1F64jqvy.js +0 -132
- ksadk/server/static/assets/c4Diagram-AAUBKEIU-9q7AUAKb.js +0 -10
- ksadk/server/static/assets/channel-iUGv2N24.js +0 -1
- ksadk/server/static/assets/chunk-2J33WTMH-6qgwJm7Q.js +0 -1
- ksadk/server/static/assets/chunk-55IACEB6-BK8paA20.js +0 -1
- ksadk/server/static/assets/chunk-727SXJPM-B-JEhy7h.js +0 -206
- ksadk/server/static/assets/chunk-AQP2D5EJ-DYBvMHAa.js +0 -231
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +0 -1
- ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
- ksadk/server/static/assets/dagre-BM42HDAG-C7TguAWo.js +0 -4
- ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
- ksadk/server/static/assets/diagram-2AECGRRQ-Dd17_kws.js +0 -43
- ksadk/server/static/assets/diagram-5GNKFQAL-DVIClIil.js +0 -10
- ksadk/server/static/assets/diagram-KO2AKTUF-Df_3gYQa.js +0 -3
- ksadk/server/static/assets/diagram-LMA3HP47-BhLk6ZtA.js +0 -24
- ksadk/server/static/assets/diagram-OG6HWLK6-D4GNGvuu.js +0 -24
- ksadk/server/static/assets/dist-DyvYLMW9.js +0 -1
- ksadk/server/static/assets/erDiagram-TEJ5UH35-CnfRcY2M.js +0 -85
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +0 -1
- ksadk/server/static/assets/flowDiagram-I6XJVG4X-DGaJjGNJ.js +0 -162
- ksadk/server/static/assets/ganttDiagram-6RSMTGT7-DpCoB2g9.js +0 -292
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +0 -1
- ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-9IKVFBVT.js +0 -106
- ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +0 -177
- ksadk/server/static/assets/index-jOPmTOZH.css +0 -2
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +0 -1
- ksadk/server/static/assets/infoDiagram-5YYISTIA-CYIIsb8y.js +0 -2
- ksadk/server/static/assets/katex-DATssjp1.js +0 -265
- ksadk/server/static/assets/mermaid-parser.core-mGAcEwcq.js +0 -161
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +0 -1
- ksadk/server/static/assets/pieDiagram-4H26LBE5-By-ra07M.js +0 -30
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +0 -1
- ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DpW90iu3.js +0 -84
- ksadk/server/static/assets/stateDiagram-AJRCARHV-DGpd9yFG.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +0 -1
- ksadk/server/static/assets/vennDiagram-CIIHVFJN-l3Z7-wbP.js +0 -34
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +0 -1
- ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-BR54GPmr.js +0 -78
- ksadk/server/static/assets/xychartDiagram-2RQKCTM6-SkiH8GsK.js +0 -7
- ksadk/tracing/exporters/langfuse_exporter.py +0 -460
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.8.0.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
"""Safe OpenAI-compatible model client for local Studio runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import ipaddress
|
|
7
|
+
import os
|
|
8
|
+
import socket
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from threading import RLock
|
|
11
|
+
from typing import Any, Awaitable, Callable
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from ksadk.studio.contracts import NetworkPolicy, ResolvedModel, Usage
|
|
17
|
+
from ksadk.studio.errors import StudioError
|
|
18
|
+
|
|
19
|
+
_DENIED_METADATA_HOSTS = {
|
|
20
|
+
"169.254.169.254",
|
|
21
|
+
"metadata.google.internal",
|
|
22
|
+
"metadata.azure.internal",
|
|
23
|
+
"fd00:ec2::254",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#: 表示输出被 token 上限截断的 finishReason 集合(chat 与 Responses 两种 wire)。
|
|
27
|
+
_LENGTH_FINISH_REASONS = {"length", "incomplete", "max_output_tokens"}
|
|
28
|
+
#: length 截断重试时把 max_tokens 提到的目标值;未配置 max_tokens 的 profile
|
|
29
|
+
#: 首次截断重试也用该值兜底(authoring 输出完整 JSON 需要宽松上限)。
|
|
30
|
+
_LENGTH_RETRY_TARGET_TOKENS = 16384
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ToolCall:
|
|
35
|
+
id: str
|
|
36
|
+
name: str
|
|
37
|
+
arguments: str
|
|
38
|
+
raw: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class ModelResponse:
|
|
43
|
+
content: str
|
|
44
|
+
finish_reason: str
|
|
45
|
+
usage: Usage
|
|
46
|
+
tool_calls: list[ToolCall]
|
|
47
|
+
raw_message: dict[str, Any]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CredentialResolver:
|
|
51
|
+
"""Resolve immutable Secret references with a workspace-persisted overlay.
|
|
52
|
+
|
|
53
|
+
Resolution order: in-memory session values → workspace secrets file
|
|
54
|
+
(``.agentkit/secrets.env``) → process environment (with paired fallback).
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
_FALLBACK_PAIRS: tuple[tuple[str, str], ...] = (
|
|
58
|
+
("AGENTKIT_MODEL_API_KEY", "OPENAI_API_KEY"),
|
|
59
|
+
("OPENAI_API_KEY", "AGENTKIT_MODEL_API_KEY"),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def __init__(self, workspace: Any = None) -> None:
|
|
63
|
+
self._session_values: dict[str, bytearray] = {}
|
|
64
|
+
self._lock = RLock()
|
|
65
|
+
self._workspace = workspace
|
|
66
|
+
self._persisted: dict[str, str] | None = None
|
|
67
|
+
|
|
68
|
+
def _secrets_path(self) -> Any | None:
|
|
69
|
+
if self._workspace is None:
|
|
70
|
+
return None
|
|
71
|
+
try:
|
|
72
|
+
return self._workspace.resolve(".agentkit/secrets.env")
|
|
73
|
+
except Exception:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def _load_persisted(self) -> dict[str, str]:
|
|
77
|
+
if self._persisted is not None:
|
|
78
|
+
return self._persisted
|
|
79
|
+
self._persisted = {}
|
|
80
|
+
path = self._secrets_path()
|
|
81
|
+
if path is not None and path.is_file():
|
|
82
|
+
try:
|
|
83
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
84
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
85
|
+
continue
|
|
86
|
+
key, _, value = line.partition("=")
|
|
87
|
+
key = key.strip()
|
|
88
|
+
if key:
|
|
89
|
+
self._persisted[key] = value
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
return self._persisted
|
|
93
|
+
|
|
94
|
+
def _write_persisted(self) -> None:
|
|
95
|
+
path = self._secrets_path()
|
|
96
|
+
if path is None:
|
|
97
|
+
return
|
|
98
|
+
lines = [f"{k}={v}" for k, v in sorted(self._persisted.items())]
|
|
99
|
+
try:
|
|
100
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
path.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
|
|
102
|
+
try:
|
|
103
|
+
path.chmod(0o600)
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _validate_name(name: str) -> str:
|
|
111
|
+
allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
|
|
112
|
+
if not name or any(char not in allowed for char in name):
|
|
113
|
+
raise StudioError(
|
|
114
|
+
"SECRET_REFERENCE_INVALID",
|
|
115
|
+
"环境变量 Secret 引用格式无效",
|
|
116
|
+
status_code=422,
|
|
117
|
+
field="credentialName",
|
|
118
|
+
)
|
|
119
|
+
return name
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def _environment_name(cls, reference: str) -> str:
|
|
123
|
+
if not reference.startswith("env://"):
|
|
124
|
+
raise StudioError(
|
|
125
|
+
"SECRET_BACKEND_UNAVAILABLE",
|
|
126
|
+
"当前本地 Runtime 仅支持 env:// Secret 引用",
|
|
127
|
+
status_code=501,
|
|
128
|
+
details={"scheme": reference.partition("://")[0]},
|
|
129
|
+
)
|
|
130
|
+
return cls._validate_name(reference.removeprefix("env://"))
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def _zero(value: bytearray | None) -> None:
|
|
134
|
+
if value is not None:
|
|
135
|
+
value[:] = b"\x00" * len(value)
|
|
136
|
+
|
|
137
|
+
def put_session(self, name: str, value: str) -> dict[str, str | bool]:
|
|
138
|
+
name = self._validate_name(name)
|
|
139
|
+
if (
|
|
140
|
+
not value
|
|
141
|
+
or len(value) > 16_384
|
|
142
|
+
or value != value.strip()
|
|
143
|
+
or any(ord(char) < 32 or ord(char) == 127 for char in value)
|
|
144
|
+
):
|
|
145
|
+
raise StudioError(
|
|
146
|
+
"SECRET_VALUE_INVALID",
|
|
147
|
+
"凭证不能为空、包含控制字符或超过 16 KiB",
|
|
148
|
+
status_code=422,
|
|
149
|
+
field="value",
|
|
150
|
+
)
|
|
151
|
+
encoded = bytearray(value.encode("utf-8"))
|
|
152
|
+
with self._lock:
|
|
153
|
+
previous = self._session_values.get(name)
|
|
154
|
+
self._session_values[name] = encoded
|
|
155
|
+
self._zero(previous)
|
|
156
|
+
if self._secrets_path() is not None:
|
|
157
|
+
persisted = self._load_persisted()
|
|
158
|
+
if persisted.get(name) != value:
|
|
159
|
+
persisted[name] = value
|
|
160
|
+
self._write_persisted()
|
|
161
|
+
return self.status(f"env://{name}")
|
|
162
|
+
|
|
163
|
+
def delete_session(self, name: str) -> dict[str, str | bool]:
|
|
164
|
+
name = self._validate_name(name)
|
|
165
|
+
with self._lock:
|
|
166
|
+
previous = self._session_values.pop(name, None)
|
|
167
|
+
self._zero(previous)
|
|
168
|
+
if self._secrets_path() is not None:
|
|
169
|
+
persisted = self._load_persisted()
|
|
170
|
+
if name in persisted:
|
|
171
|
+
persisted.pop(name, None)
|
|
172
|
+
self._write_persisted()
|
|
173
|
+
return self.status(f"env://{name}")
|
|
174
|
+
|
|
175
|
+
def clear_session(self) -> None:
|
|
176
|
+
with self._lock:
|
|
177
|
+
values = list(self._session_values.values())
|
|
178
|
+
self._session_values.clear()
|
|
179
|
+
for value in values:
|
|
180
|
+
self._zero(value)
|
|
181
|
+
|
|
182
|
+
def _fallback_name(self, name: str) -> str | None:
|
|
183
|
+
for primary, alternate in self._FALLBACK_PAIRS:
|
|
184
|
+
if name == primary and alternate != primary:
|
|
185
|
+
return alternate
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
def _resolve_source(self, name: str) -> tuple[bool, str]:
|
|
189
|
+
"""Return (configured, source) considering session, persisted file, env, and fallback."""
|
|
190
|
+
fallback = self._fallback_name(name)
|
|
191
|
+
with self._lock:
|
|
192
|
+
if name in self._session_values:
|
|
193
|
+
return True, "session"
|
|
194
|
+
if fallback and fallback in self._session_values:
|
|
195
|
+
return True, "session-alias"
|
|
196
|
+
persisted = self._load_persisted()
|
|
197
|
+
if name in persisted:
|
|
198
|
+
return True, "workspace"
|
|
199
|
+
if fallback and fallback in persisted:
|
|
200
|
+
return True, "workspace-alias"
|
|
201
|
+
if os.environ.get(name):
|
|
202
|
+
return True, "environment"
|
|
203
|
+
if fallback and os.environ.get(fallback):
|
|
204
|
+
return True, "fallback"
|
|
205
|
+
return False, "missing"
|
|
206
|
+
|
|
207
|
+
def _session_value(self, name: str) -> str | None:
|
|
208
|
+
with self._lock:
|
|
209
|
+
value = self._session_values.get(name)
|
|
210
|
+
return value.decode("utf-8") if value is not None else None
|
|
211
|
+
|
|
212
|
+
def status(self, reference: str) -> dict[str, str | bool]:
|
|
213
|
+
name = self._environment_name(reference)
|
|
214
|
+
configured, source = self._resolve_source(name)
|
|
215
|
+
return {
|
|
216
|
+
"reference": reference,
|
|
217
|
+
"name": name,
|
|
218
|
+
"configured": configured,
|
|
219
|
+
"source": source,
|
|
220
|
+
"persistence": source,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
def resolve(self, reference: str) -> str:
|
|
224
|
+
name = self._environment_name(reference)
|
|
225
|
+
fallback = self._fallback_name(name)
|
|
226
|
+
aliases = [name, *([fallback] if fallback else [])]
|
|
227
|
+
for candidate in aliases:
|
|
228
|
+
value = self._session_value(candidate)
|
|
229
|
+
if value is not None:
|
|
230
|
+
return value
|
|
231
|
+
persisted = self._load_persisted()
|
|
232
|
+
for candidate in aliases:
|
|
233
|
+
if candidate in persisted:
|
|
234
|
+
return persisted[candidate]
|
|
235
|
+
for candidate in aliases:
|
|
236
|
+
value = os.environ.get(candidate)
|
|
237
|
+
if value:
|
|
238
|
+
return value
|
|
239
|
+
raise StudioError(
|
|
240
|
+
"SECRET_NOT_FOUND",
|
|
241
|
+
"模型凭证尚未配置",
|
|
242
|
+
status_code=422,
|
|
243
|
+
details={"reference": reference},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def exists(self, reference: str) -> bool:
|
|
247
|
+
try:
|
|
248
|
+
return bool(self.status(reference)["configured"])
|
|
249
|
+
except StudioError:
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class NetworkGuard:
|
|
254
|
+
async def check(self, endpoint_url: str, policy: NetworkPolicy) -> None:
|
|
255
|
+
parsed = urlparse(endpoint_url)
|
|
256
|
+
host = (parsed.hostname or "").lower().rstrip(".")
|
|
257
|
+
if parsed.scheme not in {"http", "https"} or not host:
|
|
258
|
+
raise StudioError(
|
|
259
|
+
"MODEL_ENDPOINT_INVALID",
|
|
260
|
+
"模型地址必须是有效的 HTTP(S) URL",
|
|
261
|
+
status_code=422,
|
|
262
|
+
)
|
|
263
|
+
allowed = {value.lower().rstrip(".") for value in policy.allowed_hosts}
|
|
264
|
+
if policy.mode == "restricted" and host not in allowed:
|
|
265
|
+
raise StudioError(
|
|
266
|
+
"NETWORK_TARGET_DENIED",
|
|
267
|
+
"目标 hostname 不在网络允许清单中",
|
|
268
|
+
status_code=403,
|
|
269
|
+
details={"host": host},
|
|
270
|
+
)
|
|
271
|
+
if host in _DENIED_METADATA_HOSTS:
|
|
272
|
+
raise self._denied(host)
|
|
273
|
+
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
|
274
|
+
try:
|
|
275
|
+
records = await asyncio.to_thread(
|
|
276
|
+
socket.getaddrinfo,
|
|
277
|
+
host,
|
|
278
|
+
port,
|
|
279
|
+
0,
|
|
280
|
+
socket.SOCK_STREAM,
|
|
281
|
+
)
|
|
282
|
+
except socket.gaierror as exc:
|
|
283
|
+
raise StudioError(
|
|
284
|
+
"NETWORK_TARGET_UNRESOLVED",
|
|
285
|
+
"模型 hostname 无法解析",
|
|
286
|
+
status_code=422,
|
|
287
|
+
details={"host": host},
|
|
288
|
+
) from exc
|
|
289
|
+
for record in records:
|
|
290
|
+
address = str(record[4][0]).split("%", 1)[0]
|
|
291
|
+
ip = ipaddress.ip_address(address)
|
|
292
|
+
if str(ip) in _DENIED_METADATA_HOSTS or ip.is_link_local or ip.is_multicast:
|
|
293
|
+
raise self._denied(host)
|
|
294
|
+
if (
|
|
295
|
+
ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_unspecified
|
|
296
|
+
) and not policy.allow_private_network:
|
|
297
|
+
raise self._denied(host)
|
|
298
|
+
|
|
299
|
+
@staticmethod
|
|
300
|
+
def _denied(host: str) -> StudioError:
|
|
301
|
+
return StudioError(
|
|
302
|
+
"NETWORK_TARGET_DENIED",
|
|
303
|
+
"目标地址不满足本地 Runtime 网络策略",
|
|
304
|
+
status_code=403,
|
|
305
|
+
details={"host": host},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class OpenAICompatibleModelClient:
|
|
310
|
+
def __init__(
|
|
311
|
+
self,
|
|
312
|
+
*,
|
|
313
|
+
credential_resolver: CredentialResolver | None = None,
|
|
314
|
+
network_guard: NetworkGuard | None = None,
|
|
315
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
316
|
+
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
|
|
317
|
+
) -> None:
|
|
318
|
+
self.credential_resolver = credential_resolver or CredentialResolver()
|
|
319
|
+
self.network_guard = network_guard or NetworkGuard()
|
|
320
|
+
self.transport = transport
|
|
321
|
+
self.sleep = sleep
|
|
322
|
+
|
|
323
|
+
async def complete(
|
|
324
|
+
self,
|
|
325
|
+
model: ResolvedModel,
|
|
326
|
+
*,
|
|
327
|
+
messages: list[dict[str, Any]],
|
|
328
|
+
network_policy: NetworkPolicy,
|
|
329
|
+
timeout_seconds: int,
|
|
330
|
+
max_attempts: int,
|
|
331
|
+
backoff_seconds: float,
|
|
332
|
+
tools: list[dict[str, Any]] | None = None,
|
|
333
|
+
allow_empty: bool = False,
|
|
334
|
+
response_format: dict[str, Any] | None = None,
|
|
335
|
+
retry_on_length: bool = False,
|
|
336
|
+
) -> ModelResponse:
|
|
337
|
+
await self.network_guard.check(model.endpoint_url, network_policy)
|
|
338
|
+
credential = self.credential_resolver.resolve(model.credential_ref)
|
|
339
|
+
wire_api = (model.wire_api or "chat").strip().lower()
|
|
340
|
+
payload: dict[str, Any]
|
|
341
|
+
if wire_api == "responses":
|
|
342
|
+
payload = self._responses_payload(model, messages, tools)
|
|
343
|
+
else:
|
|
344
|
+
payload = {
|
|
345
|
+
"model": model.model,
|
|
346
|
+
"messages": messages,
|
|
347
|
+
"stream": False,
|
|
348
|
+
}
|
|
349
|
+
# 未显式配置的采样参数一律不携带字段,交给服务端默认,
|
|
350
|
+
# 避免触碰各模型族的硬约束(kimi 温度、各家 max_tokens 上限等)。
|
|
351
|
+
if model.parameters.temperature is not None:
|
|
352
|
+
payload["temperature"] = model.parameters.temperature
|
|
353
|
+
if model.parameters.max_tokens is not None:
|
|
354
|
+
payload["max_tokens"] = model.parameters.max_tokens
|
|
355
|
+
if model.parameters.top_p is not None:
|
|
356
|
+
payload["top_p"] = model.parameters.top_p
|
|
357
|
+
if tools:
|
|
358
|
+
payload["tools"] = tools
|
|
359
|
+
payload["tool_choice"] = "auto"
|
|
360
|
+
if response_format and model.parameters.allow_json_response_format:
|
|
361
|
+
payload["response_format"] = response_format
|
|
362
|
+
headers = {
|
|
363
|
+
"Authorization": f"Bearer {credential}",
|
|
364
|
+
"Content-Type": "application/json",
|
|
365
|
+
}
|
|
366
|
+
timeout = httpx.Timeout(timeout_seconds, connect=min(10, timeout_seconds))
|
|
367
|
+
dropped_response_format = False
|
|
368
|
+
length_retried = False
|
|
369
|
+
last_length_error: StudioError | None = None
|
|
370
|
+
async with httpx.AsyncClient(
|
|
371
|
+
transport=self.transport,
|
|
372
|
+
timeout=timeout,
|
|
373
|
+
follow_redirects=False,
|
|
374
|
+
) as client:
|
|
375
|
+
# 额外 1 次迭代仅用于 response_format 400 降级重发,
|
|
376
|
+
# 其余失败路径仍受 max_attempts 约束(会在原上限处 raise)。
|
|
377
|
+
for attempt in range(1, max_attempts + 2):
|
|
378
|
+
try:
|
|
379
|
+
response = await client.post(
|
|
380
|
+
model.endpoint_url,
|
|
381
|
+
headers=headers,
|
|
382
|
+
json=payload,
|
|
383
|
+
)
|
|
384
|
+
except (httpx.TimeoutException, httpx.TransportError) as exc:
|
|
385
|
+
if attempt >= max_attempts:
|
|
386
|
+
raise StudioError(
|
|
387
|
+
"MODEL_REQUEST_FAILED",
|
|
388
|
+
"模型请求网络失败",
|
|
389
|
+
status_code=502,
|
|
390
|
+
details={"attempts": attempt, "errorType": type(exc).__name__},
|
|
391
|
+
) from exc
|
|
392
|
+
await self.sleep(backoff_seconds * attempt)
|
|
393
|
+
continue
|
|
394
|
+
if response.is_redirect:
|
|
395
|
+
raise StudioError(
|
|
396
|
+
"NETWORK_TARGET_DENIED",
|
|
397
|
+
"模型 endpoint 不允许重定向",
|
|
398
|
+
status_code=403,
|
|
399
|
+
details={"statusCode": response.status_code},
|
|
400
|
+
)
|
|
401
|
+
if response.status_code == 429 or response.status_code >= 500:
|
|
402
|
+
if attempt < max_attempts:
|
|
403
|
+
await self.sleep(backoff_seconds * attempt)
|
|
404
|
+
continue
|
|
405
|
+
if (
|
|
406
|
+
response.status_code == 400
|
|
407
|
+
and "response_format" in payload
|
|
408
|
+
and not dropped_response_format
|
|
409
|
+
):
|
|
410
|
+
# 网关不支持 response_format:去掉该字段重发一次。
|
|
411
|
+
dropped_response_format = True
|
|
412
|
+
payload.pop("response_format")
|
|
413
|
+
continue
|
|
414
|
+
if response.status_code >= 400:
|
|
415
|
+
if response.status_code == 429:
|
|
416
|
+
# A selected authoring profile must not silently fall
|
|
417
|
+
# back to a different model. Preserve the actual
|
|
418
|
+
# upstream condition so Studio can offer the user a
|
|
419
|
+
# useful retry/switch decision instead of reporting a
|
|
420
|
+
# misleading generic 502.
|
|
421
|
+
raise StudioError(
|
|
422
|
+
"MODEL_RATE_LIMITED",
|
|
423
|
+
"所选生成模型当前限流,请稍后重试或切换模型 Profile",
|
|
424
|
+
status_code=429,
|
|
425
|
+
details={"upstreamStatus": response.status_code},
|
|
426
|
+
)
|
|
427
|
+
upstream_detail = ""
|
|
428
|
+
try:
|
|
429
|
+
upstream_detail = response.text[:200]
|
|
430
|
+
except Exception: # noqa: BLE001 - 诊断信息尽力而为
|
|
431
|
+
upstream_detail = ""
|
|
432
|
+
raise StudioError(
|
|
433
|
+
"MODEL_REQUEST_FAILED",
|
|
434
|
+
"模型服务返回错误",
|
|
435
|
+
status_code=502,
|
|
436
|
+
details={
|
|
437
|
+
"upstreamStatus": response.status_code,
|
|
438
|
+
"upstreamError": upstream_detail,
|
|
439
|
+
},
|
|
440
|
+
)
|
|
441
|
+
try:
|
|
442
|
+
if wire_api == "responses":
|
|
443
|
+
parsed = self._parse_responses_response(response, allow_empty=allow_empty)
|
|
444
|
+
else:
|
|
445
|
+
parsed = self._parse_response(response, allow_empty=allow_empty)
|
|
446
|
+
except StudioError as exc:
|
|
447
|
+
# finishReason=length 的空响应(大 JSON 被 max_tokens 截断):
|
|
448
|
+
# 一次性扩容 max_tokens 重发;再次截断则按原错误上抛。
|
|
449
|
+
if retry_on_length and not length_retried and self._is_length_truncation(exc):
|
|
450
|
+
length_retried = True
|
|
451
|
+
last_length_error = exc
|
|
452
|
+
payload = self._expand_length_budget(payload)
|
|
453
|
+
continue
|
|
454
|
+
raise
|
|
455
|
+
if (
|
|
456
|
+
retry_on_length
|
|
457
|
+
and not length_retried
|
|
458
|
+
and parsed.finish_reason in _LENGTH_FINISH_REASONS
|
|
459
|
+
):
|
|
460
|
+
# 有内容但被 length 截断(结构化输出必然残缺),同样扩容重发一次。
|
|
461
|
+
length_retried = True
|
|
462
|
+
payload = self._expand_length_budget(payload)
|
|
463
|
+
continue
|
|
464
|
+
return parsed
|
|
465
|
+
if last_length_error is not None:
|
|
466
|
+
raise last_length_error
|
|
467
|
+
raise AssertionError("unreachable")
|
|
468
|
+
|
|
469
|
+
@staticmethod
|
|
470
|
+
def _is_length_truncation(exc: StudioError) -> bool:
|
|
471
|
+
if exc.code != "MODEL_EMPTY_RESPONSE":
|
|
472
|
+
return False
|
|
473
|
+
return str(exc.details.get("finishReason") or "") in _LENGTH_FINISH_REASONS
|
|
474
|
+
|
|
475
|
+
@staticmethod
|
|
476
|
+
def _expand_length_budget(payload: dict[str, Any]) -> dict[str, Any]:
|
|
477
|
+
key = "max_output_tokens" if "max_output_tokens" in payload else "max_tokens"
|
|
478
|
+
current = int(payload.get(key) or 0)
|
|
479
|
+
payload[key] = max(current, _LENGTH_RETRY_TARGET_TOKENS)
|
|
480
|
+
return payload
|
|
481
|
+
|
|
482
|
+
@staticmethod
|
|
483
|
+
def _responses_payload(
|
|
484
|
+
model: ResolvedModel,
|
|
485
|
+
messages: list[dict[str, Any]],
|
|
486
|
+
tools: list[dict[str, Any]] | None,
|
|
487
|
+
) -> dict[str, Any]:
|
|
488
|
+
"""把 chat 语义消息映射到 Responses API 最小可用载荷。"""
|
|
489
|
+
if tools:
|
|
490
|
+
raise StudioError(
|
|
491
|
+
"MODEL_REQUEST_FAILED",
|
|
492
|
+
"Responses 端点暂不支持 tools 参数",
|
|
493
|
+
status_code=422,
|
|
494
|
+
)
|
|
495
|
+
instructions: list[str] = []
|
|
496
|
+
items: list[dict[str, Any]] = []
|
|
497
|
+
for message in messages:
|
|
498
|
+
role = str(message.get("role") or "user")
|
|
499
|
+
text = str(message.get("content") or "")
|
|
500
|
+
if role == "system":
|
|
501
|
+
instructions.append(text)
|
|
502
|
+
continue
|
|
503
|
+
part_type = "output_text" if role == "assistant" else "input_text"
|
|
504
|
+
items.append({"role": role, "content": [{"type": part_type, "text": text}]})
|
|
505
|
+
payload: dict[str, Any] = {
|
|
506
|
+
"model": model.model,
|
|
507
|
+
"input": items,
|
|
508
|
+
}
|
|
509
|
+
if model.parameters.max_tokens is not None:
|
|
510
|
+
payload["max_output_tokens"] = model.parameters.max_tokens
|
|
511
|
+
if instructions:
|
|
512
|
+
payload["instructions"] = "\n\n".join(instructions)
|
|
513
|
+
return payload
|
|
514
|
+
|
|
515
|
+
@staticmethod
|
|
516
|
+
def _parse_responses_response(
|
|
517
|
+
response: httpx.Response, allow_empty: bool = False
|
|
518
|
+
) -> ModelResponse:
|
|
519
|
+
if len(response.content) > 16 * 1024 * 1024:
|
|
520
|
+
raise StudioError(
|
|
521
|
+
"MODEL_RESPONSE_TOO_LARGE",
|
|
522
|
+
"模型响应超过 16 MiB 限制",
|
|
523
|
+
status_code=502,
|
|
524
|
+
)
|
|
525
|
+
try:
|
|
526
|
+
payload = response.json()
|
|
527
|
+
except ValueError as exc:
|
|
528
|
+
raise StudioError(
|
|
529
|
+
"MODEL_RESPONSE_INVALID",
|
|
530
|
+
"模型响应不符合 Responses 协议",
|
|
531
|
+
status_code=502,
|
|
532
|
+
) from exc
|
|
533
|
+
content = payload.get("output_text")
|
|
534
|
+
if not isinstance(content, str) or not content:
|
|
535
|
+
parts: list[str] = []
|
|
536
|
+
for item in payload.get("output") or []:
|
|
537
|
+
if not isinstance(item, dict):
|
|
538
|
+
continue
|
|
539
|
+
for part in item.get("content") or []:
|
|
540
|
+
if isinstance(part, dict) and part.get("type") in {"output_text", "text"}:
|
|
541
|
+
parts.append(str(part.get("text") or ""))
|
|
542
|
+
content = "".join(parts)
|
|
543
|
+
status = str(payload.get("status") or "")
|
|
544
|
+
if not content and not allow_empty:
|
|
545
|
+
raise StudioError(
|
|
546
|
+
"MODEL_EMPTY_RESPONSE",
|
|
547
|
+
"模型未返回可用内容",
|
|
548
|
+
status_code=502,
|
|
549
|
+
details={"status": status, "finishReason": status},
|
|
550
|
+
)
|
|
551
|
+
raw_usage = payload.get("usage") or {}
|
|
552
|
+
usage = Usage(
|
|
553
|
+
input_tokens=int(raw_usage.get("input_tokens") or 0),
|
|
554
|
+
output_tokens=int(raw_usage.get("output_tokens") or 0),
|
|
555
|
+
total_tokens=int(raw_usage.get("total_tokens") or 0),
|
|
556
|
+
cached_input_tokens=int(
|
|
557
|
+
(raw_usage.get("input_tokens_details") or {}).get("cached_tokens") or 0
|
|
558
|
+
),
|
|
559
|
+
reasoning_output_tokens=int(
|
|
560
|
+
(raw_usage.get("output_tokens_details") or {}).get("reasoning_tokens") or 0
|
|
561
|
+
),
|
|
562
|
+
)
|
|
563
|
+
return ModelResponse(
|
|
564
|
+
content=content,
|
|
565
|
+
finish_reason="stop" if status == "completed" else status,
|
|
566
|
+
usage=usage,
|
|
567
|
+
tool_calls=[],
|
|
568
|
+
raw_message={"output": payload.get("output")},
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
@staticmethod
|
|
572
|
+
def _parse_response(response: httpx.Response, allow_empty: bool = False) -> ModelResponse:
|
|
573
|
+
if len(response.content) > 16 * 1024 * 1024:
|
|
574
|
+
raise StudioError(
|
|
575
|
+
"MODEL_RESPONSE_TOO_LARGE",
|
|
576
|
+
"模型响应超过 16 MiB 限制",
|
|
577
|
+
status_code=502,
|
|
578
|
+
)
|
|
579
|
+
try:
|
|
580
|
+
payload = response.json()
|
|
581
|
+
choice = payload["choices"][0]
|
|
582
|
+
message = choice["message"]
|
|
583
|
+
except (ValueError, KeyError, IndexError, TypeError) as exc:
|
|
584
|
+
raise StudioError(
|
|
585
|
+
"MODEL_RESPONSE_INVALID",
|
|
586
|
+
"模型响应不符合 OpenAI-compatible 协议",
|
|
587
|
+
status_code=502,
|
|
588
|
+
) from exc
|
|
589
|
+
calls = []
|
|
590
|
+
for raw in message.get("tool_calls") or []:
|
|
591
|
+
function = raw.get("function") or {}
|
|
592
|
+
calls.append(
|
|
593
|
+
ToolCall(
|
|
594
|
+
id=str(raw.get("id") or ""),
|
|
595
|
+
name=str(function.get("name") or ""),
|
|
596
|
+
arguments=str(function.get("arguments") or "{}"),
|
|
597
|
+
raw=raw,
|
|
598
|
+
)
|
|
599
|
+
)
|
|
600
|
+
content = str(message.get("content") or "")
|
|
601
|
+
finish_reason = str(choice.get("finish_reason") or "")
|
|
602
|
+
if not content and not calls and not allow_empty:
|
|
603
|
+
raise StudioError(
|
|
604
|
+
"MODEL_EMPTY_RESPONSE",
|
|
605
|
+
"模型未返回可用内容",
|
|
606
|
+
status_code=502,
|
|
607
|
+
details={"finishReason": finish_reason},
|
|
608
|
+
)
|
|
609
|
+
raw_usage = payload.get("usage") or {}
|
|
610
|
+
usage = Usage(
|
|
611
|
+
input_tokens=int(raw_usage.get("prompt_tokens") or 0),
|
|
612
|
+
output_tokens=int(raw_usage.get("completion_tokens") or 0),
|
|
613
|
+
total_tokens=int(raw_usage.get("total_tokens") or 0),
|
|
614
|
+
cached_input_tokens=int(
|
|
615
|
+
(raw_usage.get("prompt_tokens_details") or {}).get("cached_tokens") or 0
|
|
616
|
+
),
|
|
617
|
+
reasoning_output_tokens=int(
|
|
618
|
+
(raw_usage.get("completion_tokens_details") or {}).get("reasoning_tokens") or 0
|
|
619
|
+
),
|
|
620
|
+
reported=bool(raw_usage),
|
|
621
|
+
source="model-provider" if raw_usage else None,
|
|
622
|
+
)
|
|
623
|
+
return ModelResponse(
|
|
624
|
+
content=content,
|
|
625
|
+
finish_reason=finish_reason,
|
|
626
|
+
usage=usage,
|
|
627
|
+
tool_calls=calls,
|
|
628
|
+
raw_message=message,
|
|
629
|
+
)
|