ctxprotocol 0.18.0__tar.gz → 0.19.1__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.
- ctxprotocol-0.19.1/CHANGELOG.md +17 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/PKG-INFO +1 -1
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/__init__.py +1 -1
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/__init__.py +2 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/resources/query.py +4 -2
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/types.py +26 -2
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/pyproject.toml +1 -1
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/tests/test_query.py +68 -4
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/uv.lock +1 -1
- ctxprotocol-0.18.0/CHANGELOG.md +0 -7
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/.codexignore +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/.gitignore +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/README.md +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/auth/__init__.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/client.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/resources/__init__.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/resources/developer.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/resources/discovery.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/client/resources/tools.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/context/__init__.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/context/hyperliquid.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/context/polymarket.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/context/wallet.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/contrib/__init__.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/contrib/search/__init__.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/contrib/search/core.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/contrib/search/trace.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/contrib/search/types.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/contrib/search/validation.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/handshake/__init__.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/ctxprotocol/py.typed +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/docs/README.md +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/execute_client.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/polymarket_query_trace_validation.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/query_client.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/test_favorites_only.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/test_get_tool.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/two_surfaces_client.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/validation/generate_contributor_search_validation_artifacts.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/validation/named-regression-iran-boots-on-ground.json +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/validation/named-regression-supreme-court-tariffs.json +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/validation/shared-capability-miss-unsupported-venue.json +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/validation/shared-generic-overlap-best-match.json +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/validation/shared-still-ambiguous-shortlist.json +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/server/hummingbot-contributor/README.md +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/server/hummingbot-contributor/env.example +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/server/hummingbot-contributor/requirements.txt +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/server/hummingbot-contributor/server.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/polymarket-query-trace-results-py.json +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/tests/__init__.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/tests/contrib_search_validation_cases.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/tests/test_client.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/tests/test_contrib_search.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/tests/test_discovery.py +0 -0
- {ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/tests/test_tools.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.19.1
|
|
4
|
+
|
|
5
|
+
- Fixed `client.query.start()` returning `400 "Invalid query job request"` when called without explicit `tools`. The request body builder now omits optional fields (notably `tools`) when unset instead of serializing them as JSON `null`; the durable jobs endpoint's Zod schema rejects `null` for optional fields. `client.query.run()` and `client.query.stream()` were not failing (the `/api/v1/query` endpoint tolerates `null`), but are now consistent with the TS SDK and MCP behavior.
|
|
6
|
+
|
|
7
|
+
## 0.19.0
|
|
8
|
+
|
|
9
|
+
- Fixed `client.query.run()`/`client.query.stream()` raising a `ValidationError` on queries that returned a rendered image artifact (e.g. server-rendered chart PNGs). `computedArtifacts` now accepts both `chart` (structured spec + data) and `image` (hosted URL) variants.
|
|
10
|
+
- Added `QueryImageArtifact` (`kind: "image"` with `url`, `alt`, `title`, `content_hash`, `bytes`, `width`, `height`) and made `QueryComputedArtifact` a discriminated union over `kind`.
|
|
11
|
+
- Exported `QueryImageArtifact` from the client entry point alongside `QueryChartArtifact`.
|
|
12
|
+
|
|
13
|
+
## 0.18.0
|
|
14
|
+
|
|
15
|
+
- Added durable async Query jobs with `client.query.start()`, `client.query.get_status()`, and `client.query.poll()`.
|
|
16
|
+
- Exported `QueryJobStartResult`, `QueryJobStatusResult`, and `QueryJobStatus` from the root and client entry points.
|
|
17
|
+
- Kept existing `client.query.run()` and `client.query.stream()` behavior unchanged.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctxprotocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.1
|
|
4
4
|
Summary: Official Python SDK for the Context Protocol - Discover and execute AI tools programmatically
|
|
5
5
|
Project-URL: Homepage, https://ctxprotocol.com
|
|
6
6
|
Project-URL: Documentation, https://docs.ctxprotocol.com
|
|
@@ -44,6 +44,7 @@ from ctxprotocol.client.types import (
|
|
|
44
44
|
QueryChartType,
|
|
45
45
|
QueryChartValueFormat,
|
|
46
46
|
QueryComputedArtifact,
|
|
47
|
+
QueryImageArtifact,
|
|
47
48
|
QueryCost,
|
|
48
49
|
QueryCompletenessRepairEvent,
|
|
49
50
|
QueryDeveloperTrace,
|
|
@@ -133,6 +134,7 @@ __all__ = [
|
|
|
133
134
|
"QueryChartType",
|
|
134
135
|
"QueryChartValueFormat",
|
|
135
136
|
"QueryComputedArtifact",
|
|
137
|
+
"QueryImageArtifact",
|
|
136
138
|
"QueryStreamToolStatusEvent",
|
|
137
139
|
"QueryStreamTextDeltaEvent",
|
|
138
140
|
"QueryStreamDeveloperTraceEvent",
|
|
@@ -62,8 +62,9 @@ class Query:
|
|
|
62
62
|
) -> dict[str, Any]:
|
|
63
63
|
request_body: dict[str, Any] = {
|
|
64
64
|
"query": query,
|
|
65
|
-
"tools": tools,
|
|
66
65
|
}
|
|
66
|
+
if tools is not None:
|
|
67
|
+
request_body["tools"] = tools
|
|
67
68
|
if stream is not None:
|
|
68
69
|
request_body["stream"] = stream
|
|
69
70
|
if resume_from is not None:
|
|
@@ -443,9 +444,10 @@ class Query:
|
|
|
443
444
|
"""
|
|
444
445
|
request_body: dict[str, Any] = {
|
|
445
446
|
"query": query,
|
|
446
|
-
"tools": tools,
|
|
447
447
|
"stream": True,
|
|
448
448
|
}
|
|
449
|
+
if tools is not None:
|
|
450
|
+
request_body["tools"] = tools
|
|
449
451
|
if resume_from is not None:
|
|
450
452
|
request_body["resumeFrom"] = (
|
|
451
453
|
resume_from.model_dump(by_alias=True, exclude_none=True)
|
|
@@ -6,7 +6,7 @@ This module contains all Pydantic models and type definitions used by the client
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from typing import Any, Literal, Union
|
|
9
|
+
from typing import Annotated, Any, Literal, Union
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel, Field
|
|
12
12
|
|
|
@@ -929,7 +929,31 @@ class QueryChartArtifact(BaseModel):
|
|
|
929
929
|
title: str | None = None
|
|
930
930
|
|
|
931
931
|
|
|
932
|
-
|
|
932
|
+
class QueryImageArtifact(BaseModel):
|
|
933
|
+
"""Rendered image artifact (e.g. a server-rendered chart PNG).
|
|
934
|
+
|
|
935
|
+
Emitted by the code interpreter alongside or instead of the structured
|
|
936
|
+
``chart`` spec. ``url`` points at a hosted, already-rendered image so
|
|
937
|
+
consumers that cannot render a chart spec (image-first surfaces such as
|
|
938
|
+
social posting) can attach it directly.
|
|
939
|
+
"""
|
|
940
|
+
|
|
941
|
+
kind: Literal["image"]
|
|
942
|
+
url: str
|
|
943
|
+
alt: str | None = None
|
|
944
|
+
title: str | None = None
|
|
945
|
+
content_hash: str | None = Field(default=None, alias="contentHash")
|
|
946
|
+
bytes: int | None = None
|
|
947
|
+
width: int | None = None
|
|
948
|
+
height: int | None = None
|
|
949
|
+
|
|
950
|
+
model_config = {"populate_by_name": True}
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
QueryComputedArtifact = Annotated[
|
|
954
|
+
Union[QueryChartArtifact, QueryImageArtifact],
|
|
955
|
+
Field(discriminator="kind"),
|
|
956
|
+
]
|
|
933
957
|
|
|
934
958
|
|
|
935
959
|
class QueryToolCallFailureSample(BaseModel):
|
|
@@ -420,7 +420,6 @@ class TestQueryRun:
|
|
|
420
420
|
method="POST",
|
|
421
421
|
json_body={
|
|
422
422
|
"query": "What are the top whale movements?",
|
|
423
|
-
"tools": None,
|
|
424
423
|
"stream": True,
|
|
425
424
|
},
|
|
426
425
|
extra_headers=None,
|
|
@@ -480,7 +479,6 @@ class TestQueryRun:
|
|
|
480
479
|
method="POST",
|
|
481
480
|
json_body={
|
|
482
481
|
"query": "Analyze whale activity",
|
|
483
|
-
"tools": None,
|
|
484
482
|
"stream": True,
|
|
485
483
|
"agentModelId": "kimi-k2.6-model",
|
|
486
484
|
"responseShape": "answer_with_evidence",
|
|
@@ -897,6 +895,41 @@ class TestQueryRun:
|
|
|
897
895
|
assert mixed_axis.spec.series[1].y_axis == "right"
|
|
898
896
|
assert mixed_axis.spec.series[1].satisfies == "volume"
|
|
899
897
|
|
|
898
|
+
async def test_parses_rendered_image_artifact(self) -> None:
|
|
899
|
+
"""Rendered image artifacts (kind: image) deserialize without error."""
|
|
900
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
901
|
+
response = {
|
|
902
|
+
**MOCK_SUCCESS_RESPONSE,
|
|
903
|
+
"computedArtifacts": [
|
|
904
|
+
{
|
|
905
|
+
"kind": "image",
|
|
906
|
+
"url": "https://blob.example.com/charts/abc123.png",
|
|
907
|
+
"alt": "Sector ETF YTD normalized performance chart",
|
|
908
|
+
"title": "Sector ETF YTD Performance",
|
|
909
|
+
"contentHash": "abc123",
|
|
910
|
+
"bytes": 320749,
|
|
911
|
+
"width": 1926,
|
|
912
|
+
"height": 1030,
|
|
913
|
+
},
|
|
914
|
+
],
|
|
915
|
+
}
|
|
916
|
+
with patch.object(
|
|
917
|
+
client, "fetch_stream", new_callable=AsyncMock
|
|
918
|
+
) as mock_stream:
|
|
919
|
+
mock_stream.return_value = _make_done_stream_response(response)
|
|
920
|
+
result = await client.query.run("Render a chart image")
|
|
921
|
+
|
|
922
|
+
assert result.computed_artifacts is not None
|
|
923
|
+
assert len(result.computed_artifacts) == 1
|
|
924
|
+
image = result.computed_artifacts[0]
|
|
925
|
+
assert image.kind == "image"
|
|
926
|
+
assert image.url == "https://blob.example.com/charts/abc123.png"
|
|
927
|
+
assert image.title == "Sector ETF YTD Performance"
|
|
928
|
+
assert image.content_hash == "abc123"
|
|
929
|
+
assert image.bytes == 320749
|
|
930
|
+
assert image.width == 1926
|
|
931
|
+
assert image.height == 1030
|
|
932
|
+
|
|
900
933
|
async def test_parses_shared_ungrounded_capability_miss_fixture(self) -> None:
|
|
901
934
|
"""Ungrounded runtime outcomes deserialize as capability_miss."""
|
|
902
935
|
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
@@ -1056,7 +1089,6 @@ class TestQueryStream:
|
|
|
1056
1089
|
method="POST",
|
|
1057
1090
|
json_body={
|
|
1058
1091
|
"query": "What are whale movements?",
|
|
1059
|
-
"tools": None,
|
|
1060
1092
|
"stream": True,
|
|
1061
1093
|
},
|
|
1062
1094
|
extra_headers=None,
|
|
@@ -1499,7 +1531,6 @@ class TestQueryJobs:
|
|
|
1499
1531
|
method="POST",
|
|
1500
1532
|
json_body={
|
|
1501
1533
|
"query": "long query",
|
|
1502
|
-
"tools": None,
|
|
1503
1534
|
"stream": False,
|
|
1504
1535
|
"responseShape": "evidence_only",
|
|
1505
1536
|
"includeDataUrl": True,
|
|
@@ -1509,6 +1540,39 @@ class TestQueryJobs:
|
|
|
1509
1540
|
},
|
|
1510
1541
|
)
|
|
1511
1542
|
|
|
1543
|
+
async def test_start_omits_null_optional_fields(self) -> None:
|
|
1544
|
+
"""Regression: optional fields must be absent (not JSON null) in the job body.
|
|
1545
|
+
|
|
1546
|
+
The /api/v1/query/jobs endpoint validates the body with a Zod schema whose
|
|
1547
|
+
optional fields accept `undefined`/absent but reject `null`. Sending
|
|
1548
|
+
`"tools": null` (as the SDK previously did) made query.start() 400 with
|
|
1549
|
+
"Invalid query job request". Optional fields must be omitted when unset,
|
|
1550
|
+
matching the TS SDK (JSON.stringify drops undefined keys).
|
|
1551
|
+
"""
|
|
1552
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1553
|
+
payload = {
|
|
1554
|
+
"status": "running",
|
|
1555
|
+
"jobId": "11111111-1111-4111-8111-111111111111",
|
|
1556
|
+
"pollingTool": "context_query_status",
|
|
1557
|
+
"message": "running",
|
|
1558
|
+
"progress": None,
|
|
1559
|
+
"querySession": None,
|
|
1560
|
+
"createdAt": "2026-06-14T00:00:00.000Z",
|
|
1561
|
+
"updatedAt": "2026-06-14T00:00:00.000Z",
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
with patch.object(client, "fetch", new_callable=AsyncMock) as mock_fetch:
|
|
1565
|
+
mock_fetch.return_value = payload
|
|
1566
|
+
await client.query.start(query="long query")
|
|
1567
|
+
|
|
1568
|
+
body = mock_fetch.call_args.kwargs["json_body"]
|
|
1569
|
+
# No optional field may serialize to JSON null.
|
|
1570
|
+
assert all(value is not None for value in body.values()), body
|
|
1571
|
+
# tools specifically must be omitted when not supplied (was the 400 trigger).
|
|
1572
|
+
assert "tools" not in body, body
|
|
1573
|
+
# query is always present and non-null.
|
|
1574
|
+
assert body["query"] == "long query"
|
|
1575
|
+
|
|
1512
1576
|
async def test_get_status_fetches_query_job(self) -> None:
|
|
1513
1577
|
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1514
1578
|
payload = {
|
ctxprotocol-0.18.0/CHANGELOG.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.18.0
|
|
4
|
-
|
|
5
|
-
- Added durable async Query jobs with `client.query.start()`, `client.query.get_status()`, and `client.query.poll()`.
|
|
6
|
-
- Exported `QueryJobStartResult`, `QueryJobStatusResult`, and `QueryJobStatus` from the root and client entry points.
|
|
7
|
-
- Kept existing `client.query.run()` and `client.query.stream()` behavior unchanged.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/client/polymarket_query_trace_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/server/hummingbot-contributor/env.example
RENAMED
|
File without changes
|
{ctxprotocol-0.18.0 → ctxprotocol-0.19.1}/examples/server/hummingbot-contributor/requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|