imperal-sdk 5.0.0__tar.gz → 5.0.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 (212) hide show
  1. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/CHANGELOG.md +205 -0
  2. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/PKG-INFO +1 -1
  3. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/__init__.py +1 -1
  4. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/extension.py +115 -99
  5. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/extension.py +8 -5
  6. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/imperal.schema.json +252 -251
  7. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/action_result.py +38 -0
  8. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/validator.py +125 -98
  9. imperal_sdk-5.0.2/tests/test_data_model_kwarg.py +311 -0
  10. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_panels.py +20 -4
  11. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_sdk_version_stamp.py +1 -1
  12. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator.py +24 -3
  13. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.github/workflows/identity-contract.yml +0 -0
  14. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.github/workflows/publish.yml +0 -0
  15. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.github/workflows/test.yml +0 -0
  16. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.gitignore +0 -0
  17. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/LICENSE +0 -0
  18. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/README.md +0 -0
  19. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/api_surface.json +0 -0
  20. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/pyproject.toml +0 -0
  21. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/.codebase-index-cache.pkl +0 -0
  22. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ai/__init__.py +0 -0
  23. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ai/client.py +0 -0
  24. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/auth/__init__.py +0 -0
  25. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/auth/client.py +0 -0
  26. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/auth/middleware.py +0 -0
  27. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/billing/__init__.py +0 -0
  28. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/billing/client.py +0 -0
  29. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cache/__init__.py +0 -0
  30. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cache/client.py +0 -0
  31. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cache/protocol.py +0 -0
  32. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/__init__.py +0 -0
  33. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/action_result.py +0 -0
  34. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/error_codes.py +0 -0
  35. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/exceptions.py +0 -0
  36. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/filters.py +0 -0
  37. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/guards.py +0 -0
  38. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/kernel_primitives.py +0 -0
  39. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/narration.py +0 -0
  40. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/narration_guard.py +0 -0
  41. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/prompt.py +0 -0
  42. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/refusal.py +0 -0
  43. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cli/__init__.py +0 -0
  44. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cli/main.py +0 -0
  45. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/config/__init__.py +0 -0
  46. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/config/client.py +0 -0
  47. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/context.py +0 -0
  48. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/db/__init__.py +0 -0
  49. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/db/client.py +0 -0
  50. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/errors.py +0 -0
  51. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/extensions/__init__.py +0 -0
  52. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/extensions/client.py +0 -0
  53. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/http/__init__.py +0 -0
  54. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/http/client.py +0 -0
  55. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/manifest.py +0 -0
  56. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/manifest_schema.py +0 -0
  57. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/notify/__init__.py +0 -0
  58. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/notify/client.py +0 -0
  59. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/__init__.py +0 -0
  60. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/icnli_integrity_rules.txt +0 -0
  61. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/kernel_formatting_rule.txt +0 -0
  62. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/kernel_proactivity_rule.txt +0 -0
  63. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/protocols.py +0 -0
  64. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/rpc/__init__.py +0 -0
  65. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/rpc/codec.py +0 -0
  66. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/rpc/contract.py +0 -0
  67. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/__init__.py +0 -0
  68. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/executor.py +0 -0
  69. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/llm_provider.py +0 -0
  70. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/message_adapter.py +0 -0
  71. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/action_result.schema.json +0 -0
  72. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/balance_info.schema.json +0 -0
  73. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/chat_result.schema.json +0 -0
  74. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/completion_result.schema.json +0 -0
  75. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/document.schema.json +0 -0
  76. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/event.schema.json +0 -0
  77. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/file_info.schema.json +0 -0
  78. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/function_call.schema.json +0 -0
  79. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/http_response.schema.json +0 -0
  80. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/limits_result.schema.json +0 -0
  81. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/subscription_info.schema.json +0 -0
  82. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/__init__.py +0 -0
  83. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/client.py +0 -0
  84. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/exceptions.py +0 -0
  85. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/panel_handler.py +0 -0
  86. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/spec.py +0 -0
  87. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/security/__init__.py +0 -0
  88. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/security/call_token.py +0 -0
  89. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/skeleton/__init__.py +0 -0
  90. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/skeleton/client.py +0 -0
  91. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/storage/__init__.py +0 -0
  92. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/storage/client.py +0 -0
  93. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/store/__init__.py +0 -0
  94. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/store/client.py +0 -0
  95. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/store/exceptions.py +0 -0
  96. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/testing/__init__.py +0 -0
  97. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/testing/mock_context.py +0 -0
  98. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/testing/mock_secrets.py +0 -0
  99. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/__init__.py +0 -0
  100. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/client.py +0 -0
  101. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/generate_api_surface.py +0 -0
  102. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/validate_identity_contract.py +0 -0
  103. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/__init__.py +0 -0
  104. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/chat_result.py +0 -0
  105. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/client_contracts.py +0 -0
  106. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/contracts.py +0 -0
  107. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/contributions.py +0 -0
  108. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/events.py +0 -0
  109. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/health.py +0 -0
  110. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/identity.py +0 -0
  111. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/models.py +0 -0
  112. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/pagination.py +0 -0
  113. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/store_contracts.py +0 -0
  114. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/__init__.py +0 -0
  115. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/actions.py +0 -0
  116. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/base.py +0 -0
  117. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/data.py +0 -0
  118. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/display.py +0 -0
  119. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/feedback.py +0 -0
  120. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/graph.py +0 -0
  121. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/input_components.py +0 -0
  122. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/interactive.py +0 -0
  123. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/layout.py +0 -0
  124. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/theme.py +0 -0
  125. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/validator_v1_6_0.py +0 -0
  126. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/conftest.py +0 -0
  127. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/contracts/__init__.py +0 -0
  128. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/contracts/test_store_contracts.py +0 -0
  129. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/fixtures/openapi/auth-gateway.json +0 -0
  130. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/fixtures/openapi/registry.json +0 -0
  131. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/fixtures/openapi/sharelock-cases.json +0 -0
  132. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/rpc/__init__.py +0 -0
  133. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/rpc/test_codec.py +0 -0
  134. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/rpc/test_contract.py +0 -0
  135. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/runtime/__init__.py +0 -0
  136. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/runtime/test_llm_provider_config_store.py +0 -0
  137. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/runtime/test_llm_provider_ctx_injection.py +0 -0
  138. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/store/__init__.py +0 -0
  139. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/store/test_list_users_client.py +0 -0
  140. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/store/test_query_all_client.py +0 -0
  141. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_action_result_typed.py +0 -0
  142. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_as_user.py +0 -0
  143. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_auth.py +0 -0
  144. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_billing.py +0 -0
  145. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_cache_client.py +0 -0
  146. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_cache_model.py +0 -0
  147. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_call_token.py +0 -0
  148. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_extension_deprecation.py +0 -0
  149. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_extension_no_llm_router.py +0 -0
  150. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_filters.py +0 -0
  151. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_function_background_flag.py +0 -0
  152. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_guards.py +0 -0
  153. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_guards_bleed.py +0 -0
  154. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_prompt.py +0 -0
  155. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_result.py +0 -0
  156. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_cli.py +0 -0
  157. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_client_contracts.py +0 -0
  158. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_config_client.py +0 -0
  159. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context.py +0 -0
  160. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context_background_task.py +0 -0
  161. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context_deliver_chat_message.py +0 -0
  162. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context_guards.py +0 -0
  163. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_contracts.py +0 -0
  164. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_contracts_live.py +0 -0
  165. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_contributions.py +0 -0
  166. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_document_contract.py +0 -0
  167. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_emits_decorator.py +0 -0
  168. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_error_codes.py +0 -0
  169. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_errors.py +0 -0
  170. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_event_schema_v2.py +0 -0
  171. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_events_health.py +0 -0
  172. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_extension.py +0 -0
  173. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_extension_v2.py +0 -0
  174. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_extensions_emit.py +0 -0
  175. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_http_timeout_override.py +0 -0
  176. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_id_shape_guard.py +0 -0
  177. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_identity_contract.py +0 -0
  178. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_imperal_schema_v2.py +0 -0
  179. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_kernel_primitives.py +0 -0
  180. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest.py +0 -0
  181. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_no_orchestrator_tool.py +0 -0
  182. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_roundtrip_gate.py +0 -0
  183. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_schema.py +0 -0
  184. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_v2_events.py +0 -0
  185. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_v2_other_sections.py +0 -0
  186. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_v2_webhooks.py +0 -0
  187. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_validator_v2.py +0 -0
  188. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_mock_context.py +0 -0
  189. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_models.py +0 -0
  190. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_narration_emission.py +0 -0
  191. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_narration_guard.py +0 -0
  192. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_openai_max_completion_tokens.py +0 -0
  193. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_pagination.py +0 -0
  194. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_panel_rendering_contract.py +0 -0
  195. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_skeleton_decorator.py +0 -0
  196. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_spec_validation.py +0 -0
  197. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_tools_client.py +0 -0
  198. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui.py +0 -0
  199. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_fileupload_enhanced.py +0 -0
  200. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_html.py +0 -0
  201. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_image_enhanced.py +0 -0
  202. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_open.py +0 -0
  203. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_theme.py +0 -0
  204. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_user.py +0 -0
  205. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_v7_emit_refusal.py +0 -0
  206. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_drift.py +0 -0
  207. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_pep563.py +0 -0
  208. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_v1_6_0_rules.py +0 -0
  209. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_v25.py +0 -0
  210. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_write_arg_bleed.py +0 -0
  211. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/tools/__init__.py +0 -0
  212. {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/tools/test_generate_api_surface.py +0 -0
@@ -2,6 +2,211 @@
2
2
 
3
3
  All notable changes to `imperal-sdk` are documented here.
4
4
 
5
+ ## 5.0.2 — 2026-05-26 — Federal source-cite hygiene
6
+
7
+ **Docs-only release. No behavior change. No new APIs.** Adds two federal-contract
8
+ citation comments in source so the kernel's federal source-cite test gates
9
+ (`I-SDK-DECORATOR-DATA-MODEL-KWARG` + `I-SDK-RETURN-DATA-VALIDATED-AT-EMIT`)
10
+ stay green across SDK reinstalls.
11
+
12
+ ### Touched files
13
+
14
+ - `src/imperal_sdk/chat/extension.py` — `data_model` kwarg docstring now cites
15
+ `I-SDK-DECORATOR-DATA-MODEL-KWARG`.
16
+ - `src/imperal_sdk/types/action_result.py` — `validate_against` docstring now
17
+ cites `I-SDK-RETURN-DATA-VALIDATED-AT-EMIT`.
18
+
19
+ ### Why
20
+
21
+ The kernel federal suite asserts the literal invariant IDs appear in SDK
22
+ source. 5.0.1 shipped without the comments; the kernel's 2 source-cite
23
+ tests stayed pinned green by an in-place edit of the venv `site-packages`
24
+ copy which would not survive a future `pip install -U`. 5.0.2 closes that
25
+ durability gap.
26
+
27
+ No federal contracts changed, no public API changes, no schema drift.
28
+
29
+ ## 5.0.1 — 2026-05-17 — Federal Typed Return Contract
30
+
31
+ **Additive (no breaking changes).** Adds typed return contract for
32
+ `@chat.function` handlers so the platform can validate `$REF` paths in
33
+ multi-step chains against a declared schema and prevent input/output
34
+ field-name drift. Extensions built against 5.0.0 continue to work
35
+ unmodified — V23 ships as **WARN** in 5.0.1 and may promote to **ERROR**
36
+ in a future minor after third-party adoption (env-toggle:
37
+ `IMPERAL_VALIDATOR_V23_SEVERITY=error`).
38
+
39
+ ### What's new
40
+
41
+ - **`@chat.function(data_model=...)` kwarg.** Declare the Pydantic
42
+ `BaseModel` subclass that describes `ActionResult.data` for this tool.
43
+ When declared, the SDK populates `FunctionDef._return_model` directly,
44
+ emits a `return_schema` field into the manifest, and the platform uses
45
+ the schema to:
46
+ 1. Validate `$REF:<app_id>[<n>].path` references in chain steps against
47
+ real field names (closes the "next step references field that doesn't
48
+ exist" class).
49
+ 2. Render `return_fields` in the classifier envelope so the LLM knows
50
+ what shape it can read.
51
+ 3. Run `data.model_validate(...)` at emit time (warn-only in 5.0.1).
52
+ - **`ActionResult[T]` generic auto-detection.** `-> ActionResult[NoteRecord]`
53
+ return annotations now populate `_return_model=NoteRecord` automatically.
54
+ Resolution priority for `_return_model`:
55
+ 1. Explicit `data_model=` kwarg — wins.
56
+ 2. Direct `-> SomeBaseModel` return annotation.
57
+ 3. `-> ActionResult[T]` generic extraction.
58
+ 4. None of the above → `_return_model = None`.
59
+ - **`ActionResult.validate_against(model_class)` method.** Validates
60
+ `self.data` against a Pydantic model class, logs a structured warning
61
+ on mismatch, never raises. Useful for early type assurance in handler
62
+ code.
63
+ - **Validator V23 (read tools, WARN by default).** Every
64
+ `@chat.function(action_type="read", ...)` SHOULD declare `data_model=`
65
+ (or use `-> ActionResult[T]` / `-> SomeBaseModel`). Toggle via
66
+ `IMPERAL_VALIDATOR_V23_SEVERITY=warn|error`.
67
+ - **Validator V24 (write/destructive tools, WARN).** Same rule as V23
68
+ but advisory — declaring `data_model` on writes lets the chain narrator
69
+ and audit ledger describe the resulting entity shape without
70
+ re-deriving from text.
71
+ - **`V31` first-party allowlist is now env-driven.** The SDK no longer
72
+ ships with an embedded allowlist. Set `IMPERAL_FIRSTPARTY_AUTHOR_IDS`
73
+ (comma-separated) at validation time to enable the local check; the
74
+ Dev Portal continues to enforce server-side at publish.
75
+ - **Synthetic `Secrets` panel is lazy-registered.** Previously every
76
+ `Extension` got a synthetic `__panel__secrets` entry on the right slot
77
+ of the chat UI — even when the extension declared zero secrets, the
78
+ user saw an empty placeholder titled "Secrets" with developer-guidance
79
+ text. As of 5.0.1 the panel is registered ONLY when the extension
80
+ calls `@ext.secret(...)` for the first time. Extensions with secrets
81
+ see the same UI as before; extensions without secrets no longer ship
82
+ an empty placeholder panel. No code change required for ext authors;
83
+ no platform-side contract change (the federal secrets contract is
84
+ enforced manifest-side and is independent of UI presence).
85
+
86
+ ### Migration for extension authors
87
+
88
+ **Reads (V23).** Add `data_model=...` to every `@chat.function(action_type="read", ...)`:
89
+
90
+ ```python
91
+ from pydantic import BaseModel
92
+ from imperal_sdk import Extension
93
+ from imperal_sdk.chat import ChatExtension
94
+ from imperal_sdk.chat.action_result import ActionResult
95
+
96
+ class NoteRecord(BaseModel):
97
+ note_id: str
98
+ title: str
99
+ content: str
100
+ folder_id: str | None = None
101
+
102
+ class ListNotesParams(BaseModel):
103
+ folder_id: str | None = None
104
+ limit: int = 50
105
+
106
+ @chat.function(
107
+ name="list_notes",
108
+ description="List notes — paginated, optionally filtered by folder.",
109
+ action_type="read",
110
+ data_model=NoteRecord,
111
+ )
112
+ async def list_notes(ctx, params: ListNotesParams) -> ActionResult:
113
+ notes = await ctx.api.notes.list(folder_id=params.folder_id, limit=params.limit)
114
+ return ActionResult.success(data={"notes": notes}, summary=f"{len(notes)} notes")
115
+ ```
116
+
117
+ Equivalent using the `ActionResult[T]` generic (no `data_model` kwarg
118
+ needed):
119
+
120
+ ```python
121
+ async def list_notes(ctx, params: ListNotesParams) -> ActionResult[NoteRecord]:
122
+ ...
123
+ ```
124
+
125
+ **Field-name symmetry.** Use the **same field names** in your
126
+ `data_model` as in the corresponding input `*Params` model. Closes a real
127
+ drift class where input was `content_text` but the record exposed
128
+ `content`, so chain steps referencing `$REF:notes[0].content_text` silently
129
+ got `None`. If your input uses `content_text`, your `data_model` should
130
+ expose `content_text` too (or vice versa — pick one and be consistent).
131
+
132
+ **Writes (V24).** Same `data_model=` kwarg, recommended but not required:
133
+
134
+ ```python
135
+ @chat.function(
136
+ name="create_note",
137
+ description="Create a new note with title, content, optional folder.",
138
+ action_type="write",
139
+ event="notes.created",
140
+ data_model=NoteRecord, # what the call returns
141
+ )
142
+ async def create_note(ctx, params: CreateNoteParams) -> ActionResult:
143
+ note = await ctx.api.notes.create(params.model_dump())
144
+ return ActionResult.success(data=note, summary=f"Created {note['title']!r}")
145
+ ```
146
+
147
+ ### Skeleton contract reminder
148
+
149
+ The skeleton layer is the **LLM context cache** read by the classifier
150
+ and narrator — handlers must NOT read from it (V24-AST enforces this).
151
+ Conventions when authoring an extension:
152
+
153
+ 1. **Refresh tools** that populate the skeleton MUST be named
154
+ `skeleton_refresh_<section>` (or use the `@ext.skeleton("<section>")`
155
+ decorator, which applies the convention automatically). The platform
156
+ auto-derives `skeleton_sections` rows from these names; tools named
157
+ `refresh_<section>` are NOT auto-wired (validator V13 WARN).
158
+ 2. **Alert tools** that fire when a refreshed section changes MUST be
159
+ named `skeleton_alert_<section>` (V13 INFO when prefix is missing).
160
+ 3. **Skeleton data is read-only to your handlers.** Never write
161
+ `ctx.skeleton.X = ...` or read `ctx.skeleton.X` inside a
162
+ `@chat.function` body — validator V24-AST hard-rejects it. Use
163
+ `ctx.api` to talk to the real backend; skeleton is refreshed
164
+ automatically after writes via your refresh tools.
165
+ 4. **Skeleton sections SHOULD have stable, typed shapes.** Document the
166
+ shape in a Pydantic model and serialise via `model_dump()` before
167
+ returning from your refresh tool — this keeps the LLM context cache
168
+ parseable and helps the classifier hint at what fields it can rely
169
+ on.
170
+
171
+ ### Dev Portal enforcement (publish gate)
172
+
173
+ The Dev Portal runs `imperal_sdk.validator.validate_extension` against
174
+ every submitted build. To make V23 a hard publish gate after third-party
175
+ adoption, set the env var on the Dev Portal validator process:
176
+
177
+ ```
178
+ IMPERAL_VALIDATOR_V23_SEVERITY=error
179
+ ```
180
+
181
+ V23 then promotes from WARN to ERROR; submissions missing `data_model`
182
+ on any `read` tool will be rejected at publish with a structured fix-hint
183
+ pointing at the offending function.
184
+
185
+ ### Tests
186
+
187
+ 15 new tests covering:
188
+
189
+ - `data_model=` kwarg propagation to `FunctionDef._return_model`.
190
+ - Precedence: `data_model=` wins over `-> ActionResult[T]`.
191
+ - `-> ActionResult[T]` generic auto-detection when `data_model` is absent.
192
+ - V23 WARN (default) and ERROR (`IMPERAL_VALIDATOR_V23_SEVERITY=error`)
193
+ modes for read tools missing `data_model`.
194
+ - V23 passes when `data_model=` declared OR `-> ActionResult[T]` used.
195
+ - V24 WARN for write + destructive tools missing `data_model`.
196
+ - V24 passes when `data_model=` declared.
197
+ - `ActionResult.validate_against` passes / warns / no-ops correctly.
198
+ - Synthetic `Secrets` panel: not registered when no secrets declared,
199
+ registered after first `@ext.secret(...)` call (lazy registration).
200
+
201
+ ### Compatibility
202
+
203
+ - **No breaking change.** SDK 5.0.0 extensions continue to load and run
204
+ unmodified; V23 will surface as WARNs in the validation report but
205
+ publish stays green by default.
206
+ - **Required platform.** Kernel that ingests `return_schema` from
207
+ manifests for `$REF` path validation: 5.0.1+. Earlier kernels ignore
208
+ the new field gracefully.
209
+
5
210
  ## 5.0.0 — 2026-05-15 — Unified Chain Orchestrator
6
211
 
7
212
  **BREAKING:**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: imperal-sdk
3
- Version: 5.0.0
3
+ Version: 5.0.2
4
4
  Summary: SDK for building Imperal Cloud extensions
5
5
  Author: Valentin Scerbacov, Imperal, Inc.
6
6
  License-Expression: AGPL-3.0-or-later
@@ -39,7 +39,7 @@ from imperal_sdk.secrets import (
39
39
  SecretValueTooLarge, SecretDeclarationConflict,
40
40
  )
41
41
 
42
- __version__ = "5.0.0"
42
+ __version__ = "5.0.2"
43
43
 
44
44
  __all__ = [
45
45
  # Core
@@ -27,12 +27,12 @@ _ACTION_WORDS = ("send", "create", "delete", "update", "archive", "reply", "forw
27
27
 
28
28
  @dataclass
29
29
  class FunctionDef:
30
- """Federal v4.0.0 — typed function declaration.
30
+ """Typed function declaration.
31
31
 
32
- The kernel reads ``chain_callable`` from the manifest to pick typed
32
+ The platform reads ``chain_callable`` from the manifest to pick typed
33
33
  dispatch (direct ``app/func(args)`` call) vs. legacy ChatExtension
34
34
  LLM-router delegation. Default ``True`` for any ``action_type`` other
35
- than ``"read"`` so the kernel can deterministically execute writes
35
+ than ``"read"`` so the platform can deterministically execute writes
36
36
  without giving the LLM a chance to summarise instead.
37
37
 
38
38
  ``effects`` declares the side-effect surface (``["create:note"]``,
@@ -41,12 +41,10 @@ class FunctionDef:
41
41
 
42
42
  ``id_projection`` declares the Pydantic params field that carries the
43
43
  resolved target id when this tool runs as a downstream chain step.
44
- Default empty — the kernel falls back to a verb-prefix heuristic
44
+ Default empty — the platform falls back to a verb-prefix heuristic
45
45
  (``delete_note`` -> ``note_id``). Required for compound names where
46
46
  the heuristic produces a wrong field (``delete_notes_from_folder``
47
47
  would naively yield ``notes_from_folder_id`` instead of ``folder_id``).
48
- Federal v4.1.2: one of ``id_projection``, the heuristic match, or
49
- explicit kernel registration is required for chain step targets.
50
48
  """
51
49
  name: str
52
50
  func: Callable
@@ -55,18 +53,15 @@ class FunctionDef:
55
53
  action_type: str = "read" # "read", "write", or "destructive"
56
54
  event: str = "" # event name for ActionResult publishing (e.g. "mail.sent")
57
55
  event_schema: type | None = None # Pydantic BaseModel for typed event data
58
- chain_callable: bool = True # federal v4.0.0 — kernel uses typed dispatch
56
+ chain_callable: bool = True # platform uses typed dispatch
59
57
  effects: list[str] = field(default_factory=list) # ["create:note", "delete:folder", ...]
60
- id_projection: str = "" # federal v4.1.2 — params field carrying resolved target id
61
- # LONGRUN-V1 Component D (v4.2.13+) — declarative sugar over ctx.background_task.
62
- # When background=True, the SDK chat handler auto-wraps the function call in
63
- # ctx.background_task(); the LLM sees an immediate ack with task_id and the
64
- # platform delivers the handler's ActionResult as a fresh bot turn when done.
58
+ id_projection: str = "" # params field carrying resolved target id
65
59
  background: bool = False
66
60
  long_running: bool = False
67
61
  _pydantic_model: type | None = None # auto-detected Pydantic BaseModel class
68
62
  _pydantic_param: str = "" # parameter name that receives the model instance
69
- _return_model: type | None = None # auto-detected return Pydantic model
63
+ _return_model: type | None = None # data_model kwarg OR autodetected return Pydantic model
64
+
70
65
 
71
66
  class ChatExtension:
72
67
  def __init__(self, ext, tool_name: str, description: str, system_prompt: str = "",
@@ -75,43 +70,35 @@ class ChatExtension:
75
70
  self.tool_name = tool_name
76
71
  self.description = description
77
72
  self.system_prompt = system_prompt
78
- # Sprint 2 (2026-04-28): the `model=` parameter is deprecated.
79
- # LLM resolution moved to kernel ctx-injection (see ctx._llm_configs)
80
- # in Sprint 1.2. Removing the constructor argument entirely is
81
- # scheduled for SDK 4.0.0 — until then we keep it for backward
82
- # compat but emit a class-level WARN-once when it's explicitly
83
- # passed. The flag is on the CLASS (not instance) so 11
84
- # extensions don't each warn at boot.
85
73
  if model is not None:
86
74
  if not getattr(ChatExtension, "_model_deprecation_warned", False):
87
75
  log.warning(
88
76
  f"ChatExtension(tool_name={tool_name!r}, model=...): "
89
77
  "the `model=` parameter is deprecated since SDK 3.3.0. "
90
- "LLM model resolution moved to kernel ctx-injection (see "
91
- "ctx._llm_configs). Will be removed in SDK 4.0.0. "
78
+ "LLM model resolution moved to platform ctx-injection (see "
79
+ "ctx._llm_configs). Will be removed in SDK 6.0.0. "
92
80
  "Remove `model=` from extension app.py."
93
81
  )
94
82
  ChatExtension._model_deprecation_warned = True
95
83
  self.model = model
96
84
  else:
97
- self.model = "" # historical default no longer meaningful
85
+ self.model = ""
98
86
  self.max_rounds = max_rounds
99
87
  self._functions: dict[str, FunctionDef] = {}
100
- _self = self
101
88
 
102
- # SDK IDENTITY GUARD: warn developers about self-identification in prompts.
103
89
  if system_prompt and _IDENTITY_PATTERN.search(system_prompt):
104
90
  log.warning(
105
91
  f"[SDK] ChatExtension '{tool_name}': system_prompt contains 'You are ...' — "
106
- "this will be overridden by kernel OS identity. "
92
+ "this will be overridden by platform OS identity. "
107
93
  "Use a neutral capability description instead. "
108
94
  "Example: 'Notes module — manage user notes and folders.'"
109
95
  )
110
- # v5.0.0 (federal: I-MANIFEST-NO-ORCHESTRATOR-TOOL): orchestrator-tool
111
- # auto-registration REMOVED. ChatExtension is now purely a @chat.function
112
- # bundle declaration — kernel chain_executor dispatches each function
113
- # directly via typed dispatch. tool_name kwarg retained for back-compat
114
- # but emits DeprecationWarning. Will be removed in 5.1.0.
96
+
97
+ # v5.0.0: orchestrator-tool auto-registration REMOVED. ChatExtension
98
+ # is now purely a @chat.function bundle declaration — the platform
99
+ # chain executor dispatches each function directly via typed dispatch.
100
+ # tool_name kwarg retained for back-compat but emits DeprecationWarning.
101
+ # Will be removed in 5.1.0.
115
102
  import warnings as _warnings
116
103
  _warnings.warn(
117
104
  f"ChatExtension(tool_name={tool_name!r}): kwarg deprecated in SDK 5.0.0 "
@@ -122,7 +109,7 @@ class ChatExtension:
122
109
  stacklevel=2,
123
110
  )
124
111
  ext._chat_extensions = getattr(ext, "_chat_extensions", {})
125
- ext._chat_extensions[tool_name] = self # registry walk still iterates this
112
+ ext._chat_extensions[tool_name] = self
126
113
 
127
114
  def function(self, name: str, description: str, params: dict | None = None,
128
115
  action_type: str = "read", event: str = "",
@@ -131,51 +118,63 @@ class ChatExtension:
131
118
  effects: list[str] | None = None,
132
119
  id_projection: str | None = None,
133
120
  background: bool = False,
134
- long_running: bool = False):
135
- """Register a chat function (federal v4.0.0 contract).
121
+ long_running: bool = False,
122
+ data_model: type | None = None):
123
+ """Register a chat function.
136
124
 
137
125
  Args:
138
126
  name: Function name (used in tool_use calls).
139
127
  description: What this function does, ≥20 chars (V16). Shown to LLM.
140
128
  params: Parameter definitions dict. SDK auto-derives from a Pydantic
141
- BaseModel param annotation when omitted (V17 federal rule
142
- no ``**kwargs`` or untyped handlers).
129
+ BaseModel param annotation when omitted (V17 no ``**kwargs``
130
+ or untyped handlers).
143
131
  action_type: ``"read"``, ``"write"``, or ``"destructive"``. Drives
144
- KAV verification and the 2-step confirmation gate.
132
+ kernel action verification and the 2-step confirmation gate.
145
133
  event: Event name for ActionResult publishing.
146
134
  event_schema: Optional Pydantic BaseModel class for typed event data.
147
- chain_callable: Federal v4.0.0 — when ``True`` the kernel issues a
148
- direct typed call ``app/func(args)`` instead of delegating to
149
- the ChatExtension LLM router. Defaults to ``True`` for
150
- ``action_type in ("write","destructive")`` so writes never get
151
- lost in LLM paraphrase.
135
+ chain_callable: When ``True`` the platform issues a direct typed
136
+ call ``app/func(args)`` instead of delegating to the
137
+ ChatExtension LLM router. Defaults to ``True`` for ALL
138
+ action_types since v4.2.10.
152
139
  effects: Side-effect surface list — ``["create:note"]``,
153
- ``["delete:folder"]``, etc. Used by chain narrator + audit ledger.
154
- id_projection: Federal v4.1.2 — name of the params field that
155
- carries the resolved target id when this tool runs as a
156
- downstream chain step (e.g. ``"folder_id"`` for
157
- ``delete_notes_from_folder`` whose params include
158
- ``folder_id: str``). When omitted, the kernel falls back to
159
- a verb-prefix heuristic (``delete_note`` -> ``note_id``)
160
- that fails for compound tool names. REQUIRED whenever the
161
- tool name does not directly imply the target field, e.g.
162
- ``delete_notes_from_folder``, ``mark_emails_as_read``,
163
- ``archive_drafts_in_project``.
140
+ ``["delete:folder"]``, etc. Used by chain narrator + audit
141
+ ledger.
142
+ id_projection: Name of the params field that carries the
143
+ resolved target id when this tool runs as a downstream
144
+ chain step (e.g. ``"folder_id"`` for
145
+ ``delete_notes_from_folder``).
146
+ data_model: **v5.0.1 Federal Typed Return Contract.** Explicit
147
+ Pydantic ``BaseModel`` subclass declaring the shape of
148
+ ``ActionResult.data`` for this tool. Federal contract:
149
+ I-SDK-DECORATOR-DATA-MODEL-KWARG. When declared, it
150
+ populates ``FunctionDef._return_model`` directly, takes
151
+ precedence over return-annotation auto-detect, and triggers:
152
+
153
+ 1. Manifest ``return_schema`` emission.
154
+ 2. Platform catalog ingestion of ``return_model``.
155
+ 3. Classifier envelope rendering of ``return_fields``.
156
+ 4. ``$REF`` resolver path validation against the schema.
157
+ 5. Runtime ``data.model_validate`` on emit.
158
+
159
+ **MUST** be present for ``action_type="read"`` (V23 —
160
+ initially WARN, ERROR after soak via
161
+ ``IMPERAL_VALIDATOR_V23_SEVERITY``). Recommended for
162
+ write/destructive (V24, WARN-only).
163
+
164
+ Use the same field names as the corresponding input
165
+ ``CreateXParams`` for round-trip symmetry — this closes the
166
+ ``content_text`` (input) / ``content`` (output) drift class.
164
167
  """
165
168
  if chain_callable is None:
166
- # V19 (federal v4.2.10, 2026-05-13): default True for ALL
167
- # action_types — reads also typed-dispatch when the classifier
168
- # picked them, closing the wrapper-LLM paraphrase risk on
169
- # `list_*`, `search_*`, `get_*` style handlers. Authors who
170
- # need the wrapper-LLM tool-use loop (catch-all conversational
171
- # handlers like `case_chat`) must set `chain_callable=False`
172
- # explicitly. Pre-v4.2.10 default kept `False` for reads;
173
- # backward-compatible because action_type in ("write",
174
- # "destructive") already returned True and continues to.
175
169
  chain_callable = True
176
170
 
177
171
  def decorator(func: Callable) -> Callable:
178
- # Auto-detect Pydantic BaseModel params + return type
172
+ # Auto-detect Pydantic BaseModel params + return type.
173
+ # Resolution priority for _return_model:
174
+ # 1. Explicit `data_model` kwarg — wins
175
+ # 2. Direct `-> SomeBaseModel` return annotation
176
+ # 3. `-> ActionResult[T]` generic extraction
177
+ # 4. None of above → _return_model = None
179
178
  resolved_params = params
180
179
  _detected_model = None
181
180
  _detected_param = ""
@@ -187,43 +186,70 @@ class ChatExtension:
187
186
  if ret_ann is not None:
188
187
  try:
189
188
  from pydantic import BaseModel as _BM
189
+ # (2) Direct -> SomeBaseModel
190
190
  if isinstance(ret_ann, type) and issubclass(ret_ann, _BM):
191
191
  _detected_return_model = ret_ann
192
+ else:
193
+ # (3) -> ActionResult[T] generic — extract T via
194
+ # typing.get_args. typing.get_origin returns the
195
+ # generic class (ActionResult), get_args returns
196
+ # the typevar binding (NoteRecord,).
197
+ _origin = _typing.get_origin(ret_ann)
198
+ _args = _typing.get_args(ret_ann)
199
+ if _origin is not None and _args:
200
+ _origin_name = getattr(_origin, "__name__", "")
201
+ if _origin_name == "ActionResult":
202
+ _t_arg = _args[0]
203
+ if (
204
+ isinstance(_t_arg, type)
205
+ and issubclass(_t_arg, _BM)
206
+ ):
207
+ _detected_return_model = _t_arg
192
208
  except (TypeError, ImportError):
193
209
  pass
194
210
  except Exception:
195
211
  pass
212
+ # (1) Explicit data_model kwarg WINS over auto-detect.
213
+ if data_model is not None:
214
+ try:
215
+ from pydantic import BaseModel as _BM_chk
216
+ if isinstance(data_model, type) and issubclass(data_model, _BM_chk):
217
+ _detected_return_model = data_model
218
+ except (TypeError, ImportError):
219
+ pass
196
220
  if resolved_params is None:
197
221
  import inspect
222
+ import typing as _typing2
223
+ # Use typing.get_type_hints to resolve PEP 563 string
224
+ # annotations to real classes (no manual codepath needed).
225
+ try:
226
+ _resolved_hints = _typing2.get_type_hints(func)
227
+ except Exception:
228
+ _resolved_hints = {}
198
229
  sig = inspect.signature(func)
199
230
  for pname, param in sig.parameters.items():
200
231
  if pname in ("ctx", "self"):
201
232
  continue
202
- ann = param.annotation
203
- if ann != inspect.Parameter.empty:
204
- # PEP 563: from __future__ import annotations → strings
205
- if isinstance(ann, str):
206
- try:
207
- ann = eval(ann, func.__globals__)
208
- except Exception:
209
- continue
210
- try:
211
- from pydantic import BaseModel
212
- if isinstance(ann, type) and issubclass(ann, BaseModel):
213
- schema = ann.model_json_schema()
214
- resolved_params = {}
215
- for field_name, field_info in schema.get("properties", {}).items():
216
- resolved_params[field_name] = {
217
- "type": field_info.get("type", "string"),
218
- "description": field_info.get("description", field_info.get("title", "")),
219
- }
220
- if field_name not in schema.get("required", []):
221
- resolved_params[field_name]["default"] = field_info.get("default")
222
- _detected_model = ann
223
- _detected_param = pname
224
- break
225
- except (TypeError, ImportError):
226
- pass
233
+ ann = _resolved_hints.get(pname, param.annotation)
234
+ if ann == inspect.Parameter.empty:
235
+ continue
236
+ try:
237
+ from pydantic import BaseModel
238
+ if isinstance(ann, type) and issubclass(ann, BaseModel):
239
+ schema = ann.model_json_schema()
240
+ resolved_params = {}
241
+ for field_name, field_info in schema.get("properties", {}).items():
242
+ resolved_params[field_name] = {
243
+ "type": field_info.get("type", "string"),
244
+ "description": field_info.get("description", field_info.get("title", "")),
245
+ }
246
+ if field_name not in schema.get("required", []):
247
+ resolved_params[field_name]["default"] = field_info.get("default")
248
+ _detected_model = ann
249
+ _detected_param = pname
250
+ break
251
+ except (TypeError, ImportError):
252
+ pass
227
253
  if resolved_params is None:
228
254
  resolved_params = {}
229
255
 
@@ -250,13 +276,7 @@ class ChatExtension:
250
276
  message_type: str = "text", intercepted: bool = False,
251
277
  task_cancelled: bool = False, action_meta: dict = None,
252
278
  narration_emission: dict | None = None) -> dict:
253
- """Build return dict using ChatResult for typed construction.
254
-
255
- narration_emission: when the LLM completed the turn via
256
- EMIT_NARRATION_TOOL (P2 Task 27), this carries the parsed
257
- NarrationEmission as a plain dict (.model_dump()). None for
258
- free-form text responses or on parse failure.
259
- """
279
+ """Build return dict using ChatResult for typed construction."""
260
280
  from imperal_sdk.types.chat_result import ChatResult, FunctionCall as FC
261
281
 
262
282
  fcs = []
@@ -293,11 +313,9 @@ class ChatExtension:
293
313
  at = self._functions[func_name].action_type
294
314
  if at != "read":
295
315
  return at
296
- # Backwards compat: if still "read" (default), check name for write words
297
316
  if any(w in func_name.lower() for w in _ACTION_WORDS):
298
317
  return "write"
299
318
  return "read"
300
- # Unknown function — try name-based detection
301
319
  if any(w in func_name.lower() for w in _ACTION_WORDS):
302
320
  return "write"
303
321
  return "read"
@@ -322,5 +340,3 @@ class ChatExtension:
322
340
 
323
341
  def _build_messages(self, history, message, context_window=20, keep_recent=6):
324
342
  return build_messages(history, message, context_window, keep_recent)
325
-
326
-
@@ -142,11 +142,14 @@ class Extension:
142
142
  # EXT-SECRETS-V1 (v4.2.2) — declared secrets emitted into manifest.secrets[]
143
143
  self._secrets: dict[str, "SecretSpec"] = {}
144
144
 
145
- # EXT-SECRETS-V1 (v4.2.4) auto-register synthetic 'secrets' panel
146
- # unconditionally for every Extension. Empty state (no @ext.secret
147
- # declared) renders developer guidance with code example. Federal
148
- # contract enforced platform-side regardless of UI presence.
149
- self._auto_register_secrets_panel()
145
+ # v5.0.1 UX fix: synthetic 'secrets' panel is now registered LAZILY
146
+ # only when the extension calls @ext.secret(...) for the first
147
+ # time. Previously the panel was added unconditionally to every
148
+ # Extension and appeared as an empty placeholder in the right slot
149
+ # of every chat session even for extensions that declare zero
150
+ # secrets. Lazy registration happens in ``secret()`` below; the
151
+ # synthetic Secrets panel UI is identical when the extension
152
+ # actually has secrets to show.
150
153
 
151
154
  def _auto_register_secrets_panel(self) -> None:
152
155
  """Register the platform-provided 'secrets' panel.