imperal-sdk 5.2.2__tar.gz → 5.3.0__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.2.2 → imperal_sdk-5.3.0}/CHANGELOG.md +12 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/PKG-INFO +1 -1
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/api_surface.json +7 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/sdk_claims.json +11 -1
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/__init__.py +1 -1
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/billing/client.py +94 -2
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/context.py +7 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/devtools/generate_sdk_claims.py +3 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/models.py +53 -0
- imperal_sdk-5.3.0/tests/contract/test_artifacts_freshness.py +101 -0
- imperal_sdk-5.3.0/tests/fixtures/contract/kernel-contract.sample.json +146 -0
- imperal_sdk-5.3.0/tests/test_billing.py +73 -0
- imperal_sdk-5.2.2/tests/fixtures/contract/kernel-contract.sample.json +0 -28
- imperal_sdk-5.2.2/tests/test_billing.py +0 -33
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/.github/workflows/identity-contract.yml +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/.github/workflows/publish.yml +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/.github/workflows/test.yml +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/.gitignore +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/LICENSE +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/README.md +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/docs/sdl-facets.md +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/pyproject.toml +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/sdl_roles.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/.codebase-index-cache.pkl +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ai/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ai/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/auth/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/auth/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/auth/middleware.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/billing/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/cache/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/cache/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/cache/protocol.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/action_result.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/error_codes.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/exceptions.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/extension.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/filters.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/guards.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/kernel_primitives.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/narration.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/narration_guard.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/prompt.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/chat/refusal.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/cli/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/cli/main.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/config/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/config/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/devtools/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/devtools/contract_checks.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/devtools/generate_api_surface.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/devtools/validate_identity_contract.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/errors.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/extension.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/extensions/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/extensions/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/http/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/http/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/manifest.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/manifest_schema.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/notify/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/notify/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/prompts/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/prompts/icnli_integrity_rules.txt +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/prompts/kernel_formatting_rule.txt +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/prompts/kernel_proactivity_rule.txt +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/protocols.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/rpc/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/rpc/codec.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/rpc/contract.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/runtime/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/runtime/executor.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/runtime/llm_provider.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/runtime/message_adapter.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/action_result.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/balance_info.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/chat_result.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/completion_result.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/document.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/event.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/file_info.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/function_call.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/http_response.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/imperal.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/limits_result.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/schemas/subscription_info.schema.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/_generate_roles_json.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/entity.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/catalog.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/comm.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/content.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/device.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/event.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/geo.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/identity.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/media.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/metric.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/money.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/net.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/people.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/quantity.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/rating.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/security.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/task.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/facets/time.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/field.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/sdl/roles.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/secrets/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/secrets/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/secrets/exceptions.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/secrets/panel_handler.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/secrets/spec.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/security/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/security/call_token.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/skeleton/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/skeleton/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/storage/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/storage/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/store/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/store/client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/store/exceptions.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/testing/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/testing/mock_context.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/testing/mock_secrets.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/action_result.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/chat_result.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/client_contracts.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/contracts.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/contributions.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/events.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/health.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/identity.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/pagination.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/types/store_contracts.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/actions.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/base.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/data.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/display.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/feedback.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/graph.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/input_components.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/interactive.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/layout.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/ui/theme.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/validator.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/src/imperal_sdk/validator_v1_6_0.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/conftest.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/contract/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/contract/test_contract_checks_selftest.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/contract/test_generate_sdk_claims.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/contract/test_sample_contract_shape.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/contract/test_sdk_matches_kernel_contract.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/contracts/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/contracts/test_store_contracts.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/fixtures/openapi/auth-gateway.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/fixtures/openapi/registry.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/fixtures/openapi/sharelock-cases.json +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/rpc/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/rpc/test_codec.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/rpc/test_contract.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/runtime/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/runtime/test_llm_provider_config_store.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/runtime/test_llm_provider_ctx_injection.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/store/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/store/test_list_users_client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/store/test_query_all_client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_action_result_typed.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_as_user.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_auth.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_cache_client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_cache_model.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_call_token.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_extension_deprecation.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_extension_no_llm_router.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_filters.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_function_background_flag.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_guards.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_guards_bleed.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_prompt.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_chat_result.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_cli.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_client_contracts.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_config_client.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_context.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_context_background_task.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_context_deliver_chat_message.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_context_guards.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_contracts.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_contracts_live.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_contributions.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_data_model_kwarg.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_document_contract.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_emits_decorator.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_error_codes.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_errors.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_event_schema_v2.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_events_health.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_extension.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_extension_v2.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_extensions_emit.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_http_timeout_override.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_id_shape_guard.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_identity_contract.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_imperal_schema_v2.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_import_light.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_kernel_primitives.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest_no_orchestrator_tool.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest_roundtrip_gate.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest_schema.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest_v2_events.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest_v2_other_sections.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest_v2_webhooks.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_manifest_validator_v2.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_mock_context.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_models.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_narration_emission.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_narration_guard.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_openai_max_completion_tokens.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_pagination.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_panel_rendering_contract.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_panels.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_phase_a_dead_removal.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_phase_a_drift.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_phase_a_text.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdk_version_stamp.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_entity.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_entity_marker.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_exports.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_catalog.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_collisions.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_comm.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_content.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_device.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_event.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_exports.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_field.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_geo.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_identity.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_media.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_metric.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_money.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_net.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_people.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_quantity.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_rating.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_security.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_task.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facet_time.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facets_catalog.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facets_doc.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_facets_pkg.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_field.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_roles.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_roles_json.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_sdl_roles_of_facets.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_skeleton_decorator.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_spec_validation.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_ui.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_ui_fileupload_enhanced.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_ui_html.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_ui_image_enhanced.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_ui_open.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_ui_theme.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_user.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_v7_emit_refusal.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_validator.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_validator_drift.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_validator_pep563.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_validator_v1_6_0_rules.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_validator_v25.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/test_write_arg_bleed.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/tools/__init__.py +0 -0
- {imperal_sdk-5.2.2 → imperal_sdk-5.3.0}/tests/tools/test_generate_api_surface.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `imperal-sdk` are documented here.
|
|
4
4
|
|
|
5
|
+
## 5.3.0 — 2026-06-16 — BillingClient write/payment methods
|
|
6
|
+
|
|
7
|
+
Additive — **nothing to migrate**.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `ctx.billing` write/payment methods: `list_payment_methods`, `list_payments`,
|
|
11
|
+
`create_setup_intent`, `set_default_payment_method`, `remove_payment_method`,
|
|
12
|
+
`change_plan`, `topup`. Reads degrade safely; writes surface errors so the
|
|
13
|
+
caller can render Stripe failures / drive the Payment Element.
|
|
14
|
+
- `BillingClient` now sends `X-Acting-User` on the service-token path so
|
|
15
|
+
`get_user_or_service` gateway endpoints resolve the acting user.
|
|
16
|
+
|
|
5
17
|
## 5.2.2 — 2026-06-11 — Import-light package root
|
|
6
18
|
|
|
7
19
|
Performance / robustness release. **Zero API changes** — every public name,
|
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
"complete"
|
|
4
4
|
],
|
|
5
5
|
"billing": [
|
|
6
|
+
"change_plan",
|
|
6
7
|
"check_limits",
|
|
8
|
+
"create_setup_intent",
|
|
7
9
|
"get_balance",
|
|
8
10
|
"get_subscription",
|
|
11
|
+
"list_payment_methods",
|
|
12
|
+
"list_payments",
|
|
13
|
+
"remove_payment_method",
|
|
14
|
+
"set_default_payment_method",
|
|
15
|
+
"topup",
|
|
9
16
|
"track_usage"
|
|
10
17
|
],
|
|
11
18
|
"config": [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_sdk_version": "5.
|
|
2
|
+
"_sdk_version": "5.3.0",
|
|
3
3
|
"constants": {
|
|
4
4
|
"max_call_depth": {
|
|
5
5
|
"counts_root": true,
|
|
@@ -17,11 +17,21 @@
|
|
|
17
17
|
"long_running": "advisory"
|
|
18
18
|
},
|
|
19
19
|
"http_payloads": {
|
|
20
|
+
"POST /v1/billing/change-plan": [
|
|
21
|
+
"plan_id",
|
|
22
|
+
"period"
|
|
23
|
+
],
|
|
20
24
|
"POST /v1/billing/internal/usage/track": [
|
|
21
25
|
"meter",
|
|
22
26
|
"quantity",
|
|
23
27
|
"user_id",
|
|
24
28
|
"tenant_id"
|
|
29
|
+
],
|
|
30
|
+
"POST /v1/billing/payment-methods/setup": [],
|
|
31
|
+
"POST /v1/billing/topup": [
|
|
32
|
+
"tokens",
|
|
33
|
+
"price_cents",
|
|
34
|
+
"save_payment_method"
|
|
25
35
|
]
|
|
26
36
|
}
|
|
27
37
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Imperal Cloud SDK — build extensions for the Imperal platform."""
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
|
-
__version__ = "5.
|
|
4
|
+
__version__ = "5.3.0"
|
|
5
5
|
|
|
6
6
|
# 5.2.2 (2026-06-11): the package root resolves its public surface lazily
|
|
7
7
|
# (PEP 562). The eager imports pulled the HTTP transport (Context / client
|
|
@@ -6,7 +6,10 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
import httpx
|
|
8
8
|
|
|
9
|
-
from imperal_sdk.types.models import
|
|
9
|
+
from imperal_sdk.types.models import (
|
|
10
|
+
BalanceInfo, PaymentMethod, SetupIntentResult, ChangePlanResult,
|
|
11
|
+
TopupResult, PaymentRecord,
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
log = logging.getLogger(__name__)
|
|
12
15
|
|
|
@@ -53,7 +56,10 @@ class BillingClient:
|
|
|
53
56
|
|
|
54
57
|
def _headers(self) -> dict:
|
|
55
58
|
if self._service_token:
|
|
56
|
-
|
|
59
|
+
h = {"X-Service-Token": self._service_token}
|
|
60
|
+
if self._user_id:
|
|
61
|
+
h["X-Acting-User"] = self._user_id
|
|
62
|
+
return h
|
|
57
63
|
return {"Authorization": f"Bearer {self._auth_token}"}
|
|
58
64
|
|
|
59
65
|
def _uid(self, user: Any = None) -> str:
|
|
@@ -160,3 +166,89 @@ class BillingClient:
|
|
|
160
166
|
except Exception as e:
|
|
161
167
|
log.warning("Billing get_balance failed: %s", e)
|
|
162
168
|
return BalanceInfo(balance=0, plan="unknown", cap=0)
|
|
169
|
+
|
|
170
|
+
# ─── Payment methods + plan changes + top-up + payment history ──────── #
|
|
171
|
+
|
|
172
|
+
async def list_payment_methods(self, user: Any = None) -> list[PaymentMethod]:
|
|
173
|
+
uid = self._uid(user)
|
|
174
|
+
try:
|
|
175
|
+
async with httpx.AsyncClient() as client:
|
|
176
|
+
url = (f"{self._gateway_url}/v1/billing/internal/payment-methods/{uid}"
|
|
177
|
+
if (self._service_token and uid)
|
|
178
|
+
else f"{self._gateway_url}/v1/billing/payment-methods")
|
|
179
|
+
resp = await client.get(url, headers=self._headers(), timeout=10)
|
|
180
|
+
resp.raise_for_status()
|
|
181
|
+
return [PaymentMethod(**m) for m in resp.json()]
|
|
182
|
+
except Exception as e:
|
|
183
|
+
log.warning("Billing list_payment_methods failed: %s", e)
|
|
184
|
+
return []
|
|
185
|
+
|
|
186
|
+
async def list_payments(self, user: Any = None, limit: int = 50, offset: int = 0) -> list[PaymentRecord]:
|
|
187
|
+
uid = self._uid(user)
|
|
188
|
+
try:
|
|
189
|
+
async with httpx.AsyncClient() as client:
|
|
190
|
+
if self._service_token and uid:
|
|
191
|
+
url = f"{self._gateway_url}/v1/billing/internal/payments/{uid}"
|
|
192
|
+
else:
|
|
193
|
+
url = f"{self._gateway_url}/v1/billing/payments"
|
|
194
|
+
resp = await client.get(url, headers=self._headers(),
|
|
195
|
+
params={"limit": limit, "offset": offset}, timeout=15)
|
|
196
|
+
resp.raise_for_status()
|
|
197
|
+
return [PaymentRecord(**p) for p in resp.json()]
|
|
198
|
+
except Exception as e:
|
|
199
|
+
log.warning("Billing list_payments failed: %s", e)
|
|
200
|
+
return []
|
|
201
|
+
|
|
202
|
+
async def create_setup_intent(self, user: Any = None) -> SetupIntentResult:
|
|
203
|
+
"""Add-card SetupIntent. Surfaces errors (the ext needs the client secret)."""
|
|
204
|
+
async with httpx.AsyncClient() as client:
|
|
205
|
+
resp = await client.post(f"{self._gateway_url}/v1/billing/payment-methods/setup",
|
|
206
|
+
headers=self._headers(), timeout=10)
|
|
207
|
+
resp.raise_for_status()
|
|
208
|
+
d = resp.json()
|
|
209
|
+
return SetupIntentResult(client_secret=d.get("client_secret", ""),
|
|
210
|
+
publishable_key=d.get("publishable_key", ""))
|
|
211
|
+
|
|
212
|
+
async def set_default_payment_method(self, pm_id: str, user: Any = None) -> bool:
|
|
213
|
+
async with httpx.AsyncClient() as client:
|
|
214
|
+
resp = await client.put(f"{self._gateway_url}/v1/billing/payment-methods/{pm_id}/default",
|
|
215
|
+
headers=self._headers(), timeout=10)
|
|
216
|
+
resp.raise_for_status()
|
|
217
|
+
return True
|
|
218
|
+
|
|
219
|
+
async def remove_payment_method(self, pm_id: str, user: Any = None) -> bool:
|
|
220
|
+
async with httpx.AsyncClient() as client:
|
|
221
|
+
resp = await client.delete(f"{self._gateway_url}/v1/billing/payment-methods/{pm_id}",
|
|
222
|
+
headers=self._headers(), timeout=10)
|
|
223
|
+
resp.raise_for_status()
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
async def change_plan(self, plan_id: str, period: str = "monthly", user: Any = None) -> ChangePlanResult:
|
|
227
|
+
"""Upgrade (prorated, immediate) / downgrade (scheduled). Surfaces errors."""
|
|
228
|
+
async with httpx.AsyncClient() as client:
|
|
229
|
+
resp = await client.post(f"{self._gateway_url}/v1/billing/change-plan",
|
|
230
|
+
json={"plan_id": plan_id, "period": period},
|
|
231
|
+
headers=self._headers(), timeout=15)
|
|
232
|
+
resp.raise_for_status()
|
|
233
|
+
d = resp.json()
|
|
234
|
+
return ChangePlanResult(
|
|
235
|
+
action=d.get("action", ""), plan=d.get("plan", ""),
|
|
236
|
+
succeeded=bool(d.get("succeeded", False)),
|
|
237
|
+
requires_action=bool(d.get("requires_action", False)),
|
|
238
|
+
client_secret=d.get("client_secret", ""),
|
|
239
|
+
effective_at=d.get("effective_at", "") or "",
|
|
240
|
+
pending=bool(d.get("pending", False)))
|
|
241
|
+
|
|
242
|
+
async def topup(self, tokens: int, price_cents: int, save_payment_method: bool = True,
|
|
243
|
+
user: Any = None) -> TopupResult:
|
|
244
|
+
"""Token top-up PaymentIntent. Surfaces errors."""
|
|
245
|
+
async with httpx.AsyncClient() as client:
|
|
246
|
+
resp = await client.post(f"{self._gateway_url}/v1/billing/topup",
|
|
247
|
+
json={"tokens": tokens, "price_cents": price_cents,
|
|
248
|
+
"save_payment_method": save_payment_method},
|
|
249
|
+
headers=self._headers(), timeout=15)
|
|
250
|
+
resp.raise_for_status()
|
|
251
|
+
d = resp.json()
|
|
252
|
+
return TopupResult(client_secret=d.get("client_secret", ""),
|
|
253
|
+
payment_intent_id=d.get("payment_intent_id", ""),
|
|
254
|
+
publishable_key=d.get("publishable_key", ""))
|
|
@@ -50,6 +50,13 @@ class BillingProtocol(Protocol):
|
|
|
50
50
|
async def get_subscription(self) -> SubscriptionInfo: ...
|
|
51
51
|
async def track_usage(self, meter: str, quantity: int = 1, user: Any = None) -> bool: ...
|
|
52
52
|
async def get_balance(self) -> BalanceInfo: ...
|
|
53
|
+
async def list_payment_methods(self, user: Any = None) -> list: ...
|
|
54
|
+
async def list_payments(self, user: Any = None, limit: int = 50, offset: int = 0) -> list: ...
|
|
55
|
+
async def create_setup_intent(self, user: Any = None): ...
|
|
56
|
+
async def set_default_payment_method(self, pm_id: str, user: Any = None) -> bool: ...
|
|
57
|
+
async def remove_payment_method(self, pm_id: str, user: Any = None) -> bool: ...
|
|
58
|
+
async def change_plan(self, plan_id: str, period: str = "monthly", user: Any = None): ...
|
|
59
|
+
async def topup(self, tokens: int, price_cents: int, save_payment_method: bool = True, user: Any = None): ...
|
|
53
60
|
|
|
54
61
|
|
|
55
62
|
@runtime_checkable
|
|
@@ -35,6 +35,9 @@ _DECORATOR_ROLES = {
|
|
|
35
35
|
# stale 'amount' field must never reappear here.
|
|
36
36
|
_HTTP_PAYLOADS: dict[str, list[str]] = {
|
|
37
37
|
"POST /v1/billing/internal/usage/track": ["meter", "quantity", "user_id", "tenant_id"],
|
|
38
|
+
"POST /v1/billing/change-plan": ["plan_id", "period"],
|
|
39
|
+
"POST /v1/billing/topup": ["tokens", "price_cents", "save_payment_method"],
|
|
40
|
+
"POST /v1/billing/payment-methods/setup": [],
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
|
|
@@ -123,3 +123,56 @@ class HTTPStatusError(Exception):
|
|
|
123
123
|
self.status_code = status_code
|
|
124
124
|
self.body = body or ""
|
|
125
125
|
super().__init__(f"HTTP {status_code}: {str(body)[:200]}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class PaymentMethod:
|
|
130
|
+
"""Result item from ctx.billing.list_payment_methods()."""
|
|
131
|
+
id: str = ""
|
|
132
|
+
type: str = "card"
|
|
133
|
+
brand: str = ""
|
|
134
|
+
last4: str = ""
|
|
135
|
+
exp_month: int = 0
|
|
136
|
+
exp_year: int = 0
|
|
137
|
+
is_default: bool = False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class SetupIntentResult:
|
|
142
|
+
"""Result from ctx.billing.create_setup_intent() — drives a Stripe add-card flow."""
|
|
143
|
+
client_secret: str = ""
|
|
144
|
+
publishable_key: str = ""
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class ChangePlanResult:
|
|
149
|
+
"""Result from ctx.billing.change_plan()."""
|
|
150
|
+
action: str = ""
|
|
151
|
+
plan: str = ""
|
|
152
|
+
succeeded: bool = False
|
|
153
|
+
requires_action: bool = False
|
|
154
|
+
client_secret: str = ""
|
|
155
|
+
effective_at: str = ""
|
|
156
|
+
pending: bool = False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class TopupResult:
|
|
161
|
+
"""Result from ctx.billing.topup() — drives a Stripe Payment Element."""
|
|
162
|
+
client_secret: str = ""
|
|
163
|
+
payment_intent_id: str = ""
|
|
164
|
+
publishable_key: str = ""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class PaymentRecord:
|
|
169
|
+
"""Result item from ctx.billing.list_payments()."""
|
|
170
|
+
payment_intent_id: str = ""
|
|
171
|
+
amount_cents: int = 0
|
|
172
|
+
currency: str = "usd"
|
|
173
|
+
tokens: int = 0
|
|
174
|
+
status: str = ""
|
|
175
|
+
type: str = ""
|
|
176
|
+
created_at: str | None = None
|
|
177
|
+
completed_at: str | None = None
|
|
178
|
+
receipt_url: str = ""
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Conformance A8/A9 — committed contract artifacts must match the live SDK.
|
|
2
|
+
|
|
3
|
+
A8 (audit 2026-06-04, re-validated 2026-06-12): ``sdk_claims.json`` sat at
|
|
4
|
+
5.1.0 while the SDK shipped 5.2.0/5.2.1/5.2.2 — the kernel-side
|
|
5
|
+
``contract_guard`` freshness layer keyed on the stale pin and the lag was
|
|
6
|
+
invisible to every local gate. A9: the docs_guard snapshots
|
|
7
|
+
(``api_surface.json`` / ``ctx_surface.json``) had the same hand-copied-rot
|
|
8
|
+
failure mode. These tests run under preflight Edge 1 (``tests/contract/``),
|
|
9
|
+
so a stale committed artifact turns the one sanctioned gate red locally.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import dataclasses
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from imperal_sdk.devtools.generate_api_surface import generate_surface
|
|
20
|
+
from imperal_sdk.devtools.generate_sdk_claims import generate_claims
|
|
21
|
+
|
|
22
|
+
REPO = Path(__file__).resolve().parents[2]
|
|
23
|
+
# imperal-sdk sits inside the MCP-Configs workspace checkout; standalone
|
|
24
|
+
# clones (CI on GitHub) won't have the workspace-level docs_guard inputs.
|
|
25
|
+
WORKSPACE_GUARD_INPUTS = REPO.parent / "scripts" / "docs_guard" / "inputs"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_committed_sdk_claims_match_generated() -> None:
|
|
29
|
+
committed = json.loads((REPO / "sdk_claims.json").read_text(encoding="utf-8"))
|
|
30
|
+
assert committed == generate_claims(), (
|
|
31
|
+
"sdk_claims.json is stale — regenerate with: "
|
|
32
|
+
"python -m imperal_sdk.devtools.generate_sdk_claims --output sdk_claims.json "
|
|
33
|
+
"(and copy to kernel tools/contract/sdk-claims.json on deploy)"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_docs_guard_api_surface_snapshot_fresh() -> None:
|
|
38
|
+
snap = WORKSPACE_GUARD_INPUTS / "api_surface.json"
|
|
39
|
+
if not snap.is_file():
|
|
40
|
+
pytest.skip("workspace docs_guard inputs not present (standalone SDK checkout)")
|
|
41
|
+
committed = json.loads(snap.read_text(encoding="utf-8"))
|
|
42
|
+
live = generate_surface()
|
|
43
|
+
assert {ns: sorted(m) for ns, m in committed.items()} == {
|
|
44
|
+
ns: sorted(m) for ns, m in live.items()
|
|
45
|
+
}, (
|
|
46
|
+
"scripts/docs_guard/inputs/api_surface.json is stale — refresh it from "
|
|
47
|
+
"imperal_sdk.devtools.generate_api_surface (manual-cp rot, conformance A9)"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _live_ctx_surface() -> set[str]:
|
|
52
|
+
"""Public Context surface: dataclass fields + properties + methods.
|
|
53
|
+
|
|
54
|
+
Mirrors the original ctx_surface.json snapshot semantics (everything an
|
|
55
|
+
extension author can legitimately write after ``ctx.`` at the top level,
|
|
56
|
+
excluding kernel-injected attrs like ``secrets`` which are not part of
|
|
57
|
+
the dataclass).
|
|
58
|
+
"""
|
|
59
|
+
from imperal_sdk.context import Context
|
|
60
|
+
|
|
61
|
+
fields = {f.name for f in dataclasses.fields(Context) if not f.name.startswith("_")}
|
|
62
|
+
members = {
|
|
63
|
+
name
|
|
64
|
+
for name, value in vars(Context).items()
|
|
65
|
+
if not name.startswith("_") and (isinstance(value, property) or callable(value))
|
|
66
|
+
}
|
|
67
|
+
return fields | members
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_kernel_contract_copies_mutually_consistent() -> None:
|
|
71
|
+
"""A5: the two local copies of the kernel contract (the SDK contract-test
|
|
72
|
+
fixture and the docs_guard snapshot) must be identical — both are refreshed
|
|
73
|
+
from kernel ``tools/kernel-contract.json`` on a kernel contract change, and
|
|
74
|
+
one-sided rot (e.g. the pre-2026-06-12 state: guard copy carried the old
|
|
75
|
+
max_depth=3 while the fixture carried 6) is exactly how 'ALL GREEN'
|
|
76
|
+
overstates coverage."""
|
|
77
|
+
guard_copy = WORKSPACE_GUARD_INPUTS / "kernel-contract.json"
|
|
78
|
+
if not guard_copy.is_file():
|
|
79
|
+
pytest.skip("workspace docs_guard inputs not present (standalone SDK checkout)")
|
|
80
|
+
fixture = json.loads(
|
|
81
|
+
(REPO / "tests" / "fixtures" / "contract" / "kernel-contract.sample.json").read_text(
|
|
82
|
+
encoding="utf-8"
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
assert fixture == json.loads(guard_copy.read_text(encoding="utf-8")), (
|
|
86
|
+
"kernel-contract fixture and docs_guard snapshot diverged — refresh BOTH "
|
|
87
|
+
"from kernel tools/kernel-contract.json (conformance A5)"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_docs_guard_ctx_surface_snapshot_fresh() -> None:
|
|
92
|
+
snap = WORKSPACE_GUARD_INPUTS / "ctx_surface.json"
|
|
93
|
+
if not snap.is_file():
|
|
94
|
+
pytest.skip("workspace docs_guard inputs not present (standalone SDK checkout)")
|
|
95
|
+
committed = set(json.loads(snap.read_text(encoding="utf-8")))
|
|
96
|
+
live = _live_ctx_surface()
|
|
97
|
+
assert committed == live, (
|
|
98
|
+
f"scripts/docs_guard/inputs/ctx_surface.json is stale (conformance A9). "
|
|
99
|
+
f"missing_from_snapshot={sorted(live - committed)} "
|
|
100
|
+
f"gone_from_sdk={sorted(committed - live)}"
|
|
101
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"advisory_decorator_fields": [
|
|
3
|
+
"effects",
|
|
4
|
+
"background",
|
|
5
|
+
"long_running"
|
|
6
|
+
],
|
|
7
|
+
"constants": {
|
|
8
|
+
"hub_dispatch": {
|
|
9
|
+
"max_depth": {
|
|
10
|
+
"admin_tunable": true,
|
|
11
|
+
"counts_root": false,
|
|
12
|
+
"effective_nested_calls": 6,
|
|
13
|
+
"reject_when": "depth >= value",
|
|
14
|
+
"source": "orchestration/hub_dispatch_handler.py::MAX_DEPTH",
|
|
15
|
+
"value": 6
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"consumed_decorator_fields": [
|
|
20
|
+
"action_type",
|
|
21
|
+
"chain_callable",
|
|
22
|
+
"data_model",
|
|
23
|
+
"event",
|
|
24
|
+
"id_projection"
|
|
25
|
+
],
|
|
26
|
+
"ctx_surface": {
|
|
27
|
+
"ai": [
|
|
28
|
+
"complete"
|
|
29
|
+
],
|
|
30
|
+
"billing": [
|
|
31
|
+
"change_plan",
|
|
32
|
+
"check_limits",
|
|
33
|
+
"create_setup_intent",
|
|
34
|
+
"get_balance",
|
|
35
|
+
"get_subscription",
|
|
36
|
+
"list_payment_methods",
|
|
37
|
+
"list_payments",
|
|
38
|
+
"remove_payment_method",
|
|
39
|
+
"set_default_payment_method",
|
|
40
|
+
"topup",
|
|
41
|
+
"track_usage"
|
|
42
|
+
],
|
|
43
|
+
"config": [
|
|
44
|
+
"all",
|
|
45
|
+
"get",
|
|
46
|
+
"get_section"
|
|
47
|
+
],
|
|
48
|
+
"http": [
|
|
49
|
+
"delete",
|
|
50
|
+
"get",
|
|
51
|
+
"patch",
|
|
52
|
+
"post",
|
|
53
|
+
"put"
|
|
54
|
+
],
|
|
55
|
+
"notify": [
|
|
56
|
+
"send"
|
|
57
|
+
],
|
|
58
|
+
"skeleton": [
|
|
59
|
+
"get"
|
|
60
|
+
],
|
|
61
|
+
"storage": [
|
|
62
|
+
"delete",
|
|
63
|
+
"download",
|
|
64
|
+
"list",
|
|
65
|
+
"upload"
|
|
66
|
+
],
|
|
67
|
+
"store": [
|
|
68
|
+
"count",
|
|
69
|
+
"create",
|
|
70
|
+
"delete",
|
|
71
|
+
"get",
|
|
72
|
+
"list",
|
|
73
|
+
"list_users",
|
|
74
|
+
"query",
|
|
75
|
+
"query_all",
|
|
76
|
+
"set",
|
|
77
|
+
"update"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"emit_time_validation": {
|
|
81
|
+
"action_result_data": false
|
|
82
|
+
},
|
|
83
|
+
"request_models": {
|
|
84
|
+
"POST /v1/billing/change-plan": {
|
|
85
|
+
"fields": {
|
|
86
|
+
"period": {
|
|
87
|
+
"required": false,
|
|
88
|
+
"type": "str"
|
|
89
|
+
},
|
|
90
|
+
"plan_id": {
|
|
91
|
+
"required": true,
|
|
92
|
+
"type": "str"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"source": "gateway_spec (auth-gateway billing/schemas.py::ChangePlanRequest)"
|
|
96
|
+
},
|
|
97
|
+
"POST /v1/billing/internal/usage/track": {
|
|
98
|
+
"fields": {
|
|
99
|
+
"extension_id": {
|
|
100
|
+
"required": false,
|
|
101
|
+
"type": "str | None"
|
|
102
|
+
},
|
|
103
|
+
"meter": {
|
|
104
|
+
"required": true,
|
|
105
|
+
"type": "str"
|
|
106
|
+
},
|
|
107
|
+
"quantity": {
|
|
108
|
+
"required": false,
|
|
109
|
+
"type": "int"
|
|
110
|
+
},
|
|
111
|
+
"tenant_id": {
|
|
112
|
+
"required": true,
|
|
113
|
+
"type": "str"
|
|
114
|
+
},
|
|
115
|
+
"user_id": {
|
|
116
|
+
"required": true,
|
|
117
|
+
"type": "str"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"source": "gateway_spec (auth-gateway billing/schemas.py::UsageTrackRequest)"
|
|
121
|
+
},
|
|
122
|
+
"POST /v1/billing/payment-methods/setup": {
|
|
123
|
+
"fields": {},
|
|
124
|
+
"source": "gateway_spec (auth-gateway billing/stripe_router.py::setup_method)"
|
|
125
|
+
},
|
|
126
|
+
"POST /v1/billing/topup": {
|
|
127
|
+
"fields": {
|
|
128
|
+
"price_cents": {
|
|
129
|
+
"required": true,
|
|
130
|
+
"type": "int"
|
|
131
|
+
},
|
|
132
|
+
"save_payment_method": {
|
|
133
|
+
"required": false,
|
|
134
|
+
"type": "bool"
|
|
135
|
+
},
|
|
136
|
+
"tokens": {
|
|
137
|
+
"required": true,
|
|
138
|
+
"type": "int"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"source": "gateway_spec (auth-gateway billing/stripe_schemas.py::TopUpRequest)"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"sdk_version_floor": "5.0.0",
|
|
145
|
+
"version": "1.0.0"
|
|
146
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright (c) 2026 Imperal, Inc., Valentin Scerbacov, and contributors
|
|
2
|
+
# Licensed under the AGPL-3.0 License. See LICENSE file for details.
|
|
3
|
+
from imperal_sdk.billing.client import LimitsResult, SubscriptionInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_limits_result_not_exceeded():
|
|
7
|
+
limits = LimitsResult(
|
|
8
|
+
plan="free",
|
|
9
|
+
usage={"ai_tokens": 5000},
|
|
10
|
+
limits={"ai_tokens": 10000},
|
|
11
|
+
exceeded=[],
|
|
12
|
+
)
|
|
13
|
+
assert limits.any_exceeded is False
|
|
14
|
+
assert limits.is_exceeded("ai_tokens") is False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_limits_result_exceeded():
|
|
18
|
+
limits = LimitsResult(
|
|
19
|
+
plan="free",
|
|
20
|
+
usage={"ai_tokens": 15000},
|
|
21
|
+
limits={"ai_tokens": 10000},
|
|
22
|
+
exceeded=["ai_tokens"],
|
|
23
|
+
)
|
|
24
|
+
assert limits.any_exceeded is True
|
|
25
|
+
assert limits.is_exceeded("ai_tokens") is True
|
|
26
|
+
assert limits.is_exceeded("tool_calls") is False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_subscription_info():
|
|
30
|
+
sub = SubscriptionInfo(plan="pro", status="active", started_at="2026-01-01T00:00:00")
|
|
31
|
+
assert sub.plan == "pro"
|
|
32
|
+
assert sub.status == "active"
|
|
33
|
+
assert sub.expires_at is None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
import json
|
|
37
|
+
import httpx
|
|
38
|
+
import respx
|
|
39
|
+
from imperal_sdk.billing.client import BillingClient
|
|
40
|
+
|
|
41
|
+
_GW = "http://gw.test"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@respx.mock
|
|
45
|
+
async def test_list_payment_methods_internal_path():
|
|
46
|
+
respx.get(f"{_GW}/v1/billing/internal/payment-methods/imp_u_x").mock(
|
|
47
|
+
return_value=httpx.Response(200, json=[{"id": "pm_1", "type": "card", "brand": "visa",
|
|
48
|
+
"last4": "4242", "exp_month": 12, "exp_year": 2030, "is_default": True}]))
|
|
49
|
+
c = BillingClient(gateway_url=_GW, service_token="svc", user_id="imp_u_x")
|
|
50
|
+
pms = await c.list_payment_methods()
|
|
51
|
+
assert pms[0].last4 == "4242" and pms[0].is_default is True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@respx.mock
|
|
55
|
+
async def test_change_plan_posts_plan_and_period_with_acting_user():
|
|
56
|
+
route = respx.post(f"{_GW}/v1/billing/change-plan").mock(
|
|
57
|
+
return_value=httpx.Response(200, json={"action": "upgrade", "succeeded": True, "plan": "business"}))
|
|
58
|
+
c = BillingClient(gateway_url=_GW, service_token="svc", user_id="imp_u_x")
|
|
59
|
+
res = await c.change_plan("plan_bus", "monthly")
|
|
60
|
+
assert res.action == "upgrade" and res.succeeded is True
|
|
61
|
+
req = route.calls.last.request
|
|
62
|
+
assert req.headers["X-Service-Token"] == "svc"
|
|
63
|
+
assert req.headers["X-Acting-User"] == "imp_u_x"
|
|
64
|
+
assert json.loads(req.content) == {"plan_id": "plan_bus", "period": "monthly"}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@respx.mock
|
|
68
|
+
async def test_topup_returns_client_secret():
|
|
69
|
+
respx.post(f"{_GW}/v1/billing/topup").mock(return_value=httpx.Response(200,
|
|
70
|
+
json={"client_secret": "cs_1", "payment_intent_id": "pi_1", "publishable_key": "pk_1"}))
|
|
71
|
+
c = BillingClient(gateway_url=_GW, service_token="svc", user_id="imp_u_x")
|
|
72
|
+
r = await c.topup(tokens=20000, price_cents=2000)
|
|
73
|
+
assert r.client_secret == "cs_1" and r.payment_intent_id == "pi_1"
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "5.x-sample",
|
|
3
|
-
"request_models": {
|
|
4
|
-
"POST /v1/billing/internal/usage/track": {
|
|
5
|
-
"fields": {
|
|
6
|
-
"user_id": {"type": "str", "required": true},
|
|
7
|
-
"tenant_id": {"type": "str", "required": true},
|
|
8
|
-
"meter": {"type": "str", "required": true},
|
|
9
|
-
"quantity": {"type": "int", "required": false},
|
|
10
|
-
"extension_id": {"type": "str | None", "required": false}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"constants": {
|
|
15
|
-
"hub_dispatch": {
|
|
16
|
-
"max_depth": {"value": 6, "counts_root": false, "effective_nested_calls": 6, "reject_when": "depth >= value"}
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"sdk_version_floor": "5.0.0",
|
|
20
|
-
"consumed_decorator_fields": ["action_type", "chain_callable", "data_model", "event", "id_projection"],
|
|
21
|
-
"ctx_surface": {
|
|
22
|
-
"ai": ["complete"],
|
|
23
|
-
"billing": ["check_limits", "get_balance", "get_subscription", "track_usage"],
|
|
24
|
-
"config": ["all", "get", "get_section"],
|
|
25
|
-
"skeleton": ["get"]
|
|
26
|
-
},
|
|
27
|
-
"emit_time_validation": {"action_result_data": false}
|
|
28
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2026 Imperal, Inc., Valentin Scerbacov, and contributors
|
|
2
|
-
# Licensed under the AGPL-3.0 License. See LICENSE file for details.
|
|
3
|
-
from imperal_sdk.billing.client import LimitsResult, SubscriptionInfo
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_limits_result_not_exceeded():
|
|
7
|
-
limits = LimitsResult(
|
|
8
|
-
plan="free",
|
|
9
|
-
usage={"ai_tokens": 5000},
|
|
10
|
-
limits={"ai_tokens": 10000},
|
|
11
|
-
exceeded=[],
|
|
12
|
-
)
|
|
13
|
-
assert limits.any_exceeded is False
|
|
14
|
-
assert limits.is_exceeded("ai_tokens") is False
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def test_limits_result_exceeded():
|
|
18
|
-
limits = LimitsResult(
|
|
19
|
-
plan="free",
|
|
20
|
-
usage={"ai_tokens": 15000},
|
|
21
|
-
limits={"ai_tokens": 10000},
|
|
22
|
-
exceeded=["ai_tokens"],
|
|
23
|
-
)
|
|
24
|
-
assert limits.any_exceeded is True
|
|
25
|
-
assert limits.is_exceeded("ai_tokens") is True
|
|
26
|
-
assert limits.is_exceeded("tool_calls") is False
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_subscription_info():
|
|
30
|
-
sub = SubscriptionInfo(plan="pro", status="active", started_at="2026-01-01T00:00:00")
|
|
31
|
-
assert sub.plan == "pro"
|
|
32
|
-
assert sub.status == "active"
|
|
33
|
-
assert sub.expires_at is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|