From 58986d12a92cbd81ced7f62eb3d1e3e2d93ec403 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 19:21:30 +0300 Subject: [PATCH 1/2] chore(types): regenerate hub types from gateway refactoringV2 8f85b429b (sign-out, route 30) Marketplace: SignOutMarketplaceIntegration / SignOutMarketplaceIntegrationResponse (wasSignedIn, revoke); GetMarketplaceIntegrationAccessResponse.canManage, MarketplaceAccessRoleDto.canChange, MarketplaceAccessToolDto.curated / excludedFromFullAccess, MarketplaceAccessChangesDto.readOnlyHintOverridden / excludedFromFullAccess, MarketplaceFunctionRunDto.integrationId / toolName, GetMarketplaceFunctionRuns.integrationId. Other campaigns' additive Hub wire, carried because the file is regenerated whole: NotificationReadMarkDto; FindRecords / FindOneRecord.allLanguages; IssueServiceUserApiKeyResponse.environment (+ responseStatus), ListServiceUserApiKeysResponse.responseStatus, ServiceUserApiKeyDto.environment; PushDeviceListProjection platform / installationId / appVersion / timeZone / locale / updatedAtUtc; PushCampaignBatchNotificationDto.readBy. Nothing removed. Api types unchanged. Co-Authored-By: Claude Opus 5.5 --- references/hub_dtos.py | 59 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/references/hub_dtos.py b/references/hub_dtos.py index 5bf9a89..a848f7f 100644 --- a/references/hub_dtos.py +++ b/references/hub_dtos.py @@ -1,5 +1,5 @@ """ Options: -Date: 2026-10-13 13:23:39 +Date: 2026-10-13 16:21:29 Version: 10.20 Tip: To override a DTO option, remove "#" prefix before updating BaseUrl: http://localhost:5001 @@ -5071,6 +5071,7 @@ class ServiceUserApiKeyDto: name: Optional[str] = None visible_key: Optional[str] = None scopes: List[str] = field(default_factory=list) + environment: Optional[str] = None created_date: datetime.datetime = datetime.datetime(1, 1, 1) expiry_date: Optional[datetime.datetime] = None cancelled_date: Optional[datetime.datetime] = None @@ -5965,6 +5966,7 @@ class MarketplaceAccessRoleDto: manage: bool = False custom: Optional[IReadOnlyList[MarketplaceAccessCustomPolicyDto]] = None default_not_applied: Optional[str] = None + can_change: bool = False @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -5982,6 +5984,8 @@ class MarketplaceAccessToolDto: name: Optional[str] = None read_only: bool = False grantable: bool = False + curated: bool = False + excluded_from_full_access: bool = False @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -6028,6 +6032,8 @@ class MarketplaceAccessChangesDto: read_only_added: Optional[IReadOnlyList[str]] = None read_only_removed: Optional[IReadOnlyList[str]] = None not_grantable: Optional[IReadOnlyList[str]] = None + read_only_hint_overridden: Optional[IReadOnlyList[str]] = None + excluded_from_full_access: Optional[IReadOnlyList[str]] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -6059,6 +6065,8 @@ class MarketplaceFunctionRunDto: trigger_id: Optional[str] = None task_id: Optional[str] = None input: Optional[str] = None + integration_id: Optional[str] = None + tool_name: Optional[str] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -6142,6 +6150,12 @@ class PushDeviceListProjection: os_name: Optional[str] = None os_version: Optional[str] = None platform_api_level: Optional[int] = None + platform: Optional[str] = None + app_version: Optional[str] = None + installation_id: Optional[str] = None + locale: Optional[str] = None + time_zone: Optional[str] = None + updated_at_utc: Optional[datetime.datetime] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -6185,12 +6199,20 @@ class PushCampaignBatchDto(CampaignBatchDto): recipients: Optional[PushRecipientsDto] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class NotificationReadMarkDto: + user_id: Optional[str] = None + read_at_utc: datetime.datetime = datetime.datetime(1, 1, 1) + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class PushCampaignBatchNotificationDto(CampaignBatchNotificationDto): subject: Optional[str] = None recipients: Optional[PushRecipientsDto] = None content: Optional[PushMessageContentDto] = None + read_by: Optional[List[NotificationReadMarkDto]] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -7496,12 +7518,15 @@ class IssueServiceUserApiKeyResponse: id: int = 0 name: Optional[str] = None key: Optional[str] = None + environment: Optional[str] = None + response_status: Optional[CodeMashResponseStatus] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class ListServiceUserApiKeysResponse: keys: List[ServiceUserApiKeyDto] = field(default_factory=list) + response_status: Optional[CodeMashResponseStatus] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -8131,6 +8156,7 @@ class GetMarketplaceIntegrationAccessResponse(ResponseBase): packages: Optional[IReadOnlyList[MarketplaceAccessPackageDto]] = None tools: Optional[IReadOnlyList[MarketplaceAccessToolDto]] = None functions: Optional[IReadOnlyList[MarketplaceAccessFunctionDto]] = None + can_manage: bool = False @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -8170,6 +8196,13 @@ class StartMarketplaceIntegrationSignInResponse(ResponseBase): expires_at: Optional[datetime.datetime] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SignOutMarketplaceIntegrationResponse(ResponseBase): + was_signed_in: bool = False + revoke: Optional[str] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class EmptyMarketplaceSecretsResponse(ResponseBase): @@ -12858,6 +12891,9 @@ class FindRecords(CodeMashListPaginationRequestBase, IReturn[FindRecordsResponse """ + all_languages: bool = False + + # @Route("/{version}/database/collections/{collectionName}/{id}", "GET") # @Api(Description="Database") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -12889,6 +12925,9 @@ class FindOneRecord(CodeMashRequestBase, IReturn[FindOneRecordResponse]): """ + all_languages: bool = False + + # @Route("/{version}/database/collections/{collectionName}/indexes", "GET") # @Api(Description="Database") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -15550,6 +15589,17 @@ class StartMarketplaceIntegrationSignIn(CodeMashRequestBase, IReturn[StartMarket """ +# @Route("/{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/sign-out", "POST") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SignOutMarketplaceIntegration(CodeMashRequestBase, IReturn[SignOutMarketplaceIntegrationResponse]): + # @ApiMember(Description="Integration view id, from get_marketplace_integrations.", IsRequired=true) + integration_view_id: Optional[str] = None + """ + Integration view id, from get_marketplace_integrations. + """ + + # @Route("/{version}/code/marketplace/integrations/{IntegrationViewId}/secrets", "PUT") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass @@ -15625,6 +15675,13 @@ class GetMarketplaceFunctionRuns(CodeMashRequestBase, IReturn[GetMarketplaceFunc """ + # @ApiMember(Description="The MCP connection whose tool runs to list — the calls an AI chat made to one of its tools without a saved function (int_…). One of functionId / triggerId / taskId / integrationId.") + integration_id: Optional[str] = None + """ + The MCP connection whose tool runs to list — the calls an AI chat made to one of its tools without a saved function (int_…). One of functionId / triggerId / taskId / integrationId. + """ + + # @ApiMember(DataType="integer", Description="How many runs, newest first: 1-50. Default 20.") limit: Optional[int] = None """ From 9da0e9a059dfcd1d6df2b389316ee5d5866ec6bc Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 19:25:00 +0300 Subject: [PATCH 2/2] feat(marketplace): sign_out_marketplace_integration, the OAuth sign-out (route 30) The Code module gains sign_out_marketplace_integration (POST .../integrations/{IntegrationViewId}/oauth/sign-out); sync and async; the marketplace route table gets its case (28 -> 29, run on both clients) and the docs table its row. Co-Authored-By: Claude Opus 5.5 --- docs/hub/code.md | 3 ++- src/norbix_python/hub/code.py | 26 ++++++++++++++++++++++++++ tests/hub/test_code.py | 7 ++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/hub/code.md b/docs/hub/code.md index 0a5d3c7..ee43bf3 100644 --- a/docs/hub/code.md +++ b/docs/hub/code.md @@ -29,7 +29,7 @@ Integration ids look like `int_…`. ## Marketplace -The 28 routes of the gateway contract `Code.Marketplace.md` §14: the catalog +The 29 routes of the gateway contract `Code.Marketplace.md` §14: the catalog of listings, the installed connections (integration ids look like `int_…`), their secrets and token mappings, and the functions added on a connection (`func_…`). Every method, its verb, path, query and body are asserted in @@ -60,6 +60,7 @@ their secrets and token mappings, and the functions added on a connection | `set_marketplace_integration_role_access` | `PUT` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/access/roles` | `project` | path: `integration_view_id`; JSON body: `roleId`, `package` (`None`, `Viewer`, `FunctionsOnly`, `ReadOnly`, `FullAccess`), `manage`; returns the role id | | `save_marketplace_integration_custom_access` | `POST` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/access/roles/custom` | `project` | path: `integration_view_id`; JSON body: `roleId`, `basePackage`, `tools`, `functions`, `manage`, `newPolicyForThisRoleOnly`; returns `policyId`, `created`, `usedByRoles` | | `start_marketplace_integration_sign_in` | `POST` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/start` | `project` | path: `integration_view_id`; returns `authorizeUrl` (open it in a browser), `redirectUrl`, `expiresAt` | +| `sign_out_marketplace_integration` | `POST` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/sign-out` | `project` | path: `integration_view_id`; returns `wasSignedIn`, `revoke` | | `replace_marketplace_integration_secrets` | `PUT` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/secrets` | `project` | path: `integration_view_id`; JSON body: `secrets` | | `reveal_marketplace_integration_secrets` | `POST` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/secrets/reveal` | `project` | path: `integration_view_id` | | `set_marketplace_integration_token_mappings` | `PUT` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/token-mappings` | `project` | path: `integration_view_id`; JSON body: `tokenMappings` | diff --git a/src/norbix_python/hub/code.py b/src/norbix_python/hub/code.py index 9b06b2f..6f3a1e8 100644 --- a/src/norbix_python/hub/code.py +++ b/src/norbix_python/hub/code.py @@ -438,6 +438,19 @@ def start_marketplace_integration_sign_in(self, integration_view_id: str, *, tim bearer_token=bearer_token, ) + def sign_out_marketplace_integration(self, integration_view_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/sign-out — sign an MCP connection out of OAuth; returns `wasSignedIn` and `revoke` (`Revoked`, `NoRevocationEndpoint`, `Failed` or None).""" + return self._transport.send( + target="hub", + path="/{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/sign-out", + method="POST", + path_params={"IntegrationViewId": integration_view_id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + def replace_marketplace_integration_secrets(self, integration_view_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: """PUT /{version}/code/marketplace/integrations/{IntegrationViewId}/secrets""" return self._transport.send( @@ -1002,6 +1015,19 @@ async def start_marketplace_integration_sign_in(self, integration_view_id: str, bearer_token=bearer_token, ) + async def sign_out_marketplace_integration(self, integration_view_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/sign-out — sign an MCP connection out of OAuth; returns `wasSignedIn` and `revoke` (`Revoked`, `NoRevocationEndpoint`, `Failed` or None).""" + return await self._transport.send( + target="hub", + path="/{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/sign-out", + method="POST", + path_params={"IntegrationViewId": integration_view_id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + async def replace_marketplace_integration_secrets(self, integration_view_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: """PUT /{version}/code/marketplace/integrations/{IntegrationViewId}/secrets""" return await self._transport.send( diff --git a/tests/hub/test_code.py b/tests/hub/test_code.py index 6fed20a..efe705a 100644 --- a/tests/hub/test_code.py +++ b/tests/hub/test_code.py @@ -295,6 +295,7 @@ def test_invoke_code_function_sends_the_function_ref_in_the_body() -> None: ("set_marketplace_integration_role_access", "PUT", "/v3/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/access/roles", None, {'roleId': 'acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU', 'package': 'ReadOnly', 'manage': False}, lambda m: m.set_marketplace_integration_role_access("int_6Qp1dW2kRm8NsT4vYc0Hb", roleId='acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU', package='ReadOnly', manage=False)), ("save_marketplace_integration_custom_access", "POST", "/v3/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/access/roles/custom", None, {'roleId': 'acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU', 'basePackage': 'ReadOnly', 'tools': ['create_issue'], 'functions': ['func_4hT2b9QkLm3XcT1yRfGh7a'], 'manage': False, 'newPolicyForThisRoleOnly': True}, lambda m: m.save_marketplace_integration_custom_access("int_6Qp1dW2kRm8NsT4vYc0Hb", roleId='acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU', basePackage='ReadOnly', tools=['create_issue'], functions=['func_4hT2b9QkLm3XcT1yRfGh7a'], manage=False, newPolicyForThisRoleOnly=True)), ("start_marketplace_integration_sign_in", "POST", "/v3/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/oauth/start", None, None, lambda m: m.start_marketplace_integration_sign_in("int_6Qp1dW2kRm8NsT4vYc0Hb")), + ("sign_out_marketplace_integration", "POST", "/v3/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/oauth/sign-out", None, None, lambda m: m.sign_out_marketplace_integration("int_6Qp1dW2kRm8NsT4vYc0Hb")), ("replace_marketplace_integration_secrets", "PUT", "/v3/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/secrets", None, {'secrets': {'botToken': 'xoxb-new'}}, lambda m: m.replace_marketplace_integration_secrets("int_6Qp1dW2kRm8NsT4vYc0Hb", secrets={'botToken': 'xoxb-new'})), ("reveal_marketplace_integration_secrets", "POST", "/v3/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/secrets/reveal", None, None, lambda m: m.reveal_marketplace_integration_secrets("int_6Qp1dW2kRm8NsT4vYc0Hb")), ("set_marketplace_integration_token_mappings", "PUT", "/v3/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/token-mappings", None, {'tokenMappings': [{'key': 'channel', 'value': '#orders', 'resolver': 'Custom'}]}, lambda m: m.set_marketplace_integration_token_mappings("int_6Qp1dW2kRm8NsT4vYc0Hb", tokenMappings=[{'key': 'channel', 'value': '#orders', 'resolver': 'Custom'}])), @@ -355,9 +356,9 @@ async def run() -> None: def test_marketplace_surface_size() -> None: - """28 marketplace routes in the gateway (Code.Marketplace.md §14; 24 = MCP tool catalog, 25–27 = connection access, 28 = OAuth sign-in start; 29, the callback, is not in the metadata). A new one changes this count.""" - assert len(MARKETPLACE_CASES) == 28 - assert len({case[0] for case in MARKETPLACE_CASES}) == 28 + """29 marketplace routes in the gateway (Code.Marketplace.md §14; 24 = MCP tool catalog, 25–27 = connection access, 28 = OAuth sign-in start, 30 = OAuth sign-out; 29, the callback, is not in the metadata). A new one changes this count.""" + assert len(MARKETPLACE_CASES) == 29 + assert len({case[0] for case in MARKETPLACE_CASES}) == 29 client, _ = make_client() for name, *_ in MARKETPLACE_CASES: assert callable(getattr(client.hub.code, name)), name