ctxprotocol 0.17.0__tar.gz → 0.18.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.18.0}/.gitignore +1 -0
- ctxprotocol-0.18.0/CHANGELOG.md +7 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/PKG-INFO +23 -5
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/README.md +22 -4
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/__init__.py +7 -1
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/__init__.py +6 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/query.py +114 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/types.py +47 -19
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/pyproject.toml +1 -1
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/tests/test_query.py +101 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/uv.lock +1 -1
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/.codexignore +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/auth/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/developer.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/discovery.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/tools.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/context/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/context/hyperliquid.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/context/polymarket.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/context/wallet.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/core.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/trace.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/types.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/validation.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/handshake/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/ctxprotocol/py.typed +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/docs/README.md +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/execute_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/polymarket_query_trace_validation.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/query_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/test_favorites_only.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/test_get_tool.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/two_surfaces_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/validation/generate_contributor_search_validation_artifacts.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/validation/named-regression-iran-boots-on-ground.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/validation/named-regression-supreme-court-tariffs.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/validation/shared-capability-miss-unsupported-venue.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/validation/shared-generic-overlap-best-match.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/client/validation/shared-still-ambiguous-shortlist.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/README.md +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/env.example +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/requirements.txt +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/server.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/polymarket-query-trace-results-py.json +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/tests/__init__.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/tests/contrib_search_validation_cases.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/tests/test_client.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/tests/test_contrib_search.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/tests/test_discovery.py +0 -0
- {ctxprotocol-0.17.0 → ctxprotocol-0.18.0}/tests/test_tools.py +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctxprotocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.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.18.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",
|
|
@@ -51,6 +51,9 @@ from ctxprotocol.client.types import (
|
|
|
51
51
|
QueryDeveloperTraceSummary,
|
|
52
52
|
QueryDeveloperTraceStep,
|
|
53
53
|
QueryDeveloperTraceToolRef,
|
|
54
|
+
QueryJobStartResult,
|
|
55
|
+
QueryJobStatus,
|
|
56
|
+
QueryJobStatusResult,
|
|
54
57
|
QueryOrchestrationMetrics,
|
|
55
58
|
QueryDeveloperTraceLoopInfo,
|
|
56
59
|
QueryStreamDeveloperTraceEvent,
|
|
@@ -111,6 +114,9 @@ __all__ = [
|
|
|
111
114
|
"QueryDeveloperTraceSummary",
|
|
112
115
|
"QueryDeveloperTraceStep",
|
|
113
116
|
"QueryDeveloperTraceToolRef",
|
|
117
|
+
"QueryJobStartResult",
|
|
118
|
+
"QueryJobStatus",
|
|
119
|
+
"QueryJobStatusResult",
|
|
114
120
|
"QueryOrchestrationMetrics",
|
|
115
121
|
"QueryDeveloperTraceLoopInfo",
|
|
116
122
|
"QueryApiSuccessResponse",
|
|
@@ -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,
|
|
@@ -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(
|
|
@@ -1703,7 +1697,7 @@ class QueryResult(BaseModel):
|
|
|
1703
1697
|
tools_used: Tools that were used to answer the query
|
|
1704
1698
|
cost: Cost breakdown
|
|
1705
1699
|
duration_ms: Total duration in milliseconds
|
|
1706
|
-
data:
|
|
1700
|
+
data: Bounded execution data when include_data is enabled
|
|
1707
1701
|
data_url: Optional blob URL for execution data (when include_data_url is enabled)
|
|
1708
1702
|
computed_artifacts: Optional chart artifacts emitted by code interpreter
|
|
1709
1703
|
developer_trace: Optional machine-readable Developer Mode trace
|
|
@@ -1721,9 +1715,9 @@ class QueryResult(BaseModel):
|
|
|
1721
1715
|
data: Any | None = Field(
|
|
1722
1716
|
default=None,
|
|
1723
1717
|
description=(
|
|
1724
|
-
"
|
|
1725
|
-
"
|
|
1726
|
-
"
|
|
1718
|
+
"Bounded execution data from tools. Returned only when include_data "
|
|
1719
|
+
"is true. Small payloads may be direct data; large payloads are a "
|
|
1720
|
+
"truncation object with preview and fullData references."
|
|
1727
1721
|
),
|
|
1728
1722
|
)
|
|
1729
1723
|
data_url: str | None = Field(
|
|
@@ -1892,6 +1886,40 @@ class QueryApiSuccessResponse(BaseModel):
|
|
|
1892
1886
|
model_config = {"populate_by_name": True}
|
|
1893
1887
|
|
|
1894
1888
|
|
|
1889
|
+
QueryJobStatus = Literal["queued", "running", "completed", "failed"]
|
|
1890
|
+
|
|
1891
|
+
|
|
1892
|
+
class QueryJobStartResult(BaseModel):
|
|
1893
|
+
"""Response from starting a durable async query job."""
|
|
1894
|
+
|
|
1895
|
+
status: QueryJobStatus
|
|
1896
|
+
job_id: str = Field(..., alias="jobId")
|
|
1897
|
+
polling_tool: str | None = Field(default=None, alias="pollingTool")
|
|
1898
|
+
message: str | None = None
|
|
1899
|
+
progress: Any | None = None
|
|
1900
|
+
query_session: Any | None = Field(default=None, alias="querySession")
|
|
1901
|
+
created_at: str = Field(..., alias="createdAt")
|
|
1902
|
+
updated_at: str = Field(..., alias="updatedAt")
|
|
1903
|
+
|
|
1904
|
+
model_config = {"populate_by_name": True}
|
|
1905
|
+
|
|
1906
|
+
|
|
1907
|
+
class QueryJobStatusResult(BaseModel):
|
|
1908
|
+
"""Current status for a durable async query job."""
|
|
1909
|
+
|
|
1910
|
+
status: QueryJobStatus
|
|
1911
|
+
job_id: str = Field(..., alias="jobId")
|
|
1912
|
+
progress: Any | None = None
|
|
1913
|
+
query_session: Any | None = Field(default=None, alias="querySession")
|
|
1914
|
+
result: QueryResult | None = None
|
|
1915
|
+
error: str | None = None
|
|
1916
|
+
created_at: str = Field(..., alias="createdAt")
|
|
1917
|
+
updated_at: str = Field(..., alias="updatedAt")
|
|
1918
|
+
completed_at: str | None = Field(default=None, alias="completedAt")
|
|
1919
|
+
|
|
1920
|
+
model_config = {"populate_by_name": True}
|
|
1921
|
+
|
|
1922
|
+
|
|
1895
1923
|
class QueryStreamToolStatusEvent(BaseModel):
|
|
1896
1924
|
"""Emitted when a tool starts or changes execution status."""
|
|
1897
1925
|
|
|
@@ -1466,3 +1466,104 @@ class TestQueryStream:
|
|
|
1466
1466
|
events.append(event)
|
|
1467
1467
|
|
|
1468
1468
|
assert len(events) == 1
|
|
1469
|
+
|
|
1470
|
+
|
|
1471
|
+
class TestQueryJobs:
|
|
1472
|
+
"""Tests for durable async query job helpers."""
|
|
1473
|
+
|
|
1474
|
+
async def test_start_creates_query_job(self) -> None:
|
|
1475
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1476
|
+
payload = {
|
|
1477
|
+
"status": "running",
|
|
1478
|
+
"jobId": "11111111-1111-4111-8111-111111111111",
|
|
1479
|
+
"pollingTool": "context_query_status",
|
|
1480
|
+
"message": "running",
|
|
1481
|
+
"progress": None,
|
|
1482
|
+
"querySession": None,
|
|
1483
|
+
"createdAt": "2026-06-14T00:00:00.000Z",
|
|
1484
|
+
"updatedAt": "2026-06-14T00:00:00.000Z",
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
with patch.object(client, "fetch", new_callable=AsyncMock) as mock_fetch:
|
|
1488
|
+
mock_fetch.return_value = payload
|
|
1489
|
+
job = await client.query.start(
|
|
1490
|
+
query="long query",
|
|
1491
|
+
response_shape="evidence_only",
|
|
1492
|
+
include_data_url=True,
|
|
1493
|
+
idempotency_key="21118fda-33be-4d66-8df5-0e50b3371f54",
|
|
1494
|
+
)
|
|
1495
|
+
|
|
1496
|
+
assert job.job_id == "11111111-1111-4111-8111-111111111111"
|
|
1497
|
+
mock_fetch.assert_called_once_with(
|
|
1498
|
+
"/api/v1/query/jobs",
|
|
1499
|
+
method="POST",
|
|
1500
|
+
json_body={
|
|
1501
|
+
"query": "long query",
|
|
1502
|
+
"tools": None,
|
|
1503
|
+
"stream": False,
|
|
1504
|
+
"responseShape": "evidence_only",
|
|
1505
|
+
"includeDataUrl": True,
|
|
1506
|
+
},
|
|
1507
|
+
extra_headers={
|
|
1508
|
+
"Idempotency-Key": "21118fda-33be-4d66-8df5-0e50b3371f54",
|
|
1509
|
+
},
|
|
1510
|
+
)
|
|
1511
|
+
|
|
1512
|
+
async def test_get_status_fetches_query_job(self) -> None:
|
|
1513
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1514
|
+
payload = {
|
|
1515
|
+
"status": "completed",
|
|
1516
|
+
"jobId": "11111111-1111-4111-8111-111111111111",
|
|
1517
|
+
"progress": None,
|
|
1518
|
+
"querySession": None,
|
|
1519
|
+
"result": MOCK_SUCCESS_RESPONSE,
|
|
1520
|
+
"error": None,
|
|
1521
|
+
"createdAt": "2026-06-14T00:00:00.000Z",
|
|
1522
|
+
"updatedAt": "2026-06-14T00:01:00.000Z",
|
|
1523
|
+
"completedAt": "2026-06-14T00:01:00.000Z",
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
with patch.object(client, "fetch", new_callable=AsyncMock) as mock_fetch:
|
|
1527
|
+
mock_fetch.return_value = payload
|
|
1528
|
+
status = await client.query.get_status(
|
|
1529
|
+
"11111111-1111-4111-8111-111111111111"
|
|
1530
|
+
)
|
|
1531
|
+
|
|
1532
|
+
assert status.status == "completed"
|
|
1533
|
+
assert status.result is not None
|
|
1534
|
+
assert status.result.response == MOCK_SUCCESS_RESPONSE["response"]
|
|
1535
|
+
mock_fetch.assert_called_once_with(
|
|
1536
|
+
"/api/v1/query/jobs/11111111-1111-4111-8111-111111111111"
|
|
1537
|
+
)
|
|
1538
|
+
|
|
1539
|
+
async def test_poll_waits_until_query_job_completes(self) -> None:
|
|
1540
|
+
client = ContextClient(api_key="ctx_test_key_1234567890abcdef12345678")
|
|
1541
|
+
running_payload = {
|
|
1542
|
+
"status": "running",
|
|
1543
|
+
"jobId": "11111111-1111-4111-8111-111111111111",
|
|
1544
|
+
"progress": None,
|
|
1545
|
+
"querySession": None,
|
|
1546
|
+
"result": None,
|
|
1547
|
+
"error": None,
|
|
1548
|
+
"createdAt": "2026-06-14T00:00:00.000Z",
|
|
1549
|
+
"updatedAt": "2026-06-14T00:00:30.000Z",
|
|
1550
|
+
"completedAt": None,
|
|
1551
|
+
}
|
|
1552
|
+
completed_payload = {
|
|
1553
|
+
**running_payload,
|
|
1554
|
+
"status": "completed",
|
|
1555
|
+
"result": MOCK_SUCCESS_RESPONSE,
|
|
1556
|
+
"updatedAt": "2026-06-14T00:01:00.000Z",
|
|
1557
|
+
"completedAt": "2026-06-14T00:01:00.000Z",
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
with patch.object(client, "fetch", new_callable=AsyncMock) as mock_fetch:
|
|
1561
|
+
mock_fetch.side_effect = [running_payload, completed_payload]
|
|
1562
|
+
status = await client.query.poll(
|
|
1563
|
+
"11111111-1111-4111-8111-111111111111",
|
|
1564
|
+
interval_ms=1,
|
|
1565
|
+
timeout_ms=1000,
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
assert status.status == "completed"
|
|
1569
|
+
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.18.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.18.0}/examples/server/hummingbot-contributor/env.example
RENAMED
|
File without changes
|
{ctxprotocol-0.17.0 → ctxprotocol-0.18.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
|