ctxprotocol 0.17.0__tar.gz → 0.19.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.
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/.gitignore +1 -0
- ctxprotocol-0.19.0/CHANGELOG.md +13 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/PKG-INFO +23 -5
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/README.md +22 -4
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/__init__.py +7 -1
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/__init__.py +8 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/resources/query.py +114 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/types.py +73 -21
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/pyproject.toml +1 -1
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/tests/test_query.py +136 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/uv.lock +1 -1
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/.codexignore +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/auth/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/resources/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/resources/developer.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/resources/discovery.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/client/resources/tools.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/context/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/context/hyperliquid.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/context/polymarket.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/context/wallet.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/contrib/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/contrib/search/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/contrib/search/core.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/contrib/search/trace.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/contrib/search/types.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/contrib/search/validation.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/handshake/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/ctxprotocol/py.typed +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/docs/README.md +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/execute_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/polymarket_query_trace_validation.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/query_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/test_favorites_only.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/test_get_tool.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/two_surfaces_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/validation/generate_contributor_search_validation_artifacts.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/validation/named-regression-iran-boots-on-ground.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/validation/named-regression-supreme-court-tariffs.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/validation/shared-capability-miss-unsupported-venue.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/validation/shared-generic-overlap-best-match.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/client/validation/shared-still-ambiguous-shortlist.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/server/hummingbot-contributor/README.md +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/server/hummingbot-contributor/env.example +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/server/hummingbot-contributor/requirements.txt +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/examples/server/hummingbot-contributor/server.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/polymarket-query-trace-results-py.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/tests/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/tests/contrib_search_validation_cases.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/tests/test_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/tests/test_contrib_search.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/tests/test_discovery.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/tests/test_tools.py +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- Added `QueryImageArtifact` (`kind: "image"` with `url`, `alt`, `title`, `content_hash`, `bytes`, `width`, `height`) and made `QueryComputedArtifact` a discriminated union over `kind`.
|
|
7
|
+
- Exported `QueryImageArtifact` from the client entry point alongside `QueryChartArtifact`.
|
|
8
|
+
|
|
9
|
+
## 0.18.0
|
|
10
|
+
|
|
11
|
+
- Added durable async Query jobs with `client.query.start()`, `client.query.get_status()`, and `client.query.poll()`.
|
|
12
|
+
- Exported `QueryJobStartResult`, `QueryJobStatusResult`, and `QueryJobStatus` from the root and client entry points.
|
|
13
|
+
- 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.0
|
|
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
|
|
@@ -149,6 +149,24 @@ print(
|
|
|
149
149
|
print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
For long-running questions, start a durable job and poll until it completes:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
job = await client.query.start(
|
|
156
|
+
query="Build a chart-ready dataset of Base whale movements over the last 30 days",
|
|
157
|
+
response_shape="evidence_only",
|
|
158
|
+
include_data_url=True,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
completed = await client.query.poll(
|
|
162
|
+
job.job_id,
|
|
163
|
+
interval_ms=2000,
|
|
164
|
+
timeout_ms=15 * 60_000,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
print(completed.result.data_url if completed.result else None)
|
|
168
|
+
```
|
|
169
|
+
|
|
152
170
|
To see valid model slugs without guessing:
|
|
153
171
|
|
|
154
172
|
```python
|
|
@@ -166,7 +184,7 @@ print(AGENT_MODEL_IDS) # Supported agent_model_id values
|
|
|
166
184
|
`response_shape` options:
|
|
167
185
|
|
|
168
186
|
- `answer_with_evidence` (default): prose plus `summary`, `evidence`, `artifacts`, `freshness`, `confidence`, `usage`, `outcome`, and `controller`
|
|
169
|
-
- `evidence_only`:
|
|
187
|
+
- `evidence_only`: bounded evidence, computed artifacts, and full-data references for downstream agents (no prose synthesis)
|
|
170
188
|
|
|
171
189
|
Premium wedge answers can also expose `evidence.market_intelligence`, `view.rows`, `view.columns`, and the top-level controller fields `stop_reason`, `issue_class`, and `actions_taken`.
|
|
172
190
|
|
|
@@ -327,7 +345,7 @@ answer = await client.query.run(
|
|
|
327
345
|
query="Analyze whale activity on Base",
|
|
328
346
|
tools=["tool-uuid-1", "tool-uuid-2"], # optional — auto-discover if omitted
|
|
329
347
|
agent_model_id="kimi-k2.6-model", # optional main librarian agent model
|
|
330
|
-
include_data=True, # optional: include execution data inline
|
|
348
|
+
include_data=True, # optional: include bounded execution data inline
|
|
331
349
|
include_data_url=True, # optional: include blob URL for full data
|
|
332
350
|
include_developer_trace=True, # optional: include Developer Mode trace
|
|
333
351
|
)
|
|
@@ -336,7 +354,7 @@ print(answer.response) # response text or summary
|
|
|
336
354
|
print(answer.tools_used) # [QueryToolUsage(id, name, skill_calls)]
|
|
337
355
|
print(answer.cost) # QueryCost(model_cost_usd, tool_cost_usd, total_cost_usd)
|
|
338
356
|
print(answer.duration_ms) # Total time
|
|
339
|
-
print(answer.data) # Optional
|
|
357
|
+
print(answer.data) # Optional bounded data or truncation preview
|
|
340
358
|
print(answer.data_url) # Optional blob URL (when include_data_url=True)
|
|
341
359
|
print(answer.developer_trace.summary if answer.developer_trace else None)
|
|
342
360
|
print(
|
|
@@ -347,7 +365,7 @@ print(
|
|
|
347
365
|
print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
348
366
|
```
|
|
349
367
|
|
|
350
|
-
When retrieval-first synthesis rollout is enabled server-side, full-data or truncation-sensitive query requests can switch to retrieval-first context assembly using private stage artifacts and canonical execution data slices. `include_data` and `include_data_url`
|
|
368
|
+
When retrieval-first synthesis rollout is enabled server-side, full-data or truncation-sensitive query requests can switch to retrieval-first context assembly using private stage artifacts and canonical execution data slices. `include_data` returns a bounded inline preview when needed, and `include_data_url`/`artifacts.canonical_data_ref` reference the same canonical dataset used for synthesis.
|
|
351
369
|
|
|
352
370
|
#### `client.query.stream(query, tools?, agent_model_id?, include_data?, include_data_url?, include_developer_trace?, idempotency_key?)`
|
|
353
371
|
|
|
@@ -111,6 +111,24 @@ print(
|
|
|
111
111
|
print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
For long-running questions, start a durable job and poll until it completes:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
job = await client.query.start(
|
|
118
|
+
query="Build a chart-ready dataset of Base whale movements over the last 30 days",
|
|
119
|
+
response_shape="evidence_only",
|
|
120
|
+
include_data_url=True,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
completed = await client.query.poll(
|
|
124
|
+
job.job_id,
|
|
125
|
+
interval_ms=2000,
|
|
126
|
+
timeout_ms=15 * 60_000,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
print(completed.result.data_url if completed.result else None)
|
|
130
|
+
```
|
|
131
|
+
|
|
114
132
|
To see valid model slugs without guessing:
|
|
115
133
|
|
|
116
134
|
```python
|
|
@@ -128,7 +146,7 @@ print(AGENT_MODEL_IDS) # Supported agent_model_id values
|
|
|
128
146
|
`response_shape` options:
|
|
129
147
|
|
|
130
148
|
- `answer_with_evidence` (default): prose plus `summary`, `evidence`, `artifacts`, `freshness`, `confidence`, `usage`, `outcome`, and `controller`
|
|
131
|
-
- `evidence_only`:
|
|
149
|
+
- `evidence_only`: bounded evidence, computed artifacts, and full-data references for downstream agents (no prose synthesis)
|
|
132
150
|
|
|
133
151
|
Premium wedge answers can also expose `evidence.market_intelligence`, `view.rows`, `view.columns`, and the top-level controller fields `stop_reason`, `issue_class`, and `actions_taken`.
|
|
134
152
|
|
|
@@ -289,7 +307,7 @@ answer = await client.query.run(
|
|
|
289
307
|
query="Analyze whale activity on Base",
|
|
290
308
|
tools=["tool-uuid-1", "tool-uuid-2"], # optional — auto-discover if omitted
|
|
291
309
|
agent_model_id="kimi-k2.6-model", # optional main librarian agent model
|
|
292
|
-
include_data=True, # optional: include execution data inline
|
|
310
|
+
include_data=True, # optional: include bounded execution data inline
|
|
293
311
|
include_data_url=True, # optional: include blob URL for full data
|
|
294
312
|
include_developer_trace=True, # optional: include Developer Mode trace
|
|
295
313
|
)
|
|
@@ -298,7 +316,7 @@ print(answer.response) # response text or summary
|
|
|
298
316
|
print(answer.tools_used) # [QueryToolUsage(id, name, skill_calls)]
|
|
299
317
|
print(answer.cost) # QueryCost(model_cost_usd, tool_cost_usd, total_cost_usd)
|
|
300
318
|
print(answer.duration_ms) # Total time
|
|
301
|
-
print(answer.data) # Optional
|
|
319
|
+
print(answer.data) # Optional bounded data or truncation preview
|
|
302
320
|
print(answer.data_url) # Optional blob URL (when include_data_url=True)
|
|
303
321
|
print(answer.developer_trace.summary if answer.developer_trace else None)
|
|
304
322
|
print(
|
|
@@ -309,7 +327,7 @@ print(
|
|
|
309
327
|
print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
310
328
|
```
|
|
311
329
|
|
|
312
|
-
When retrieval-first synthesis rollout is enabled server-side, full-data or truncation-sensitive query requests can switch to retrieval-first context assembly using private stage artifacts and canonical execution data slices. `include_data` and `include_data_url`
|
|
330
|
+
When retrieval-first synthesis rollout is enabled server-side, full-data or truncation-sensitive query requests can switch to retrieval-first context assembly using private stage artifacts and canonical execution data slices. `include_data` returns a bounded inline preview when needed, and `include_data_url`/`artifacts.canonical_data_ref` reference the same canonical dataset used for synthesis.
|
|
313
331
|
|
|
314
332
|
#### `client.query.stream(query, tools?, agent_model_id?, include_data?, include_data_url?, include_developer_trace?, idempotency_key?)`
|
|
315
333
|
|
|
@@ -31,7 +31,7 @@ Example:
|
|
|
31
31
|
For more information, visit: https://ctxprotocol.com
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
|
-
__version__ = "0.
|
|
34
|
+
__version__ = "0.19.0"
|
|
35
35
|
|
|
36
36
|
# Re-export everything from client module
|
|
37
37
|
from ctxprotocol.client import (
|
|
@@ -70,6 +70,9 @@ from ctxprotocol.client.types import (
|
|
|
70
70
|
QueryDeveloperTraceSummary,
|
|
71
71
|
QueryDeveloperTraceStep,
|
|
72
72
|
QueryDeveloperTraceToolRef,
|
|
73
|
+
QueryJobStartResult,
|
|
74
|
+
QueryJobStatus,
|
|
75
|
+
QueryJobStatusResult,
|
|
73
76
|
QueryOrchestrationMetrics,
|
|
74
77
|
QueryDeveloperTraceLoopInfo,
|
|
75
78
|
QueryStreamDeveloperTraceEvent,
|
|
@@ -195,6 +198,9 @@ __all__ = [
|
|
|
195
198
|
"QueryDeveloperTraceSummary",
|
|
196
199
|
"QueryDeveloperTraceStep",
|
|
197
200
|
"QueryDeveloperTraceToolRef",
|
|
201
|
+
"QueryJobStartResult",
|
|
202
|
+
"QueryJobStatus",
|
|
203
|
+
"QueryJobStatusResult",
|
|
198
204
|
"QueryOrchestrationMetrics",
|
|
199
205
|
"QueryDeveloperTraceLoopInfo",
|
|
200
206
|
"QueryApiSuccessResponse",
|
|
@@ -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,
|
|
@@ -51,6 +52,9 @@ from ctxprotocol.client.types import (
|
|
|
51
52
|
QueryDeveloperTraceSummary,
|
|
52
53
|
QueryDeveloperTraceStep,
|
|
53
54
|
QueryDeveloperTraceToolRef,
|
|
55
|
+
QueryJobStartResult,
|
|
56
|
+
QueryJobStatus,
|
|
57
|
+
QueryJobStatusResult,
|
|
54
58
|
QueryOrchestrationMetrics,
|
|
55
59
|
QueryDeveloperTraceLoopInfo,
|
|
56
60
|
QueryStreamDeveloperTraceEvent,
|
|
@@ -111,6 +115,9 @@ __all__ = [
|
|
|
111
115
|
"QueryDeveloperTraceSummary",
|
|
112
116
|
"QueryDeveloperTraceStep",
|
|
113
117
|
"QueryDeveloperTraceToolRef",
|
|
118
|
+
"QueryJobStartResult",
|
|
119
|
+
"QueryJobStatus",
|
|
120
|
+
"QueryJobStatusResult",
|
|
114
121
|
"QueryOrchestrationMetrics",
|
|
115
122
|
"QueryDeveloperTraceLoopInfo",
|
|
116
123
|
"QueryApiSuccessResponse",
|
|
@@ -127,6 +134,7 @@ __all__ = [
|
|
|
127
134
|
"QueryChartType",
|
|
128
135
|
"QueryChartValueFormat",
|
|
129
136
|
"QueryComputedArtifact",
|
|
137
|
+
"QueryImageArtifact",
|
|
130
138
|
"QueryStreamToolStatusEvent",
|
|
131
139
|
"QueryStreamTextDeltaEvent",
|
|
132
140
|
"QueryStreamDeveloperTraceEvent",
|
|
@@ -9,6 +9,7 @@ execute -> synthesize -> settle) and AI synthesis — all for one flat fee.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import asyncio
|
|
12
13
|
import json
|
|
13
14
|
from typing import TYPE_CHECKING, Any, AsyncGenerator
|
|
14
15
|
|
|
@@ -18,6 +19,8 @@ from ctxprotocol.client.types import (
|
|
|
18
19
|
QueryAttemptReference,
|
|
19
20
|
QueryDeveloperTrace,
|
|
20
21
|
QueryForkReference,
|
|
22
|
+
QueryJobStartResult,
|
|
23
|
+
QueryJobStatusResult,
|
|
21
24
|
QueryResponseShape,
|
|
22
25
|
QueryResult,
|
|
23
26
|
QueryStreamDeveloperTraceEvent,
|
|
@@ -43,6 +46,52 @@ class Query:
|
|
|
43
46
|
"""
|
|
44
47
|
self._client = client
|
|
45
48
|
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _build_request_body(
|
|
51
|
+
query: str,
|
|
52
|
+
tools: list[str] | None = None,
|
|
53
|
+
favorites_only: bool | None = None,
|
|
54
|
+
agent_model_id: AgentModelId | str | None = None,
|
|
55
|
+
response_shape: QueryResponseShape | None = None,
|
|
56
|
+
include_data: bool | None = None,
|
|
57
|
+
include_data_url: bool | None = None,
|
|
58
|
+
include_developer_trace: bool | None = None,
|
|
59
|
+
resume_from: QueryAttemptReference | dict[str, Any] | None = None,
|
|
60
|
+
fork_from: QueryForkReference | dict[str, Any] | None = None,
|
|
61
|
+
stream: bool | None = None,
|
|
62
|
+
) -> dict[str, Any]:
|
|
63
|
+
request_body: dict[str, Any] = {
|
|
64
|
+
"query": query,
|
|
65
|
+
"tools": tools,
|
|
66
|
+
}
|
|
67
|
+
if stream is not None:
|
|
68
|
+
request_body["stream"] = stream
|
|
69
|
+
if resume_from is not None:
|
|
70
|
+
request_body["resumeFrom"] = (
|
|
71
|
+
resume_from.model_dump(by_alias=True, exclude_none=True)
|
|
72
|
+
if isinstance(resume_from, QueryAttemptReference)
|
|
73
|
+
else resume_from
|
|
74
|
+
)
|
|
75
|
+
if fork_from is not None:
|
|
76
|
+
request_body["forkFrom"] = (
|
|
77
|
+
fork_from.model_dump(by_alias=True, exclude_none=True)
|
|
78
|
+
if isinstance(fork_from, QueryForkReference)
|
|
79
|
+
else fork_from
|
|
80
|
+
)
|
|
81
|
+
if favorites_only is not None:
|
|
82
|
+
request_body["favoritesOnly"] = favorites_only
|
|
83
|
+
if agent_model_id is not None:
|
|
84
|
+
request_body["agentModelId"] = agent_model_id
|
|
85
|
+
if response_shape is not None:
|
|
86
|
+
request_body["responseShape"] = response_shape
|
|
87
|
+
if include_data is not None:
|
|
88
|
+
request_body["includeData"] = include_data
|
|
89
|
+
if include_data_url is not None:
|
|
90
|
+
request_body["includeDataUrl"] = include_data_url
|
|
91
|
+
if include_developer_trace is not None:
|
|
92
|
+
request_body["includeDeveloperTrace"] = include_developer_trace
|
|
93
|
+
return request_body
|
|
94
|
+
|
|
46
95
|
@staticmethod
|
|
47
96
|
def _merge_developer_trace(
|
|
48
97
|
first: QueryDeveloperTrace | None,
|
|
@@ -278,6 +327,71 @@ class Query:
|
|
|
278
327
|
|
|
279
328
|
raise ContextError("Streaming query ended before done event")
|
|
280
329
|
|
|
330
|
+
async def start(
|
|
331
|
+
self,
|
|
332
|
+
query: str,
|
|
333
|
+
tools: list[str] | None = None,
|
|
334
|
+
favorites_only: bool | None = None,
|
|
335
|
+
agent_model_id: AgentModelId | str | None = None,
|
|
336
|
+
response_shape: QueryResponseShape | None = None,
|
|
337
|
+
include_data: bool | None = None,
|
|
338
|
+
include_data_url: bool | None = None,
|
|
339
|
+
include_developer_trace: bool | None = None,
|
|
340
|
+
resume_from: QueryAttemptReference | dict[str, Any] | None = None,
|
|
341
|
+
fork_from: QueryForkReference | dict[str, Any] | None = None,
|
|
342
|
+
idempotency_key: str | None = None,
|
|
343
|
+
) -> QueryJobStartResult:
|
|
344
|
+
"""Start a durable async query job and return its job id immediately."""
|
|
345
|
+
request_body = self._build_request_body(
|
|
346
|
+
query=query,
|
|
347
|
+
tools=tools,
|
|
348
|
+
favorites_only=favorites_only,
|
|
349
|
+
agent_model_id=agent_model_id,
|
|
350
|
+
response_shape=response_shape,
|
|
351
|
+
include_data=include_data,
|
|
352
|
+
include_data_url=include_data_url,
|
|
353
|
+
include_developer_trace=include_developer_trace,
|
|
354
|
+
resume_from=resume_from,
|
|
355
|
+
fork_from=fork_from,
|
|
356
|
+
stream=False,
|
|
357
|
+
)
|
|
358
|
+
response = await self._client.fetch(
|
|
359
|
+
"/api/v1/query/jobs",
|
|
360
|
+
method="POST",
|
|
361
|
+
json_body=request_body,
|
|
362
|
+
extra_headers=(
|
|
363
|
+
{"Idempotency-Key": idempotency_key}
|
|
364
|
+
if idempotency_key
|
|
365
|
+
else None
|
|
366
|
+
),
|
|
367
|
+
)
|
|
368
|
+
return QueryJobStartResult.model_validate(response)
|
|
369
|
+
|
|
370
|
+
async def get_status(self, job_id: str) -> QueryJobStatusResult:
|
|
371
|
+
"""Fetch the current status for a durable async query job."""
|
|
372
|
+
response = await self._client.fetch(f"/api/v1/query/jobs/{job_id}")
|
|
373
|
+
return QueryJobStatusResult.model_validate(response)
|
|
374
|
+
|
|
375
|
+
async def poll(
|
|
376
|
+
self,
|
|
377
|
+
job_id: str,
|
|
378
|
+
interval_ms: int = 2000,
|
|
379
|
+
timeout_ms: int = 15 * 60_000,
|
|
380
|
+
) -> QueryJobStatusResult:
|
|
381
|
+
"""Poll a durable async query job until completion or failure."""
|
|
382
|
+
loop = asyncio.get_running_loop()
|
|
383
|
+
deadline = loop.time() + timeout_ms / 1000
|
|
384
|
+
|
|
385
|
+
while loop.time() <= deadline:
|
|
386
|
+
status = await self.get_status(job_id)
|
|
387
|
+
if status.status == "completed":
|
|
388
|
+
return status
|
|
389
|
+
if status.status == "failed":
|
|
390
|
+
raise ContextError(status.error or "Context query job failed")
|
|
391
|
+
await asyncio.sleep(interval_ms / 1000)
|
|
392
|
+
|
|
393
|
+
raise ContextError(f"Context query job polling timed out after {timeout_ms}ms")
|
|
394
|
+
|
|
281
395
|
async def stream(
|
|
282
396
|
self,
|
|
283
397
|
query: str,
|
|
@@ -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
|
|
|
@@ -46,12 +46,9 @@ SuggestedPromptSource = Literal["contributor", "platform", "sdk"]
|
|
|
46
46
|
DEFAULT_AGENT_MODEL_ID = "kimi-k2.6-model"
|
|
47
47
|
AGENT_MODEL_IDS = (
|
|
48
48
|
"kimi-k2.6-model",
|
|
49
|
+
"glm-5.2-model",
|
|
49
50
|
"deepseek-v4-pro-model",
|
|
50
51
|
"deepseek-v4-flash-model",
|
|
51
|
-
"gemini-3.5-flash-model",
|
|
52
|
-
"gemini-flash-model",
|
|
53
|
-
"gemini-lite-model",
|
|
54
|
-
"minimax-m3-model",
|
|
55
52
|
"qwen-3.7-plus-model",
|
|
56
53
|
"qwen-3.7-max-model",
|
|
57
54
|
"gpt-5.5-model",
|
|
@@ -59,12 +56,9 @@ AGENT_MODEL_IDS = (
|
|
|
59
56
|
)
|
|
60
57
|
AgentModelId = Literal[
|
|
61
58
|
"kimi-k2.6-model",
|
|
59
|
+
"glm-5.2-model",
|
|
62
60
|
"deepseek-v4-pro-model",
|
|
63
61
|
"deepseek-v4-flash-model",
|
|
64
|
-
"gemini-3.5-flash-model",
|
|
65
|
-
"gemini-flash-model",
|
|
66
|
-
"gemini-lite-model",
|
|
67
|
-
"minimax-m3-model",
|
|
68
62
|
"qwen-3.7-plus-model",
|
|
69
63
|
"qwen-3.7-max-model",
|
|
70
64
|
"gpt-5.5-model",
|
|
@@ -745,21 +739,21 @@ class QueryOptions(BaseModel):
|
|
|
745
739
|
description=(
|
|
746
740
|
"Structured response mode. Defaults to `answer_with_evidence` on the "
|
|
747
741
|
"server when omitted. The runtime always produces a grounded result "
|
|
748
|
-
"(
|
|
749
|
-
"a prose synthesis layer is added on top. "
|
|
742
|
+
"(bounded evidence + computed artifacts + full-data references); "
|
|
743
|
+
"this controls whether a prose synthesis layer is added on top. "
|
|
750
744
|
"`answer_with_evidence` returns prose plus the structured grounding "
|
|
751
745
|
"(chat parity); `evidence_only` returns grounding only with no prose "
|
|
752
|
-
"(the agent-harness shape, with
|
|
753
|
-
"
|
|
746
|
+
"(the agent-harness shape, with bounded evidence, computed_artifacts, "
|
|
747
|
+
"and data_url/canonical_data_ref references)."
|
|
754
748
|
),
|
|
755
749
|
)
|
|
756
750
|
include_data: bool | None = Field(
|
|
757
751
|
default=None,
|
|
758
752
|
alias="includeData",
|
|
759
753
|
description=(
|
|
760
|
-
"Include
|
|
761
|
-
"response_shape
|
|
762
|
-
"
|
|
754
|
+
"Include bounded execution data inline. Defaults to false for every "
|
|
755
|
+
"response_shape. Large payloads are returned as a preview object "
|
|
756
|
+
"with fullData.dataUrl/canonicalDataRef instead of unbounded raw rows."
|
|
763
757
|
),
|
|
764
758
|
)
|
|
765
759
|
include_data_url: bool | None = Field(
|
|
@@ -935,7 +929,31 @@ class QueryChartArtifact(BaseModel):
|
|
|
935
929
|
title: str | None = None
|
|
936
930
|
|
|
937
931
|
|
|
938
|
-
|
|
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
|
+
]
|
|
939
957
|
|
|
940
958
|
|
|
941
959
|
class QueryToolCallFailureSample(BaseModel):
|
|
@@ -1703,7 +1721,7 @@ class QueryResult(BaseModel):
|
|
|
1703
1721
|
tools_used: Tools that were used to answer the query
|
|
1704
1722
|
cost: Cost breakdown
|
|
1705
1723
|
duration_ms: Total duration in milliseconds
|
|
1706
|
-
data:
|
|
1724
|
+
data: Bounded execution data when include_data is enabled
|
|
1707
1725
|
data_url: Optional blob URL for execution data (when include_data_url is enabled)
|
|
1708
1726
|
computed_artifacts: Optional chart artifacts emitted by code interpreter
|
|
1709
1727
|
developer_trace: Optional machine-readable Developer Mode trace
|
|
@@ -1721,9 +1739,9 @@ class QueryResult(BaseModel):
|
|
|
1721
1739
|
data: Any | None = Field(
|
|
1722
1740
|
default=None,
|
|
1723
1741
|
description=(
|
|
1724
|
-
"
|
|
1725
|
-
"
|
|
1726
|
-
"
|
|
1742
|
+
"Bounded execution data from tools. Returned only when include_data "
|
|
1743
|
+
"is true. Small payloads may be direct data; large payloads are a "
|
|
1744
|
+
"truncation object with preview and fullData references."
|
|
1727
1745
|
),
|
|
1728
1746
|
)
|
|
1729
1747
|
data_url: str | None = Field(
|
|
@@ -1892,6 +1910,40 @@ class QueryApiSuccessResponse(BaseModel):
|
|
|
1892
1910
|
model_config = {"populate_by_name": True}
|
|
1893
1911
|
|
|
1894
1912
|
|
|
1913
|
+
QueryJobStatus = Literal["queued", "running", "completed", "failed"]
|
|
1914
|
+
|
|
1915
|
+
|
|
1916
|
+
class QueryJobStartResult(BaseModel):
|
|
1917
|
+
"""Response from starting a durable async query job."""
|
|
1918
|
+
|
|
1919
|
+
status: QueryJobStatus
|
|
1920
|
+
job_id: str = Field(..., alias="jobId")
|
|
1921
|
+
polling_tool: str | None = Field(default=None, alias="pollingTool")
|
|
1922
|
+
message: str | None = None
|
|
1923
|
+
progress: Any | None = None
|
|
1924
|
+
query_session: Any | None = Field(default=None, alias="querySession")
|
|
1925
|
+
created_at: str = Field(..., alias="createdAt")
|
|
1926
|
+
updated_at: str = Field(..., alias="updatedAt")
|
|
1927
|
+
|
|
1928
|
+
model_config = {"populate_by_name": True}
|
|
1929
|
+
|
|
1930
|
+
|
|
1931
|
+
class QueryJobStatusResult(BaseModel):
|
|
1932
|
+
"""Current status for a durable async query job."""
|
|
1933
|
+
|
|
1934
|
+
status: QueryJobStatus
|
|
1935
|
+
job_id: str = Field(..., alias="jobId")
|
|
1936
|
+
progress: Any | None = None
|
|
1937
|
+
query_session: Any | None = Field(default=None, alias="querySession")
|
|
1938
|
+
result: QueryResult | None = None
|
|
1939
|
+
error: str | None = None
|
|
1940
|
+
created_at: str = Field(..., alias="createdAt")
|
|
1941
|
+
updated_at: str = Field(..., alias="updatedAt")
|
|
1942
|
+
completed_at: str | None = Field(default=None, alias="completedAt")
|
|
1943
|
+
|
|
1944
|
+
model_config = {"populate_by_name": True}
|
|
1945
|
+
|
|
1946
|
+
|
|
1895
1947
|
class QueryStreamToolStatusEvent(BaseModel):
|
|
1896
1948
|
"""Emitted when a tool starts or changes execution status."""
|
|
1897
1949
|
|
|
@@ -897,6 +897,41 @@ class TestQueryRun:
|
|
|
897
897
|
assert mixed_axis.spec.series[1].y_axis == "right"
|
|
898
898
|
assert mixed_axis.spec.series[1].satisfies == "volume"
|
|
899
899
|
|
|
900
|
+
async def test_parses_rendered_image_artifact(self) -> None:
|
|
901
|
+
"""Rendered image artifacts (kind: image) deserialize without error."""
|
|
902
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
903
|
+
response = {
|
|
904
|
+
**MOCK_SUCCESS_RESPONSE,
|
|
905
|
+
"computedArtifacts": [
|
|
906
|
+
{
|
|
907
|
+
"kind": "image",
|
|
908
|
+
"url": "https://blob.example.com/charts/abc123.png",
|
|
909
|
+
"alt": "Sector ETF YTD normalized performance chart",
|
|
910
|
+
"title": "Sector ETF YTD Performance",
|
|
911
|
+
"contentHash": "abc123",
|
|
912
|
+
"bytes": 320749,
|
|
913
|
+
"width": 1926,
|
|
914
|
+
"height": 1030,
|
|
915
|
+
},
|
|
916
|
+
],
|
|
917
|
+
}
|
|
918
|
+
with patch.object(
|
|
919
|
+
client, "fetch_stream", new_callable=AsyncMock
|
|
920
|
+
) as mock_stream:
|
|
921
|
+
mock_stream.return_value = _make_done_stream_response(response)
|
|
922
|
+
result = await client.query.run("Render a chart image")
|
|
923
|
+
|
|
924
|
+
assert result.computed_artifacts is not None
|
|
925
|
+
assert len(result.computed_artifacts) == 1
|
|
926
|
+
image = result.computed_artifacts[0]
|
|
927
|
+
assert image.kind == "image"
|
|
928
|
+
assert image.url == "https://blob.example.com/charts/abc123.png"
|
|
929
|
+
assert image.title == "Sector ETF YTD Performance"
|
|
930
|
+
assert image.content_hash == "abc123"
|
|
931
|
+
assert image.bytes == 320749
|
|
932
|
+
assert image.width == 1926
|
|
933
|
+
assert image.height == 1030
|
|
934
|
+
|
|
900
935
|
async def test_parses_shared_ungrounded_capability_miss_fixture(self) -> None:
|
|
901
936
|
"""Ungrounded runtime outcomes deserialize as capability_miss."""
|
|
902
937
|
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
@@ -1466,3 +1501,104 @@ class TestQueryStream:
|
|
|
1466
1501
|
events.append(event)
|
|
1467
1502
|
|
|
1468
1503
|
assert len(events) == 1
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
class TestQueryJobs:
|
|
1507
|
+
"""Tests for durable async query job helpers."""
|
|
1508
|
+
|
|
1509
|
+
async def test_start_creates_query_job(self) -> None:
|
|
1510
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1511
|
+
payload = {
|
|
1512
|
+
"status": "running",
|
|
1513
|
+
"jobId": "11111111-1111-4111-8111-111111111111",
|
|
1514
|
+
"pollingTool": "context_query_status",
|
|
1515
|
+
"message": "running",
|
|
1516
|
+
"progress": None,
|
|
1517
|
+
"querySession": None,
|
|
1518
|
+
"createdAt": "2026-06-14T00:00:00.000Z",
|
|
1519
|
+
"updatedAt": "2026-06-14T00:00:00.000Z",
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
with patch.object(client, "fetch", new_callable=AsyncMock) as mock_fetch:
|
|
1523
|
+
mock_fetch.return_value = payload
|
|
1524
|
+
job = await client.query.start(
|
|
1525
|
+
query="long query",
|
|
1526
|
+
response_shape="evidence_only",
|
|
1527
|
+
include_data_url=True,
|
|
1528
|
+
idempotency_key="21118fda-33be-4d66-8df5-0e50b3371f54",
|
|
1529
|
+
)
|
|
1530
|
+
|
|
1531
|
+
assert job.job_id == "11111111-1111-4111-8111-111111111111"
|
|
1532
|
+
mock_fetch.assert_called_once_with(
|
|
1533
|
+
"/api/v1/query/jobs",
|
|
1534
|
+
method="POST",
|
|
1535
|
+
json_body={
|
|
1536
|
+
"query": "long query",
|
|
1537
|
+
"tools": None,
|
|
1538
|
+
"stream": False,
|
|
1539
|
+
"responseShape": "evidence_only",
|
|
1540
|
+
"includeDataUrl": True,
|
|
1541
|
+
},
|
|
1542
|
+
extra_headers={
|
|
1543
|
+
"Idempotency-Key": "21118fda-33be-4d66-8df5-0e50b3371f54",
|
|
1544
|
+
},
|
|
1545
|
+
)
|
|
1546
|
+
|
|
1547
|
+
async def test_get_status_fetches_query_job(self) -> None:
|
|
1548
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1549
|
+
payload = {
|
|
1550
|
+
"status": "completed",
|
|
1551
|
+
"jobId": "11111111-1111-4111-8111-111111111111",
|
|
1552
|
+
"progress": None,
|
|
1553
|
+
"querySession": None,
|
|
1554
|
+
"result": MOCK_SUCCESS_RESPONSE,
|
|
1555
|
+
"error": None,
|
|
1556
|
+
"createdAt": "2026-06-14T00:00:00.000Z",
|
|
1557
|
+
"updatedAt": "2026-06-14T00:01:00.000Z",
|
|
1558
|
+
"completedAt": "2026-06-14T00:01:00.000Z",
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
with patch.object(client, "fetch", new_callable=AsyncMock) as mock_fetch:
|
|
1562
|
+
mock_fetch.return_value = payload
|
|
1563
|
+
status = await client.query.get_status(
|
|
1564
|
+
"11111111-1111-4111-8111-111111111111"
|
|
1565
|
+
)
|
|
1566
|
+
|
|
1567
|
+
assert status.status == "completed"
|
|
1568
|
+
assert status.result is not None
|
|
1569
|
+
assert status.result.response == MOCK_SUCCESS_RESPONSE["response"]
|
|
1570
|
+
mock_fetch.assert_called_once_with(
|
|
1571
|
+
"/api/v1/query/jobs/11111111-1111-4111-8111-111111111111"
|
|
1572
|
+
)
|
|
1573
|
+
|
|
1574
|
+
async def test_poll_waits_until_query_job_completes(self) -> None:
|
|
1575
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1576
|
+
running_payload = {
|
|
1577
|
+
"status": "running",
|
|
1578
|
+
"jobId": "11111111-1111-4111-8111-111111111111",
|
|
1579
|
+
"progress": None,
|
|
1580
|
+
"querySession": None,
|
|
1581
|
+
"result": None,
|
|
1582
|
+
"error": None,
|
|
1583
|
+
"createdAt": "2026-06-14T00:00:00.000Z",
|
|
1584
|
+
"updatedAt": "2026-06-14T00:00:30.000Z",
|
|
1585
|
+
"completedAt": None,
|
|
1586
|
+
}
|
|
1587
|
+
completed_payload = {
|
|
1588
|
+
**running_payload,
|
|
1589
|
+
"status": "completed",
|
|
1590
|
+
"result": MOCK_SUCCESS_RESPONSE,
|
|
1591
|
+
"updatedAt": "2026-06-14T00:01:00.000Z",
|
|
1592
|
+
"completedAt": "2026-06-14T00:01:00.000Z",
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
with patch.object(client, "fetch", new_callable=AsyncMock) as mock_fetch:
|
|
1596
|
+
mock_fetch.side_effect = [running_payload, completed_payload]
|
|
1597
|
+
status = await client.query.poll(
|
|
1598
|
+
"11111111-1111-4111-8111-111111111111",
|
|
1599
|
+
interval_ms=1,
|
|
1600
|
+
timeout_ms=1000,
|
|
1601
|
+
)
|
|
1602
|
+
|
|
1603
|
+
assert status.status == "completed"
|
|
1604
|
+
assert mock_fetch.await_count == 2
|
|
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.17.0 → ctxprotocol-0.19.0}/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.17.0 → ctxprotocol-0.19.0}/examples/server/hummingbot-contributor/env.example
RENAMED
|
File without changes
|
{ctxprotocol-0.17.0 → ctxprotocol-0.19.0}/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
|