cloud-dog-api-kit 0.13.0__tar.gz → 0.13.2__tar.gz

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.
Files changed (229) hide show
  1. cloud_dog_api_kit-0.13.2/.docs-manifest.yml +6 -0
  2. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/CHANGELOG.md +13 -0
  3. cloud_dog_api_kit-0.13.2/DATA-MODEL.md +33 -0
  4. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/PKG-INFO +1 -1
  5. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/README.md +1 -1
  6. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/__init__.py +2 -1
  7. cloud_dog_api_kit-0.13.2/cloud_dog_api_kit/agent/__init__.py +11 -0
  8. cloud_dog_api_kit-0.13.2/cloud_dog_api_kit/agent/session.py +113 -0
  9. cloud_dog_api_kit-0.13.2/cloud_dog_api_kit/agent/strategy.py +36 -0
  10. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_sdk.py +23 -3
  11. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/tool_router.py +1 -1
  12. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/transport.py +144 -1
  13. cloud_dog_api_kit-0.13.2/cloud_dog_api_kit/web/__init__.py +23 -0
  14. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/web/proxy.py +61 -3
  15. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/pyproject.toml +1 -1
  16. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.36_MCPTransportHelpers/test_mcp_transport.py +54 -3
  17. cloud_dog_api_kit-0.13.2/tests/unit/UT1.40_AgentSession/test_agent_session.py +158 -0
  18. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.47_WebApiProxyHeaders/test_web_proxy_headers.py +62 -2
  19. cloud_dog_api_kit-0.13.2/tests/unit/UT_B5A_SyncClass/__init__.py +0 -0
  20. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_B5B_ClientSDK/test_client_sdk.py +36 -1
  21. cloud_dog_api_kit-0.13.2/working/w28b-309/diff-check.log +0 -0
  22. cloud_dog_api_kit-0.13.2/working/w28b-309/focused-tests.log +39 -0
  23. cloud_dog_api_kit-0.13.2/working/w28b-309/regression-tests.log +33 -0
  24. cloud_dog_api_kit-0.13.2/working/w28b-309/secret-grep.log +0 -0
  25. cloud_dog_api_kit-0.13.2/working/w28b-310/git-diff-check.log +0 -0
  26. cloud_dog_api_kit-0.13.2/working/w28b-310/import-smoke.log +3 -0
  27. cloud_dog_api_kit-0.13.2/working/w28b-310/platform-agent-tests.log +2 -0
  28. cloud_dog_api_kit-0.13.2/working/w28b-310/platform-api-kit-tests.log +33 -0
  29. cloud_dog_api_kit-0.13.2/working/w28b-310/platform-cache-tests.log +2 -0
  30. cloud_dog_api_kit-0.13.2/working/w28b-310/platform-jobs-tests.log +3 -0
  31. cloud_dog_api_kit-0.13.2/working/w28b-310/platform-llm-tests.log +2 -0
  32. cloud_dog_api_kit-0.13.2/working/w28b-310/ps96-greps.log +0 -0
  33. cloud_dog_api_kit-0.13.0/cloud_dog_api_kit/web/__init__.py +0 -7
  34. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/.gitignore +0 -0
  35. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/AGENT-INSTRUCTION-FIX-API-KIT.md +0 -0
  36. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/ARCHITECTURE.md +0 -0
  37. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/BUILD.md +0 -0
  38. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/LICENCE +0 -0
  39. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/LICENSE +0 -0
  40. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/NOTICE +0 -0
  41. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/REQUIREMENTS.md +0 -0
  42. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/TESTS.md +0 -0
  43. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/adoption_test.py +0 -0
  44. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/a2a/__init__.py +0 -0
  45. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/a2a/card.py +0 -0
  46. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/a2a/events.py +0 -0
  47. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/a2a/gateway.py +0 -0
  48. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/a2a/skill_audit.py +0 -0
  49. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/auth/__init__.py +0 -0
  50. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/auth/dependency.py +0 -0
  51. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/auth/rbac.py +0 -0
  52. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/auth/service_auth.py +0 -0
  53. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/clients/__init__.py +0 -0
  54. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/clients/circuit_breaker.py +0 -0
  55. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/clients/http_client.py +0 -0
  56. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/clients/retry.py +0 -0
  57. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/compat/__init__.py +0 -0
  58. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/compat/envelope.py +0 -0
  59. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/compat/profile.py +0 -0
  60. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/compat/routes.py +0 -0
  61. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/config.py +0 -0
  62. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/correlation/__init__.py +0 -0
  63. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/correlation/context.py +0 -0
  64. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/correlation/middleware.py +0 -0
  65. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/envelopes/__init__.py +0 -0
  66. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/envelopes/error.py +0 -0
  67. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/envelopes/success.py +0 -0
  68. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/errors/__init__.py +0 -0
  69. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/errors/exceptions.py +0 -0
  70. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/errors/handler.py +0 -0
  71. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/errors/taxonomy.py +0 -0
  72. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/factory.py +0 -0
  73. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/idempotency/__init__.py +0 -0
  74. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/idempotency/middleware.py +0 -0
  75. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/idempotency/store.py +0 -0
  76. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/lifecycle/__init__.py +0 -0
  77. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/lifecycle/hooks.py +0 -0
  78. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/lifecycle/shutdown.py +0 -0
  79. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/__init__.py +0 -0
  80. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/async_jobs.py +0 -0
  81. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_transport/__init__.py +0 -0
  82. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_transport/base.py +0 -0
  83. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_transport/exceptions.py +0 -0
  84. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_transport/http_jsonrpc.py +0 -0
  85. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_transport/legacy_sse.py +0 -0
  86. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_transport/stdio.py +0 -0
  87. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/client_transport/streamable_http.py +0 -0
  88. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/contract.py +0 -0
  89. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/error_mapper.py +0 -0
  90. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/gateway.py +0 -0
  91. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/legacy_sse.py +0 -0
  92. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/session.py +0 -0
  93. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/sync_handler.py +0 -0
  94. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/mcp/tool_audit.py +0 -0
  95. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/middleware/__init__.py +0 -0
  96. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/middleware/cors.py +0 -0
  97. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/middleware/logging.py +0 -0
  98. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/middleware/request_size_limit.py +0 -0
  99. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/middleware/timeout.py +0 -0
  100. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/middleware/timing.py +0 -0
  101. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/openapi/__init__.py +0 -0
  102. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/openapi/customise.py +0 -0
  103. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/openapi/route.py +0 -0
  104. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/routers/__init__.py +0 -0
  105. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/routers/crud.py +0 -0
  106. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/routers/health.py +0 -0
  107. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/routers/jobs.py +0 -0
  108. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/routers/version.py +0 -0
  109. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/schemas/__init__.py +0 -0
  110. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/schemas/envelopes.py +0 -0
  111. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/schemas/filters.py +0 -0
  112. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/schemas/pagination.py +0 -0
  113. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/streaming/__init__.py +0 -0
  114. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/streaming/events.py +0 -0
  115. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/streaming/jsonl.py +0 -0
  116. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/streaming/sse.py +0 -0
  117. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/__init__.py +0 -0
  118. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/conformance.py +0 -0
  119. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/fixtures.py +0 -0
  120. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/flows/__init__.py +0 -0
  121. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/flows/auth_flow.py +0 -0
  122. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/flows/crud_flow.py +0 -0
  123. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/flows/job_flow.py +0 -0
  124. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/testing/flows/streaming_flow.py +0 -0
  125. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/traceability_ids.py +0 -0
  126. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/versioning/__init__.py +0 -0
  127. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/versioning/header.py +0 -0
  128. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/webhook/__init__.py +0 -0
  129. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/cloud_dog_api_kit/webhook/signature.py +0 -0
  130. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/docs/ARCHITECTURE.md +0 -0
  131. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/docs/CONFIGURATION.md +0 -0
  132. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/docs/EXAMPLES.md +0 -0
  133. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/examples/__init__.py +0 -0
  134. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/examples/fastapi_app/README.md +0 -0
  135. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/examples/fastapi_app/__init__.py +0 -0
  136. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/examples/fastapi_app/app.py +0 -0
  137. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/__init__.py +0 -0
  138. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/application/AT1.7_MigrationPatternLegacyToVersioned/test_migration_pattern.py +0 -0
  139. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/conftest.py +0 -0
  140. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/env-AT +0 -0
  141. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/env-IT +0 -0
  142. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/env-ST +0 -0
  143. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/env-UT +0 -0
  144. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/integration/IT1.8_MCPGatewayIntegration/test_mcp_gateway_integration.py +0 -0
  145. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/integration/IT1.9_MCPClientTransports/test_mcp_client_transports.py +0 -0
  146. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/integration/IT1_10_A2AEventsCommonPackage/__init__.py +0 -0
  147. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/integration/IT1_10_A2AEventsCommonPackage/test_a2a_events_common_package.py +0 -0
  148. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/quality/QT_PUBLISH_COMPLIANCE/__init__.py +0 -0
  149. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/quality/QT_PUBLISH_COMPLIANCE/test_publish_compliance.py +0 -0
  150. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/quality/__init__.py +0 -0
  151. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/security/SEC1.1_AuthEnforcement/test_auth_enforcement.py +0 -0
  152. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/security/SEC1.2_RBACEnforcement/test_rbac_enforcement.py +0 -0
  153. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/security/SEC1.3_TenantIsolation/test_tenant_sec.py +0 -0
  154. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/security/SEC1.4_InputValidation/test_input_validation.py +0 -0
  155. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/security/SEC1.5_SecretRedaction/test_secret_redaction.py +0 -0
  156. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/security/SEC1.6_ErrorSanitisation/test_error_sanitisation.py +0 -0
  157. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.10_IdempotencyEndToEnd/test_idempotency_e2e.py +0 -0
  158. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.11_HTTPClientEndToEnd/test_http_client_e2e.py +0 -0
  159. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.12_MCPFullTransportFlow/test_mcp_transport_flow.py +0 -0
  160. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.13_StartupLifecycleIntegration/test_startup_lifecycle_integration.py +0 -0
  161. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.1_FullMiddlewareStack/test_full_stack.py +0 -0
  162. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.2_ErrorFlowEndToEnd/test_error_flow.py +0 -0
  163. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.3_PaginationEndToEnd/test_pagination_e2e.py +0 -0
  164. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.4_AuthEndToEnd/test_auth_e2e.py +0 -0
  165. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.5_HealthStatusEndToEnd/test_health_e2e.py +0 -0
  166. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.6_AppFactory/test_app_factory.py +0 -0
  167. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.7_CRUDFlowEndToEnd/test_crud_e2e.py +0 -0
  168. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.8_JobFlowEndToEnd/test_job_e2e.py +0 -0
  169. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/system/ST1.9_StreamingEndToEnd/test_streaming_e2e.py +0 -0
  170. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/test_traceability_ids.py +0 -0
  171. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.10_ServiceAuth/test_service_auth.py +0 -0
  172. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.11_CorrelationContext/test_correlation_context.py +0 -0
  173. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.12_CorrelationMiddleware/test_correlation_middleware.py +0 -0
  174. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.13_PaginationModels/test_pagination_models.py +0 -0
  175. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.14_PaginationDependency/test_pagination_dep.py +0 -0
  176. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.15_FilterHelpers/test_filters.py +0 -0
  177. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.16_CRUDRouter/test_crud_router.py +0 -0
  178. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.17_HealthRouter/test_health_router.py +0 -0
  179. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.18_ReadyLiveEndpoints/test_ready_live.py +0 -0
  180. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.19_JobRouter/test_job_router.py +0 -0
  181. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.1_SuccessEnvelope/test_success_envelope.py +0 -0
  182. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.20_SSEStreaming/test_sse.py +0 -0
  183. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.21_JSONLStreaming/test_jsonl.py +0 -0
  184. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.22_SSEEventModel/test_sse_event.py +0 -0
  185. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.23_HTTPClient/test_http_client.py +0 -0
  186. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.24_RetryPolicy/test_retry_policy.py +0 -0
  187. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.25_CORSHelper/test_cors.py +0 -0
  188. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.26_RequestLogging/test_request_logging.py +0 -0
  189. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.27_TimeoutMiddleware/test_timeout.py +0 -0
  190. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.28_IdempotencyMiddleware/test_idempotency.py +0 -0
  191. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.29_IdempotencyStore/test_idem_store.py +0 -0
  192. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.2_ErrorEnvelope/test_error_envelope.py +0 -0
  193. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.30_VersionHeader/test_version_header.py +0 -0
  194. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.31_OpenAPICustomise/test_openapi.py +0 -0
  195. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.32_MCPGateway/test_mcp_gateway.py +0 -0
  196. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.33_A2AGateway/test_a2a_gateway.py +0 -0
  197. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.34_TestFixtures/test_fixtures.py +0 -0
  198. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.35_ConformanceValidators/test_conformance.py +0 -0
  199. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.37_MCPClientTransports/test_base.py +0 -0
  200. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.37_MCPClientTransports/test_http_jsonrpc.py +0 -0
  201. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.37_MCPClientTransports/test_legacy_sse.py +0 -0
  202. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.37_MCPClientTransports/test_stdio.py +0 -0
  203. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.37_MCPClientTransports/test_streamable_http.py +0 -0
  204. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.37_MCPToolRouter/test_mcp_tool_router.py +0 -0
  205. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.38_StartupLifecycleHooks/test_lifecycle_hooks.py +0 -0
  206. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.39_LegacyEnvelopeMiddleware/test_legacy_envelope.py +0 -0
  207. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.3_ErrorTaxonomy/test_error_taxonomy.py +0 -0
  208. {cloud_dog_api_kit-0.13.0/tests/unit/UT_B5A_SyncClass → cloud_dog_api_kit-0.13.2/tests/unit/UT1.40_AgentSession}/__init__.py +0 -0
  209. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.40_LegacyRouteAdapter/test_legacy_route_adapter.py +0 -0
  210. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.41_MCPSessionLifecycle/test_mcp_session.py +0 -0
  211. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.42_ProfileContextMiddleware/test_profile_context.py +0 -0
  212. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.43_WebhookSignatureVerification/test_webhook_signature.py +0 -0
  213. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.44_RequestSizeLimit/test_request_size_limit.py +0 -0
  214. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.45_GracefulShutdown/test_graceful_shutdown.py +0 -0
  215. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.46_MCPContractRegistration/test_mcp_contract_registration.py +0 -0
  216. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.4_ErrorExceptions/test_error_exceptions.py +0 -0
  217. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.5_ErrorHandler/test_error_handler.py +0 -0
  218. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.6_AuthDependency/test_auth_dependency.py +0 -0
  219. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.7_BearerAuth/test_bearer_auth.py +0 -0
  220. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.8_RBACHelpers/test_rbac.py +0 -0
  221. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT1.9_TenantIsolation/test_tenant.py +0 -0
  222. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_A2AEvents/test_a2a_events.py +0 -0
  223. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_A2AEvents/test_http_ingest_adapter.py +0 -0
  224. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_A2AEvents/test_persistent_event_broadcaster.py +0 -0
  225. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_A2AEvents/test_rest_poll_adapter.py +0 -0
  226. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_A2AEvents/test_websocket_adapter.py +0 -0
  227. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_A2aSkillAudit/test_a2a_skill_audit.py +0 -0
  228. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_B5A_SyncClass/test_sync_class.py +0 -0
  229. {cloud_dog_api_kit-0.13.0 → cloud_dog_api_kit-0.13.2}/tests/unit/UT_McpToolAudit/test_mcp_tool_audit.py +0 -0
@@ -0,0 +1,6 @@
1
+ schema-version: 1.0
2
+ project: platform-api-kit
3
+ canon-set: PACKAGE
4
+ canon-template-baseline: 1.0
5
+ conditional-docs: []
6
+ additional-docs: []
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.2 - 2026-06-05
4
+ - MCP `sync-with-progress` dispatch now allows the full PS-95 five-minute Mode 2
5
+ sync budget instead of clamping fallback execution to the short 55-second
6
+ budget. This lets progress-aware clients receive the final tool result for
7
+ migrated long-call services after the old Traefik timeout window.
8
+
9
+ ## 0.13.1 - 2026-06-05
10
+ - MCP streamable HTTP transport now emits `notifications/progress` SSE frames for
11
+ `sync-with-progress` tools when the client advertises `text/event-stream`, then
12
+ returns the final JSON-RPC tool result on the same stream. This fixes W28D-323
13
+ Mode 2 progress-aware client compatibility for migrated services while keeping
14
+ stock JSON clients on the existing final-result response path.
15
+
3
16
  ## 0.12.4 - 2026-05-04
4
17
  - `WebApiProxy._build_headers` now performs a case-insensitive merge of caller-supplied headers with caller-wins semantics. Previously, Python dict case-sensitivity allowed BOTH `X-API-Key` (the proxy's service key) and `x-api-key` (the caller's forwarded header from FastAPI request iteration) to coexist; httpx emitted both as separate header lines and Starlette's first-match selection caused the service key to override the caller's authenticated identity downstream — breaking RBAC such that any developer making a proxied request impersonated admin. The fix removes any existing case-insensitive duplicate of an incoming header before adding the caller's value, preserving the caller's identity for downstream authorisation. Version skips 0.12.3 because that internal-PyPI release was reverted (untested + bundled an unrelated shared-client change). Resolves the D-RBAC root cause documented in W28A-#A51-REPORT-2026-05-04.md Section D.
5
18
 
@@ -0,0 +1,33 @@
1
+ ---
2
+ template-id: T-PKG-DMT
3
+ template-version: 1.0
4
+ applies-to: DATA-MODEL.md
5
+ registry: package
6
+ required: must-have
7
+ when-applicable: ""
8
+ template-last-updated: 2026-06-12
9
+ template-owner: platform-standards
10
+
11
+ project: platform-api-kit
12
+ public: true
13
+ doc-last-updated: 2026-06-12
14
+ doc-git-commit: no-git
15
+ doc-git-branch: main
16
+ doc-source-shas: []
17
+ doc-age-policy: indefinite
18
+ doc-conformance-stamp: 2026-06-12T12:00:00Z
19
+ ---
20
+
21
+ # platform-api-kit — DATA-MODEL
22
+
23
+ > **Template version:** T-PKG-DMT v1.0
24
+
25
+ ## 1. Exported types
26
+ | Type | Shape | Purpose |
27
+ |---|---|---|
28
+
29
+ ## 2. Persisted state
30
+ If the package owns persisted state, describe schema.
31
+
32
+ ## 3. Cross-references
33
+ - [ARCHITECTURE.md](ARCHITECTURE.md)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloud-dog-api-kit
3
- Version: 0.13.0
3
+ Version: 0.13.2
4
4
  Summary: PS-20 API toolkit for Cloud-Dog services
5
5
  Project-URL: Source, https://github.com/cloud-dog-ai/platform-api-kit
6
6
  Author: Cloud-Dog, Viewdeck Engineering Limited
@@ -133,7 +133,7 @@ pip install cloud-dog-api-kit
133
133
  - `ruff check cloud_dog_api_kit tests` -> `All checks passed!`
134
134
  - `ruff format --check cloud_dog_api_kit tests examples` -> `143 files already formatted`
135
135
  - `python -m build` -> fails in network-restricted environment (isolated `hatchling` install unavailable)
136
- - `/opt/iac/Development/cloud-dog-ai/cloud-dog-ai-platform-standards/packages/backend/platform-config/.venv/bin/python -m build --no-isolation` -> built `.tar.gz` and `.whl`
136
+ - `<platform-config>/.venv/bin/python -m build --no-isolation` -> built `.tar.gz` and `.whl`
137
137
 
138
138
  ---
139
139
 
@@ -33,7 +33,7 @@ Public API:
33
33
 
34
34
  from __future__ import annotations
35
35
 
36
- __version__ = "0.12.1"
36
+ __version__ = "0.13.2"
37
37
 
38
38
 
39
39
  def create_app(*args, **kwargs): # type: ignore[no-untyped-def]
@@ -109,6 +109,7 @@ from cloud_dog_api_kit.openapi import configure_openapi
109
109
  from cloud_dog_api_kit.versioning import VersionHeaderMiddleware
110
110
  from cloud_dog_api_kit.webhook import WebhookSignatureMiddleware, compute_webhook_signature
111
111
  from cloud_dog_api_kit.web.proxy import WebApiProxy
112
+ from cloud_dog_api_kit.agent import AgentSession, AgentStrategy, SessionState, parse_prefer_strategy
112
113
 
113
114
  __all__ = [
114
115
  "create_app",
@@ -0,0 +1,11 @@
1
+ """Agent session lifecycle and strategy parsing (W28B-309, PS-96)."""
2
+
3
+ from cloud_dog_api_kit.agent.session import AgentSession, SessionState
4
+ from cloud_dog_api_kit.agent.strategy import AgentStrategy, parse_prefer_strategy
5
+
6
+ __all__ = [
7
+ "AgentSession",
8
+ "AgentStrategy",
9
+ "SessionState",
10
+ "parse_prefer_strategy",
11
+ ]
@@ -0,0 +1,113 @@
1
+ """Agent session lifecycle (W28B-309, PS-96 §5).
2
+
3
+ An AgentSession tracks a multi-turn agent interaction across
4
+ create → (suspend ↔ resume)* → close transitions. Memory
5
+ persistence is delegated to cloud_dog_cache interfaces.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timezone
12
+ from enum import Enum
13
+ from typing import Any, Callable, Optional
14
+ import uuid
15
+
16
+
17
+ class SessionState(str, Enum):
18
+ """Agent session lifecycle states."""
19
+ CREATED = "created"
20
+ ACTIVE = "active"
21
+ SUSPENDED = "suspended"
22
+ CLOSED = "closed"
23
+
24
+
25
+ _TRANSITIONS: dict[SessionState, set[SessionState]] = {
26
+ SessionState.CREATED: {SessionState.ACTIVE, SessionState.CLOSED},
27
+ SessionState.ACTIVE: {SessionState.SUSPENDED, SessionState.CLOSED},
28
+ SessionState.SUSPENDED: {SessionState.ACTIVE, SessionState.CLOSED},
29
+ SessionState.CLOSED: set(),
30
+ }
31
+
32
+
33
+ @dataclass
34
+ class AgentSession:
35
+ """Lifecycle-managed agent session with memory persistence hooks."""
36
+
37
+ session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
38
+ state: SessionState = SessionState.CREATED
39
+ strategy: Optional[str] = None
40
+ memory: dict[str, Any] = field(default_factory=dict)
41
+ created_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
42
+ updated_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
43
+ _on_persist: Optional[Callable[["AgentSession"], None]] = field(default=None, repr=False)
44
+
45
+ def _transition(self, target: SessionState) -> None:
46
+ allowed = _TRANSITIONS.get(self.state, set())
47
+ if target not in allowed:
48
+ raise ValueError(f"Invalid transition: {self.state.value} → {target.value}")
49
+ self.state = target
50
+ self.updated_at = datetime.now(tz=timezone.utc)
51
+
52
+ # ── Lifecycle hooks ──
53
+
54
+ def create(self, *, strategy: Optional[str] = None) -> "AgentSession":
55
+ """Activate a newly created session."""
56
+ self._transition(SessionState.ACTIVE)
57
+ if strategy:
58
+ self.strategy = strategy
59
+ self._persist()
60
+ return self
61
+
62
+ def suspend(self, *, checkpoint: dict[str, Any] | None = None) -> "AgentSession":
63
+ """Suspend the session, persisting current memory."""
64
+ self._transition(SessionState.SUSPENDED)
65
+ if checkpoint:
66
+ self.memory["_checkpoint"] = checkpoint
67
+ self._persist()
68
+ return self
69
+
70
+ def resume(self) -> "AgentSession":
71
+ """Resume a suspended session."""
72
+ self._transition(SessionState.ACTIVE)
73
+ self.memory.pop("_checkpoint", None)
74
+ self._persist()
75
+ return self
76
+
77
+ def close(self) -> "AgentSession":
78
+ """Close the session permanently."""
79
+ self._transition(SessionState.CLOSED)
80
+ self._persist()
81
+ return self
82
+
83
+ # ── Memory ──
84
+
85
+ def set_memory(self, key: str, value: Any) -> None:
86
+ """Store a value in session memory."""
87
+ self.memory[key] = value
88
+ self._persist()
89
+
90
+ def get_memory(self, key: str, default: Any = None) -> Any:
91
+ """Retrieve a value from session memory."""
92
+ return self.memory.get(key, default)
93
+
94
+ # ── Persistence ──
95
+
96
+ def set_persist_hook(self, hook: Callable[["AgentSession"], None]) -> None:
97
+ """Register a persistence callback (e.g., cloud_dog_cache write)."""
98
+ self._on_persist = hook
99
+
100
+ def _persist(self) -> None:
101
+ if self._on_persist is not None:
102
+ self._on_persist(self)
103
+
104
+ def to_dict(self) -> dict[str, Any]:
105
+ """Serialise for cache/storage."""
106
+ return {
107
+ "session_id": self.session_id,
108
+ "state": self.state.value,
109
+ "strategy": self.strategy,
110
+ "memory": self.memory,
111
+ "created_at": self.created_at.isoformat(),
112
+ "updated_at": self.updated_at.isoformat(),
113
+ }
@@ -0,0 +1,36 @@
1
+ """Agent strategy selection via Prefer header (W28B-309, PS-96 §3)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+ from typing import Optional
7
+
8
+
9
+ class AgentStrategy(str, Enum):
10
+ """PS-96 agent execution strategies."""
11
+ SIMPLE = "simple"
12
+ REACT = "react"
13
+ CODEACT = "codeact"
14
+ SUBAGENT_ROUTER = "subagent_router"
15
+ RLM = "rlm"
16
+ REFLEXION = "reflexion"
17
+
18
+
19
+ def parse_prefer_strategy(prefer_header: Optional[str]) -> Optional[AgentStrategy]:
20
+ """Parse agent-strategy from RFC 7240 Prefer header.
21
+
22
+ Example: ``Prefer: agent-strategy=react``
23
+
24
+ Returns None if no agent-strategy preference found or value unknown.
25
+ """
26
+ if not prefer_header:
27
+ return None
28
+ for token in prefer_header.split(","):
29
+ token = token.strip()
30
+ if token.lower().startswith("agent-strategy="):
31
+ value = token.split("=", 1)[1].strip().strip('"').lower()
32
+ try:
33
+ return AgentStrategy(value)
34
+ except ValueError:
35
+ return None
36
+ return None
@@ -64,9 +64,18 @@ class AsyncJobClient:
64
64
  )
65
65
  return job_id
66
66
 
67
- async def wait(self, job_id: str, *, timeout: float | None = None) -> dict[str, Any]:
67
+ async def wait(
68
+ self,
69
+ job_id: str,
70
+ *,
71
+ timeout: float | None = None,
72
+ on_progress: Callable[[float, float | None, str | None], Awaitable[None]] | None = None,
73
+ ) -> dict[str, Any]:
68
74
  """Poll a job until completion or timeout.
69
75
 
76
+ If on_progress is provided, invokes it with (progress_pct, total, message)
77
+ each time a poll returns progress data (Mode 2 client-side).
78
+
70
79
  Returns the final job status dict with 'result' on success.
71
80
  Raises TimeoutError with the job_id if timeout expires.
72
81
  """
@@ -74,6 +83,17 @@ class AsyncJobClient:
74
83
  while True:
75
84
  status = await self._poll_job(job_id)
76
85
  state = str(status.get("status", "")).lower()
86
+
87
+ # Emit progress if callback provided and status has progress data
88
+ if on_progress and "progress" in status:
89
+ prog = status["progress"]
90
+ if isinstance(prog, dict):
91
+ await on_progress(
92
+ float(prog.get("percentage", 0)),
93
+ prog.get("total"),
94
+ prog.get("message"),
95
+ )
96
+
77
97
  if state in ("completed", "succeeded"):
78
98
  return status
79
99
  if state in ("failed", "cancelled", "timeout", "dead_lettered"):
@@ -109,13 +129,13 @@ class AsyncJobClient:
109
129
  if _is_budget_exceeded(response):
110
130
  job_id = _extract_job_id_from_error(response)
111
131
  if job_id:
112
- return await self.wait(job_id, timeout=timeout)
132
+ return await self.wait(job_id, timeout=timeout, on_progress=on_progress)
113
133
  raise ValueError("Budget exceeded but no job_id in error response")
114
134
 
115
135
  # Async/job_id response (Mode 3)
116
136
  job_id = _extract_job_id(response)
117
137
  if job_id:
118
- return await self.wait(job_id, timeout=timeout)
138
+ return await self.wait(job_id, timeout=timeout, on_progress=on_progress)
119
139
 
120
140
  # Fallback: treat entire response as sync result
121
141
  return {"status": "completed", "result": response}
@@ -45,7 +45,7 @@ SYNC_CLASS_ASYNC = "async-only"
45
45
  SYNC_CLASSES = frozenset({SYNC_CLASS_DEFAULT, SYNC_CLASS_PROGRESS, SYNC_CLASS_ASYNC})
46
46
 
47
47
  DEFAULT_SYNC_BUDGET_SECONDS = 50
48
- MAX_SYNC_BUDGET_SECONDS = 55
48
+ MAX_SYNC_BUDGET_SECONDS = 300
49
49
 
50
50
 
51
51
  @dataclass(slots=True)
@@ -25,6 +25,7 @@
25
25
 
26
26
  from __future__ import annotations
27
27
 
28
+ import asyncio
28
29
  import inspect
29
30
  import json
30
31
  import sys
@@ -44,7 +45,7 @@ from cloud_dog_api_kit.mcp.async_jobs import AsyncJobStore
44
45
  from cloud_dog_api_kit.mcp.error_mapper import map_legacy_mcp_payload
45
46
  from cloud_dog_api_kit.mcp.legacy_sse import LegacySSEBroker, LegacySSEConfig
46
47
  from cloud_dog_api_kit.mcp.session import SESSION_HEADER, McpSessionManager
47
- from cloud_dog_api_kit.mcp.tool_router import ToolContract, normalise_tool_registry
48
+ from cloud_dog_api_kit.mcp.tool_router import SYNC_CLASS_PROGRESS, ToolContract, normalise_tool_registry
48
49
 
49
50
  TransportModes = set[str]
50
51
  ToolCallable = Callable[[dict[str, Any], Request], Awaitable[Any] | Any]
@@ -198,6 +199,41 @@ def _mcp_tool_call_payload(result: Any) -> dict[str, Any]:
198
199
  return payload
199
200
 
200
201
 
202
+ def _accepts_event_stream(request: Request) -> bool:
203
+ return "text/event-stream" in request.headers.get("accept", "").lower()
204
+
205
+
206
+ def _sse_frame(data: dict[str, Any], *, event: str | None = None) -> str:
207
+ lines: list[str] = []
208
+ if event:
209
+ lines.append(f"event: {event}")
210
+ encoded = json.dumps(data, default=str, ensure_ascii=True)
211
+ for line in encoded.splitlines() or [""]:
212
+ lines.append(f"data: {line}")
213
+ return "\n".join(lines) + "\n\n"
214
+
215
+
216
+ def _mcp_progress_notification(
217
+ *,
218
+ progress_token: str,
219
+ progress: float,
220
+ total: float | None,
221
+ message: str,
222
+ ) -> dict[str, Any]:
223
+ params: dict[str, Any] = {
224
+ "progressToken": progress_token,
225
+ "progress": progress,
226
+ "message": message,
227
+ }
228
+ if total is not None:
229
+ params["total"] = total
230
+ return {
231
+ "jsonrpc": "2.0",
232
+ "method": "notifications/progress",
233
+ "params": params,
234
+ }
235
+
236
+
201
237
  async def _maybe_await(value: Any) -> Any:
202
238
  if inspect.isawaitable(value):
203
239
  return await value
@@ -289,6 +325,18 @@ async def _resolve_tool_context(
289
325
  return context
290
326
 
291
327
 
328
+ def _progress_token_from_params(params: dict[str, Any], session_id: str, request_id: Any) -> str:
329
+ meta = params.get("_meta")
330
+ if isinstance(meta, dict):
331
+ token = meta.get("progressToken")
332
+ if token is not None and str(token).strip():
333
+ return str(token)
334
+ token = params.get("progressToken")
335
+ if token is not None and str(token).strip():
336
+ return str(token)
337
+ return f"{session_id}:{request_id if request_id is not None else uuid4().hex}"
338
+
339
+
292
340
  async def _dispatch_payload(
293
341
  tools: dict[str, ToolContract],
294
342
  payload: dict[str, Any],
@@ -665,6 +713,101 @@ async def _dispatch_payload(
665
713
  response.headers["X-Mcp-Session-Created"] = "true" if created else "false"
666
714
  return response
667
715
 
716
+ if (
717
+ payload.get("jsonrpc") == "2.0"
718
+ and modern_jsonrpc
719
+ and tool.sync_class == SYNC_CLASS_PROGRESS
720
+ and wait_value is not False
721
+ and _accepts_event_stream(request)
722
+ ):
723
+ rpc_id = payload.get("id")
724
+ progress_token = _progress_token_from_params(params, session.session_id, rpc_id)
725
+ budget_total = max(float(tool.sync_budget_seconds or 0.0), 1.0)
726
+
727
+ async def _event_stream() -> Any:
728
+ started = asyncio.get_running_loop().time()
729
+ yield _sse_frame(
730
+ _mcp_progress_notification(
731
+ progress_token=progress_token,
732
+ progress=0.0,
733
+ total=budget_total,
734
+ message=f"Started {tool.name}",
735
+ ),
736
+ event="message",
737
+ )
738
+
739
+ task = asyncio.create_task(_call_tool(tool, arguments, request, tool_context))
740
+ last_reported_second = 0
741
+ try:
742
+ while not task.done():
743
+ await asyncio.sleep(1.0)
744
+ elapsed = asyncio.get_running_loop().time() - started
745
+ reported_second = int(elapsed)
746
+ if reported_second <= last_reported_second:
747
+ continue
748
+ last_reported_second = reported_second
749
+ yield _sse_frame(
750
+ _mcp_progress_notification(
751
+ progress_token=progress_token,
752
+ progress=min(float(elapsed), budget_total),
753
+ total=budget_total,
754
+ message=f"Running {tool.name}",
755
+ ),
756
+ event="message",
757
+ )
758
+
759
+ result = await task
760
+ yield _sse_frame(
761
+ _mcp_progress_notification(
762
+ progress_token=progress_token,
763
+ progress=budget_total,
764
+ total=budget_total,
765
+ message=f"Completed {tool.name}",
766
+ ),
767
+ event="message",
768
+ )
769
+ tool_payload = _mcp_tool_call_payload(result)
770
+ response_payload = _jsonrpc_response(rpc_id, result=tool_payload)
771
+ if is_messages_path:
772
+ response_payload = _messages_compat_payload(
773
+ jsonrpc_payload=response_payload,
774
+ legacy_result=result,
775
+ request_id=request_id,
776
+ correlation_id=correlation_id,
777
+ )
778
+ yield _sse_frame(response_payload, event="message")
779
+ except Exception as exc:
780
+ if not task.done():
781
+ task.cancel()
782
+ yield _sse_frame(
783
+ _mcp_progress_notification(
784
+ progress_token=progress_token,
785
+ progress=budget_total,
786
+ total=budget_total,
787
+ message=f"Failed {tool.name}",
788
+ ),
789
+ event="message",
790
+ )
791
+ yield _sse_frame(
792
+ _jsonrpc_response(
793
+ rpc_id,
794
+ error={
795
+ "code": -32000,
796
+ "message": str(exc) or f"Tool failed: {tool.name}",
797
+ },
798
+ ),
799
+ event="message",
800
+ )
801
+
802
+ response = StreamingResponse(
803
+ _event_stream(),
804
+ media_type="text/event-stream",
805
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
806
+ )
807
+ response.headers[SESSION_HEADER] = session.session_id
808
+ response.headers["X-Mcp-Session-Created"] = "true" if created else "false"
809
+ return response
810
+
668
811
  result = await _call_tool(tool, arguments, request, tool_context)
669
812
  if payload.get("jsonrpc") == "2.0" and modern_jsonrpc:
670
813
  tool_payload = _mcp_tool_call_payload(result)
@@ -0,0 +1,23 @@
1
+ # Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
2
+ # Licensed under the Apache License, Version 2.0
3
+ """Web server utilities for cloud_dog_api_kit."""
4
+
5
+ from .proxy import (
6
+ PRINCIPAL_ROLE_HEADER,
7
+ PRINCIPAL_SOURCE_HEADER,
8
+ PRINCIPAL_SOURCE_WEBUI,
9
+ PRINCIPAL_USER_HEADER,
10
+ ProxyResponse,
11
+ SessionPrincipal,
12
+ WebApiProxy,
13
+ )
14
+
15
+ __all__ = [
16
+ "PRINCIPAL_ROLE_HEADER",
17
+ "PRINCIPAL_SOURCE_HEADER",
18
+ "PRINCIPAL_SOURCE_WEBUI",
19
+ "PRINCIPAL_USER_HEADER",
20
+ "ProxyResponse",
21
+ "SessionPrincipal",
22
+ "WebApiProxy",
23
+ ]
@@ -25,6 +25,11 @@ Related Tests: UT1.XX_WebApiProxy
25
25
 
26
26
  Recent Changes (max 10):
27
27
  - 2026-04-08: W28A-849 — Initial implementation.
28
+ - 2026-06-10: PS-82 §8.3 — canonical session-principal forwarding. Adds the
29
+ SessionPrincipal carrier + per-call ``principal=`` forwarding so a web-proxy
30
+ NEVER substitutes its service/service-admin key for a user-originated request
31
+ (the W28A-890 / notification 3-of-76 identity-collapse defect class). The
32
+ backend enforces RBAC on the forwarded principal.
28
33
  """
29
34
 
30
35
  from __future__ import annotations
@@ -37,6 +42,44 @@ import httpx
37
42
 
38
43
  logger = logging.getLogger(__name__)
39
44
 
45
+ # --- PS-82 §8.3 canonical session-principal forwarding headers -------------------
46
+ # The ONE canonical set of headers a web-proxy uses to forward the authenticated
47
+ # session principal (identity + role) to the backend. Services MUST NOT invent
48
+ # their own header names — the backend trusts exactly these on the internal hop.
49
+ PRINCIPAL_USER_HEADER = "X-Request-User"
50
+ PRINCIPAL_ROLE_HEADER = "X-Request-Role"
51
+ PRINCIPAL_SOURCE_HEADER = "X-Request-Source"
52
+ #: Marks a request as originating from an authenticated WebUI session (vs a raw
53
+ #: service-to-service call). The backend uses this to require the forwarded
54
+ #: principal instead of materialising the service-key identity.
55
+ PRINCIPAL_SOURCE_WEBUI = "webui"
56
+
57
+
58
+ @dataclass(frozen=True, slots=True)
59
+ class SessionPrincipal:
60
+ """The authenticated WebUI session principal forwarded to the backend (PS-82 §8.3).
61
+
62
+ Carries the *user's* identity + role so the backend enforces RBAC on the real
63
+ caller. A web-proxy MUST construct one of these from the session for EVERY
64
+ user-originated call site and pass it to the proxy, so the service/service-admin
65
+ key is never substituted for a user request (the identity-collapse defect).
66
+ """
67
+
68
+ user: str
69
+ role: str = "user"
70
+
71
+ def headers(self) -> dict[str, str]:
72
+ """Return the canonical forwarding headers for this principal."""
73
+ user = str(self.user or "").strip()
74
+ if not user:
75
+ return {}
76
+ role = str(self.role or "user").strip().lower() or "user"
77
+ return {
78
+ PRINCIPAL_SOURCE_HEADER: PRINCIPAL_SOURCE_WEBUI,
79
+ PRINCIPAL_USER_HEADER: user,
80
+ PRINCIPAL_ROLE_HEADER: role,
81
+ }
82
+
40
83
 
41
84
  @dataclass
42
85
  class ProxyResponse:
@@ -118,17 +161,28 @@ class WebApiProxy:
118
161
  )
119
162
 
120
163
  def _build_headers(
121
- self, extra_headers: Mapping[str, str] | None = None
164
+ self,
165
+ extra_headers: Mapping[str, str] | None = None,
166
+ principal: SessionPrincipal | None = None,
122
167
  ) -> dict[str, str]:
123
- """Build request headers with API key and optional extras.
168
+ """Build request headers with API key, optional extras, and session principal.
124
169
 
125
170
  Caller-supplied headers take precedence on case-insensitive name match,
126
171
  so a forwarded X-API-Key from an authenticated user overrides the proxy's
127
172
  service key (preserves caller identity for downstream RBAC).
173
+
174
+ When ``principal`` is supplied (PS-82 §8.3), the canonical
175
+ ``X-Request-Source``/``X-Request-User``/``X-Request-Role`` forwarding headers
176
+ are added so the backend authorises the REAL caller. The proxy's
177
+ service/service-admin key still travels as the transport credential for the
178
+ internal hop, but the backend MUST enforce RBAC on the forwarded principal —
179
+ never collapse the user to the service-admin identity.
128
180
  """
129
181
  headers: dict[str, str] = {}
130
182
  if self._api_key:
131
183
  headers[self._api_key_header] = self._api_key
184
+ if principal is not None:
185
+ headers.update(principal.headers())
132
186
  if extra_headers:
133
187
  for incoming_key, incoming_value in extra_headers.items():
134
188
  normalised = incoming_key.lower()
@@ -146,6 +200,7 @@ class WebApiProxy:
146
200
  params: dict[str, Any] | None = None,
147
201
  headers: Mapping[str, str] | None = None,
148
202
  cookies: dict[str, str] | None = None,
203
+ principal: SessionPrincipal | None = None,
149
204
  ) -> ProxyResponse:
150
205
  """Send a proxied request to the API server.
151
206
 
@@ -156,12 +211,15 @@ class WebApiProxy:
156
211
  params: Query parameters.
157
212
  headers: Additional headers to forward.
158
213
  cookies: Cookies to forward (for session-based auth proxying).
214
+ principal: The authenticated WebUI session principal (PS-82 §8.3).
215
+ MUST be supplied for every user-originated call site so the backend
216
+ enforces RBAC on the real caller instead of the service-admin key.
159
217
 
160
218
  Returns:
161
219
  ProxyResponse with status_code, data, and optional error.
162
220
  """
163
221
  url = f"{self._base_url}{path}"
164
- merged_headers = self._build_headers(headers)
222
+ merged_headers = self._build_headers(headers, principal=principal)
165
223
  try:
166
224
  async with httpx.AsyncClient(
167
225
  verify=self._verify_tls,
@@ -4,7 +4,7 @@ authors = [
4
4
  { name = "Cloud-Dog, Viewdeck Engineering Limited" }
5
5
  ]
6
6
  name = "cloud-dog-api-kit"
7
- version = "0.13.0"
7
+ version = "0.13.2"
8
8
  description = "PS-20 API toolkit for Cloud-Dog services"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [