From ea03a53ce533c9b941b321485ebd7ea26190ea4c Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 09:07:09 +0300 Subject: [PATCH 1/2] chore(types): regenerate hub types from gateway refactoringV2 81f87e446 (runs per function) GetMarketplaceFunctionRuns gains functionId (one of functionId / triggerId / taskId); MarketplaceFunctionRunDto gains status, correlationId, hasDetails, triggerId and taskId. Additive only; Api types unchanged. Co-Authored-By: Claude Opus 5.5 --- references/hub_dtos.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/references/hub_dtos.py b/references/hub_dtos.py index 101a46a..4ec2226 100644 --- a/references/hub_dtos.py +++ b/references/hub_dtos.py @@ -1,5 +1,5 @@ """ Options: -Date: 2026-10-12 21:43:18 +Date: 2026-10-13 06:06:37 Version: 10.20 Tip: To override a DTO option, remove "#" prefix before updating BaseUrl: http://localhost:5001 @@ -5965,9 +5965,14 @@ class MarketplaceFunctionRunDto: function_id: Optional[str] = None run_as_user_id: Optional[str] = None succeeded: bool = False + status: Optional[str] = None duration_ms: Optional[int] = None error: Optional[str] = None vendor_request_id: Optional[str] = None + correlation_id: Optional[str] = None + has_details: bool = False + trigger_id: Optional[str] = None + task_id: Optional[str] = None input: Optional[str] = None @@ -15361,17 +15366,24 @@ class GetMarketplaceFunctionCatalog(CodeMashRequestBase, IReturn[GetMarketplaceF @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetMarketplaceFunctionRuns(CodeMashRequestBase, IReturn[GetMarketplaceFunctionRunsResponse]): - # @ApiMember(Description="The trigger whose marketplace function runs to list (trg_…). One of triggerId / taskId.") + # @ApiMember(Description="The marketplace function whose runs to list, from every source (func_…). One of functionId / triggerId / taskId.") + function_id: Optional[str] = None + """ + The marketplace function whose runs to list, from every source (func_…). One of functionId / triggerId / taskId. + """ + + + # @ApiMember(Description="The trigger whose marketplace function runs to list (trg_…). One of functionId / triggerId / taskId.") trigger_id: Optional[str] = None """ - The trigger whose marketplace function runs to list (trg_…). One of triggerId / taskId. + The trigger whose marketplace function runs to list (trg_…). One of functionId / triggerId / taskId. """ - # @ApiMember(Description="The scheduler task whose marketplace function runs to list (tsk_…). One of triggerId / taskId.") + # @ApiMember(Description="The scheduler task whose marketplace function runs to list (tsk_…). One of functionId / triggerId / taskId.") task_id: Optional[str] = None """ - The scheduler task whose marketplace function runs to list (tsk_…). One of triggerId / taskId. + The scheduler task whose marketplace function runs to list (tsk_…). One of functionId / triggerId / taskId. """ From 938da74f6b9bb50484cc24379af8fd50ff86b067 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 09:13:26 +0300 Subject: [PATCH 2/2] feat(marketplace): runs per function (functionId) on get_marketplace_function_runs The regenerated GetMarketplaceFunctionRuns reference carries function_id (one of functionId / triggerId / taskId) and the run row carries status, correlation_id, has_details, trigger_id and task_id. New sync and async tests pin the exact query (functionId, limit) and no body. docs/hub/code.md: the query row names functionId; "Marketplace function runs and their details" shows the two-call pattern (hub.logs.get_logs_by_correlation_id, targetCorrelationId). Co-Authored-By: Claude Opus 5.5 --- docs/hub/code.md | 26 +++++++++++++++++++- tests/hub/test_code.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/docs/hub/code.md b/docs/hub/code.md index e24df63..101b14d 100644 --- a/docs/hub/code.md +++ b/docs/hub/code.md @@ -59,7 +59,7 @@ their secrets and token mappings, and the functions added on a connection | `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` | | `get_marketplace_function_catalog` | `GET` | `/{version}/code/marketplace/integrations/{IntegrationViewId}/catalog` | `project` | path: `integration_view_id` | -| `get_marketplace_function_runs` | `GET` | `/{version}/code/marketplace/functions/runs` | `project` | query: exactly one of `triggerId` / `taskId`, `limit` (1–50, default 20); newest first | +| `get_marketplace_function_runs` | `GET` | `/{version}/code/marketplace/functions/runs` | `project` | query: exactly one of `functionId` / `triggerId` / `taskId`, `limit` (1–50, default 20); newest first | ```python listings = norbix.hub.code.get_marketplace_listings(search="slack") @@ -67,6 +67,30 @@ runs = norbix.hub.code.get_marketplace_function_runs(triggerId="trg_…", limit= result = norbix.hub.code.invoke_marketplace_function("func_…", payload={"text": "Order shipped"}) ``` +### Marketplace function runs and their details + +Each call of a marketplace function is one run row: an invoke (user, API key, +AI), a trigger run or a scheduler fire. `functionId` lists every source of one +function (up to 50 kept); `triggerId` / `taskId` list the runs one trigger or +task started (20 kept). A row carries `status` (`ok` / `failed` / `skipped`, +`None` on rows written before the field existed), `durationMs`, `source` +(`user`, `api-key`, `ai`, `trigger`, `scheduler`), `event`, `correlationId` +and `hasDetails`. + +The input, steps and response of a run are written to the project log under +the row's `correlationId`, only while the environment's Norbix Logging is on — +then `hasDetails` is `True`. Read them with the logs route; its query field is +`targetCorrelationId`: + +```python +runs = norbix.hub.code.get_marketplace_function_runs(functionId="func_…", limit=10) +for run in runs.get("runs", []): + if not run.get("hasDetails"): + continue # logs were off for this run + details = norbix.hub.logs.get_logs_by_correlation_id(targetCorrelationId=run["correlationId"]) + # details["items"]: marketplace:run:started → marketplace:run:step … → completed | failed | skipped +``` + ## Cloud functions The five routes of the gateway contract `Code.Functions.md` (§3.3, §3.4, diff --git a/tests/hub/test_code.py b/tests/hub/test_code.py index b238a72..eb75c37 100644 --- a/tests/hub/test_code.py +++ b/tests/hub/test_code.py @@ -356,3 +356,58 @@ def test_marketplace_surface_size() -> None: client, _ = make_client() for name, *_ in MARKETPLACE_CASES: assert callable(getattr(client.hub.code, name)), name + + +RUNS_PATH = "/v3/code/marketplace/functions/runs" +RUNS_BY_FUNCTION_QUERY = {"functionId": ["func_1p4R3gTobGZpD6fXvHT4rd"], "limit": ["50"]} + + +def test_marketplace_function_runs_by_function_id() -> None: + """Exactly one of functionId / triggerId / taskId (gateway item gw-runs): a function's own runs.""" + client, transport = make_client() + client.hub.code.get_marketplace_function_runs(functionId="func_1p4R3gTobGZpD6fXvHT4rd", limit=50) + assert transport.last_request is not None + _assert_function_request( + "get_marketplace_function_runs(functionId)", + transport.last_request["method"], + transport.last_request["url"], + transport.last_request["body"], + "GET", + RUNS_PATH, + RUNS_BY_FUNCTION_QUERY, + None, + ) + + +def test_async_marketplace_function_runs_by_function_id() -> None: + seen: list[httpx.Request] = [] + + def handler(request: httpx.Request) -> httpx.Response: + seen.append(request) + return httpx.Response(200, json={"runs": [{"functionId": "func_1p4R3gTobGZpD6fXvHT4rd", "status": "ok", "hasDetails": True}]}) + + async def run() -> Any: + client = AsyncNorbix( + project_id="test-project", + bearer_token="test-token", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)), + ) + try: + return await client.hub.code.get_marketplace_function_runs(functionId="func_1p4R3gTobGZpD6fXvHT4rd", limit=50) + finally: + await client.aclose() + + result = asyncio.run(run()) + + assert len(seen) == 1 + _assert_function_request( + "async get_marketplace_function_runs(functionId)", + seen[0].method, + str(seen[0].url), + seen[0].content.decode("utf-8"), + "GET", + RUNS_PATH, + RUNS_BY_FUNCTION_QUERY, + None, + ) + assert result == {"runs": [{"functionId": "func_1p4R3gTobGZpD6fXvHT4rd", "status": "ok", "hasDetails": True}]}