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.
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/CHANGELOG.md +205 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/PKG-INFO +1 -1
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/__init__.py +1 -1
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/extension.py +115 -99
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/extension.py +8 -5
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/imperal.schema.json +252 -251
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/action_result.py +38 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/validator.py +125 -98
- imperal_sdk-5.0.2/tests/test_data_model_kwarg.py +311 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_panels.py +20 -4
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_sdk_version_stamp.py +1 -1
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator.py +24 -3
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.github/workflows/identity-contract.yml +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.github/workflows/publish.yml +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.github/workflows/test.yml +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/.gitignore +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/LICENSE +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/README.md +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/api_surface.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/pyproject.toml +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/.codebase-index-cache.pkl +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ai/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ai/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/auth/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/auth/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/auth/middleware.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/billing/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/billing/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cache/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cache/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cache/protocol.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/action_result.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/error_codes.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/exceptions.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/filters.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/guards.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/kernel_primitives.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/narration.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/narration_guard.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/prompt.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/chat/refusal.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cli/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/cli/main.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/config/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/config/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/context.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/db/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/db/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/errors.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/extensions/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/extensions/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/http/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/http/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/manifest.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/manifest_schema.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/notify/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/notify/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/icnli_integrity_rules.txt +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/kernel_formatting_rule.txt +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/prompts/kernel_proactivity_rule.txt +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/protocols.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/rpc/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/rpc/codec.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/rpc/contract.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/executor.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/llm_provider.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/runtime/message_adapter.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/action_result.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/balance_info.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/chat_result.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/completion_result.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/document.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/event.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/file_info.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/function_call.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/http_response.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/limits_result.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/schemas/subscription_info.schema.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/exceptions.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/panel_handler.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/secrets/spec.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/security/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/security/call_token.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/skeleton/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/skeleton/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/storage/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/storage/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/store/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/store/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/store/exceptions.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/testing/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/testing/mock_context.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/testing/mock_secrets.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/generate_api_surface.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/tools/validate_identity_contract.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/chat_result.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/client_contracts.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/contracts.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/contributions.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/events.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/health.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/identity.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/models.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/pagination.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/types/store_contracts.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/actions.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/base.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/data.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/display.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/feedback.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/graph.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/input_components.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/interactive.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/layout.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/ui/theme.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/src/imperal_sdk/validator_v1_6_0.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/conftest.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/contracts/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/contracts/test_store_contracts.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/fixtures/openapi/auth-gateway.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/fixtures/openapi/registry.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/fixtures/openapi/sharelock-cases.json +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/rpc/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/rpc/test_codec.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/rpc/test_contract.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/runtime/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/runtime/test_llm_provider_config_store.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/runtime/test_llm_provider_ctx_injection.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/store/__init__.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/store/test_list_users_client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/store/test_query_all_client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_action_result_typed.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_as_user.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_auth.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_billing.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_cache_client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_cache_model.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_call_token.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_extension_deprecation.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_extension_no_llm_router.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_filters.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_function_background_flag.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_guards.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_guards_bleed.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_prompt.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_chat_result.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_cli.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_client_contracts.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_config_client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context_background_task.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context_deliver_chat_message.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_context_guards.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_contracts.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_contracts_live.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_contributions.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_document_contract.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_emits_decorator.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_error_codes.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_errors.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_event_schema_v2.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_events_health.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_extension.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_extension_v2.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_extensions_emit.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_http_timeout_override.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_id_shape_guard.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_identity_contract.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_imperal_schema_v2.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_kernel_primitives.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_no_orchestrator_tool.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_roundtrip_gate.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_schema.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_v2_events.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_v2_other_sections.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_v2_webhooks.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_manifest_validator_v2.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_mock_context.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_models.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_narration_emission.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_narration_guard.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_openai_max_completion_tokens.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_pagination.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_panel_rendering_contract.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_skeleton_decorator.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_spec_validation.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_tools_client.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_fileupload_enhanced.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_html.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_image_enhanced.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_open.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_ui_theme.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_user.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_v7_emit_refusal.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_drift.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_pep563.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_v1_6_0_rules.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_validator_v25.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/test_write_arg_bleed.py +0 -0
- {imperal_sdk-5.0.0 → imperal_sdk-5.0.2}/tests/tools/__init__.py +0 -0
- {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:**
|
|
@@ -27,12 +27,12 @@ _ACTION_WORDS = ("send", "create", "delete", "update", "archive", "reply", "forw
|
|
|
27
27
|
|
|
28
28
|
@dataclass
|
|
29
29
|
class FunctionDef:
|
|
30
|
-
"""
|
|
30
|
+
"""Typed function declaration.
|
|
31
31
|
|
|
32
|
-
The
|
|
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
|
|
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
|
|
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 #
|
|
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 = "" #
|
|
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 #
|
|
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
|
|
91
|
-
"ctx._llm_configs). Will be removed in SDK
|
|
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 = ""
|
|
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
|
|
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
|
-
|
|
111
|
-
# auto-registration REMOVED. ChatExtension
|
|
112
|
-
# bundle declaration —
|
|
113
|
-
# directly via typed dispatch.
|
|
114
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
|
142
|
-
|
|
129
|
+
BaseModel param annotation when omitted (V17 — no ``**kwargs``
|
|
130
|
+
or untyped handlers).
|
|
143
131
|
action_type: ``"read"``, ``"write"``, or ``"destructive"``. Drives
|
|
144
|
-
|
|
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:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
``
|
|
158
|
-
``
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
``
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
resolved_params[field_name] =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
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.
|