ctxprotocol 0.16.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.16.0 → ctxprotocol-0.18.0}/.gitignore +1 -0
- ctxprotocol-0.18.0/CHANGELOG.md +7 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/PKG-INFO +36 -9
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/README.md +35 -8
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/__init__.py +13 -1
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/__init__.py +12 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/query.py +122 -7
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/types.py +74 -15
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/pyproject.toml +1 -1
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/tests/test_query.py +111 -8
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/uv.lock +1 -1
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/.codexignore +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/auth/__init__.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/client.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/__init__.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/developer.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/discovery.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/client/resources/tools.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/context/__init__.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/context/hyperliquid.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/context/polymarket.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/context/wallet.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/__init__.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/__init__.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/core.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/trace.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/types.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/contrib/search/validation.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/handshake/__init__.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/ctxprotocol/py.typed +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/docs/README.md +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/execute_client.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/polymarket_query_trace_validation.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/query_client.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/test_favorites_only.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/test_get_tool.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/two_surfaces_client.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/validation/generate_contributor_search_validation_artifacts.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/validation/named-regression-iran-boots-on-ground.json +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/validation/named-regression-supreme-court-tariffs.json +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/validation/shared-capability-miss-unsupported-venue.json +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/validation/shared-generic-overlap-best-match.json +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/client/validation/shared-still-ambiguous-shortlist.json +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/README.md +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/env.example +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/requirements.txt +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/server.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/polymarket-query-trace-results-py.json +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/tests/__init__.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/tests/contrib_search_validation_cases.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/tests/test_client.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/tests/test_contrib_search.py +0 -0
- {ctxprotocol-0.16.0 → ctxprotocol-0.18.0}/tests/test_discovery.py +0 -0
- {ctxprotocol-0.16.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
|
|
@@ -129,7 +129,7 @@ print(result.session) # method_price, spent, remaining, max_spend, ...
|
|
|
129
129
|
```python
|
|
130
130
|
answer = await client.query.run(
|
|
131
131
|
query="What are the top whale movements on Base?",
|
|
132
|
-
|
|
132
|
+
agent_model_id="kimi-k2.6-model", # optional: choose the main librarian agent model
|
|
133
133
|
response_shape="answer_with_evidence", # optional: answer_with_evidence (default) | evidence_only
|
|
134
134
|
include_data_url=True, # optional: persist full execution data to blob
|
|
135
135
|
include_developer_trace=True, # optional: include runtime developer trace
|
|
@@ -149,6 +149,33 @@ 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
|
+
|
|
170
|
+
To see valid model slugs without guessing:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from ctxprotocol import AGENT_MODEL_IDS, DEFAULT_AGENT_MODEL_ID
|
|
174
|
+
|
|
175
|
+
print(DEFAULT_AGENT_MODEL_ID) # "kimi-k2.6-model"
|
|
176
|
+
print(AGENT_MODEL_IDS) # Supported agent_model_id values
|
|
177
|
+
```
|
|
178
|
+
|
|
152
179
|
> Mixed listings are first-class: one listing can expose methods to both surfaces. Methods without `_meta.pricing.executeUsd` remain query-only until priced.
|
|
153
180
|
>
|
|
154
181
|
> Compatibility: SDK/API payload fields such as `price` and `price_per_query` are retained for backward compatibility. In Query mode, they represent listing-level **price per response turn**.
|
|
@@ -157,7 +184,7 @@ print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
|
157
184
|
`response_shape` options:
|
|
158
185
|
|
|
159
186
|
- `answer_with_evidence` (default): prose plus `summary`, `evidence`, `artifacts`, `freshness`, `confidence`, `usage`, `outcome`, and `controller`
|
|
160
|
-
- `evidence_only`:
|
|
187
|
+
- `evidence_only`: bounded evidence, computed artifacts, and full-data references for downstream agents (no prose synthesis)
|
|
161
188
|
|
|
162
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`.
|
|
163
190
|
|
|
@@ -297,7 +324,7 @@ closed = await client.tools.close_session("sess_123")
|
|
|
297
324
|
|
|
298
325
|
### Query (Pay-Per-Response)
|
|
299
326
|
|
|
300
|
-
#### `client.query.run(query, tools?,
|
|
327
|
+
#### `client.query.run(query, tools?, agent_model_id?, include_data?, include_data_url?, include_developer_trace?, idempotency_key?)`
|
|
301
328
|
|
|
302
329
|
Run an agentic query. The server applies the live librarian pipeline (`discover -> select -> iterative execute -> synthesize -> settle`) with up to 100 MCP calls per response turn, then returns the selected Query response contract (`answer_with_evidence` or `evidence_only`, default `answer_with_evidence`).
|
|
303
330
|
|
|
@@ -317,8 +344,8 @@ answer = await client.query.run("What are the top whale movements on Base?")
|
|
|
317
344
|
answer = await client.query.run(
|
|
318
345
|
query="Analyze whale activity on Base",
|
|
319
346
|
tools=["tool-uuid-1", "tool-uuid-2"], # optional — auto-discover if omitted
|
|
320
|
-
|
|
321
|
-
include_data=True, # optional: include execution data inline
|
|
347
|
+
agent_model_id="kimi-k2.6-model", # optional main librarian agent model
|
|
348
|
+
include_data=True, # optional: include bounded execution data inline
|
|
322
349
|
include_data_url=True, # optional: include blob URL for full data
|
|
323
350
|
include_developer_trace=True, # optional: include Developer Mode trace
|
|
324
351
|
)
|
|
@@ -327,7 +354,7 @@ print(answer.response) # response text or summary
|
|
|
327
354
|
print(answer.tools_used) # [QueryToolUsage(id, name, skill_calls)]
|
|
328
355
|
print(answer.cost) # QueryCost(model_cost_usd, tool_cost_usd, total_cost_usd)
|
|
329
356
|
print(answer.duration_ms) # Total time
|
|
330
|
-
print(answer.data) # Optional
|
|
357
|
+
print(answer.data) # Optional bounded data or truncation preview
|
|
331
358
|
print(answer.data_url) # Optional blob URL (when include_data_url=True)
|
|
332
359
|
print(answer.developer_trace.summary if answer.developer_trace else None)
|
|
333
360
|
print(
|
|
@@ -338,9 +365,9 @@ print(
|
|
|
338
365
|
print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
339
366
|
```
|
|
340
367
|
|
|
341
|
-
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.
|
|
342
369
|
|
|
343
|
-
#### `client.query.stream(query, tools?,
|
|
370
|
+
#### `client.query.stream(query, tools?, agent_model_id?, include_data?, include_data_url?, include_developer_trace?, idempotency_key?)`
|
|
344
371
|
|
|
345
372
|
Same as `run()` but streams events in real-time via SSE.
|
|
346
373
|
|
|
@@ -91,7 +91,7 @@ print(result.session) # method_price, spent, remaining, max_spend, ...
|
|
|
91
91
|
```python
|
|
92
92
|
answer = await client.query.run(
|
|
93
93
|
query="What are the top whale movements on Base?",
|
|
94
|
-
|
|
94
|
+
agent_model_id="kimi-k2.6-model", # optional: choose the main librarian agent model
|
|
95
95
|
response_shape="answer_with_evidence", # optional: answer_with_evidence (default) | evidence_only
|
|
96
96
|
include_data_url=True, # optional: persist full execution data to blob
|
|
97
97
|
include_developer_trace=True, # optional: include runtime developer trace
|
|
@@ -111,6 +111,33 @@ 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
|
+
|
|
132
|
+
To see valid model slugs without guessing:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from ctxprotocol import AGENT_MODEL_IDS, DEFAULT_AGENT_MODEL_ID
|
|
136
|
+
|
|
137
|
+
print(DEFAULT_AGENT_MODEL_ID) # "kimi-k2.6-model"
|
|
138
|
+
print(AGENT_MODEL_IDS) # Supported agent_model_id values
|
|
139
|
+
```
|
|
140
|
+
|
|
114
141
|
> Mixed listings are first-class: one listing can expose methods to both surfaces. Methods without `_meta.pricing.executeUsd` remain query-only until priced.
|
|
115
142
|
>
|
|
116
143
|
> Compatibility: SDK/API payload fields such as `price` and `price_per_query` are retained for backward compatibility. In Query mode, they represent listing-level **price per response turn**.
|
|
@@ -119,7 +146,7 @@ print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
|
119
146
|
`response_shape` options:
|
|
120
147
|
|
|
121
148
|
- `answer_with_evidence` (default): prose plus `summary`, `evidence`, `artifacts`, `freshness`, `confidence`, `usage`, `outcome`, and `controller`
|
|
122
|
-
- `evidence_only`:
|
|
149
|
+
- `evidence_only`: bounded evidence, computed artifacts, and full-data references for downstream agents (no prose synthesis)
|
|
123
150
|
|
|
124
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`.
|
|
125
152
|
|
|
@@ -259,7 +286,7 @@ closed = await client.tools.close_session("sess_123")
|
|
|
259
286
|
|
|
260
287
|
### Query (Pay-Per-Response)
|
|
261
288
|
|
|
262
|
-
#### `client.query.run(query, tools?,
|
|
289
|
+
#### `client.query.run(query, tools?, agent_model_id?, include_data?, include_data_url?, include_developer_trace?, idempotency_key?)`
|
|
263
290
|
|
|
264
291
|
Run an agentic query. The server applies the live librarian pipeline (`discover -> select -> iterative execute -> synthesize -> settle`) with up to 100 MCP calls per response turn, then returns the selected Query response contract (`answer_with_evidence` or `evidence_only`, default `answer_with_evidence`).
|
|
265
292
|
|
|
@@ -279,8 +306,8 @@ answer = await client.query.run("What are the top whale movements on Base?")
|
|
|
279
306
|
answer = await client.query.run(
|
|
280
307
|
query="Analyze whale activity on Base",
|
|
281
308
|
tools=["tool-uuid-1", "tool-uuid-2"], # optional — auto-discover if omitted
|
|
282
|
-
|
|
283
|
-
include_data=True, # optional: include execution data inline
|
|
309
|
+
agent_model_id="kimi-k2.6-model", # optional main librarian agent model
|
|
310
|
+
include_data=True, # optional: include bounded execution data inline
|
|
284
311
|
include_data_url=True, # optional: include blob URL for full data
|
|
285
312
|
include_developer_trace=True, # optional: include Developer Mode trace
|
|
286
313
|
)
|
|
@@ -289,7 +316,7 @@ print(answer.response) # response text or summary
|
|
|
289
316
|
print(answer.tools_used) # [QueryToolUsage(id, name, skill_calls)]
|
|
290
317
|
print(answer.cost) # QueryCost(model_cost_usd, tool_cost_usd, total_cost_usd)
|
|
291
318
|
print(answer.duration_ms) # Total time
|
|
292
|
-
print(answer.data) # Optional
|
|
319
|
+
print(answer.data) # Optional bounded data or truncation preview
|
|
293
320
|
print(answer.data_url) # Optional blob URL (when include_data_url=True)
|
|
294
321
|
print(answer.developer_trace.summary if answer.developer_trace else None)
|
|
295
322
|
print(
|
|
@@ -300,9 +327,9 @@ print(
|
|
|
300
327
|
print(answer.orchestration_metrics) # Optional first-pass / rediscovery metrics
|
|
301
328
|
```
|
|
302
329
|
|
|
303
|
-
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.
|
|
304
331
|
|
|
305
|
-
#### `client.query.stream(query, tools?,
|
|
332
|
+
#### `client.query.stream(query, tools?, agent_model_id?, include_data?, include_data_url?, include_developer_trace?, idempotency_key?)`
|
|
306
333
|
|
|
307
334
|
Same as `run()` but streams events in real-time via SSE.
|
|
308
335
|
|
|
@@ -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 (
|
|
@@ -43,8 +43,10 @@ from ctxprotocol.client import (
|
|
|
43
43
|
Tools,
|
|
44
44
|
)
|
|
45
45
|
from ctxprotocol.client.types import (
|
|
46
|
+
AGENT_MODEL_IDS,
|
|
46
47
|
ContextClientOptions,
|
|
47
48
|
ContextErrorCode,
|
|
49
|
+
DEFAULT_AGENT_MODEL_ID,
|
|
48
50
|
ExecuteApiErrorResponse,
|
|
49
51
|
ExecuteApiSuccessResponse,
|
|
50
52
|
ExecuteMethodInfo,
|
|
@@ -68,6 +70,9 @@ from ctxprotocol.client.types import (
|
|
|
68
70
|
QueryDeveloperTraceSummary,
|
|
69
71
|
QueryDeveloperTraceStep,
|
|
70
72
|
QueryDeveloperTraceToolRef,
|
|
73
|
+
QueryJobStartResult,
|
|
74
|
+
QueryJobStatus,
|
|
75
|
+
QueryJobStatusResult,
|
|
71
76
|
QueryOrchestrationMetrics,
|
|
72
77
|
QueryDeveloperTraceLoopInfo,
|
|
73
78
|
QueryStreamDeveloperTraceEvent,
|
|
@@ -83,6 +88,7 @@ from ctxprotocol.client.types import (
|
|
|
83
88
|
SearchResponse,
|
|
84
89
|
Tool,
|
|
85
90
|
ToolInfo,
|
|
91
|
+
AgentModelId,
|
|
86
92
|
)
|
|
87
93
|
|
|
88
94
|
# Context types for portfolio injection
|
|
@@ -178,6 +184,9 @@ __all__ = [
|
|
|
178
184
|
"ExecuteApiErrorResponse",
|
|
179
185
|
"ExecuteSessionApiSuccessResponse",
|
|
180
186
|
"ToolInfo",
|
|
187
|
+
"AGENT_MODEL_IDS",
|
|
188
|
+
"DEFAULT_AGENT_MODEL_ID",
|
|
189
|
+
"AgentModelId",
|
|
181
190
|
# Query types (pay-per-response)
|
|
182
191
|
"QueryOptions",
|
|
183
192
|
"QueryResult",
|
|
@@ -189,6 +198,9 @@ __all__ = [
|
|
|
189
198
|
"QueryDeveloperTraceSummary",
|
|
190
199
|
"QueryDeveloperTraceStep",
|
|
191
200
|
"QueryDeveloperTraceToolRef",
|
|
201
|
+
"QueryJobStartResult",
|
|
202
|
+
"QueryJobStatus",
|
|
203
|
+
"QueryJobStatusResult",
|
|
192
204
|
"QueryOrchestrationMetrics",
|
|
193
205
|
"QueryDeveloperTraceLoopInfo",
|
|
194
206
|
"QueryApiSuccessResponse",
|
|
@@ -10,9 +10,11 @@ from ctxprotocol.client.resources.discovery import Discovery
|
|
|
10
10
|
from ctxprotocol.client.resources.query import Query
|
|
11
11
|
from ctxprotocol.client.resources.tools import Tools
|
|
12
12
|
from ctxprotocol.client.types import (
|
|
13
|
+
AGENT_MODEL_IDS,
|
|
13
14
|
ContextClientOptions,
|
|
14
15
|
ContextError,
|
|
15
16
|
ContextErrorCode,
|
|
17
|
+
DEFAULT_AGENT_MODEL_ID,
|
|
16
18
|
ExecuteApiErrorResponse,
|
|
17
19
|
ExecuteApiSuccessResponse,
|
|
18
20
|
ExecuteMethodInfo,
|
|
@@ -49,6 +51,9 @@ from ctxprotocol.client.types import (
|
|
|
49
51
|
QueryDeveloperTraceSummary,
|
|
50
52
|
QueryDeveloperTraceStep,
|
|
51
53
|
QueryDeveloperTraceToolRef,
|
|
54
|
+
QueryJobStartResult,
|
|
55
|
+
QueryJobStatus,
|
|
56
|
+
QueryJobStatusResult,
|
|
52
57
|
QueryOrchestrationMetrics,
|
|
53
58
|
QueryDeveloperTraceLoopInfo,
|
|
54
59
|
QueryStreamDeveloperTraceEvent,
|
|
@@ -64,6 +69,7 @@ from ctxprotocol.client.types import (
|
|
|
64
69
|
SearchResponse,
|
|
65
70
|
Tool,
|
|
66
71
|
ToolInfo,
|
|
72
|
+
AgentModelId,
|
|
67
73
|
)
|
|
68
74
|
|
|
69
75
|
__all__ = [
|
|
@@ -76,6 +82,9 @@ __all__ = [
|
|
|
76
82
|
"Query",
|
|
77
83
|
# Types
|
|
78
84
|
"ContextClientOptions",
|
|
85
|
+
"AGENT_MODEL_IDS",
|
|
86
|
+
"DEFAULT_AGENT_MODEL_ID",
|
|
87
|
+
"AgentModelId",
|
|
79
88
|
"Tool",
|
|
80
89
|
"McpTool",
|
|
81
90
|
"McpToolMeta",
|
|
@@ -105,6 +114,9 @@ __all__ = [
|
|
|
105
114
|
"QueryDeveloperTraceSummary",
|
|
106
115
|
"QueryDeveloperTraceStep",
|
|
107
116
|
"QueryDeveloperTraceToolRef",
|
|
117
|
+
"QueryJobStartResult",
|
|
118
|
+
"QueryJobStatus",
|
|
119
|
+
"QueryJobStatusResult",
|
|
108
120
|
"QueryOrchestrationMetrics",
|
|
109
121
|
"QueryDeveloperTraceLoopInfo",
|
|
110
122
|
"QueryApiSuccessResponse",
|
|
@@ -9,14 +9,18 @@ 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
|
|
|
15
16
|
from ctxprotocol.client.types import (
|
|
16
17
|
ContextError,
|
|
18
|
+
AgentModelId,
|
|
17
19
|
QueryAttemptReference,
|
|
18
20
|
QueryDeveloperTrace,
|
|
19
21
|
QueryForkReference,
|
|
22
|
+
QueryJobStartResult,
|
|
23
|
+
QueryJobStatusResult,
|
|
20
24
|
QueryResponseShape,
|
|
21
25
|
QueryResult,
|
|
22
26
|
QueryStreamDeveloperTraceEvent,
|
|
@@ -42,6 +46,52 @@ class Query:
|
|
|
42
46
|
"""
|
|
43
47
|
self._client = client
|
|
44
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
|
+
|
|
45
95
|
@staticmethod
|
|
46
96
|
def _merge_developer_trace(
|
|
47
97
|
first: QueryDeveloperTrace | None,
|
|
@@ -193,7 +243,7 @@ class Query:
|
|
|
193
243
|
query: str,
|
|
194
244
|
tools: list[str] | None = None,
|
|
195
245
|
favorites_only: bool | None = None,
|
|
196
|
-
|
|
246
|
+
agent_model_id: AgentModelId | str | None = None,
|
|
197
247
|
response_shape: QueryResponseShape | None = None,
|
|
198
248
|
include_data: bool | None = None,
|
|
199
249
|
include_data_url: bool | None = None,
|
|
@@ -215,7 +265,7 @@ class Query:
|
|
|
215
265
|
query: The natural-language question to answer
|
|
216
266
|
tools: Optional tool IDs to use (auto-discover if not provided)
|
|
217
267
|
favorites_only: Restrict auto-discovery to favorite tools for this request
|
|
218
|
-
|
|
268
|
+
agent_model_id: Optional model ID for the main librarian agent loop
|
|
219
269
|
response_shape: Structured response mode (`answer_with_evidence` or
|
|
220
270
|
`evidence_only`; server default `answer_with_evidence`)
|
|
221
271
|
include_data: Include execution data inline in the query response
|
|
@@ -253,7 +303,7 @@ class Query:
|
|
|
253
303
|
query=query,
|
|
254
304
|
tools=tools,
|
|
255
305
|
favorites_only=favorites_only,
|
|
256
|
-
|
|
306
|
+
agent_model_id=agent_model_id,
|
|
257
307
|
response_shape=response_shape,
|
|
258
308
|
include_data=include_data,
|
|
259
309
|
include_data_url=include_data_url,
|
|
@@ -277,12 +327,77 @@ class Query:
|
|
|
277
327
|
|
|
278
328
|
raise ContextError("Streaming query ended before done event")
|
|
279
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
|
+
|
|
280
395
|
async def stream(
|
|
281
396
|
self,
|
|
282
397
|
query: str,
|
|
283
398
|
tools: list[str] | None = None,
|
|
284
399
|
favorites_only: bool | None = None,
|
|
285
|
-
|
|
400
|
+
agent_model_id: AgentModelId | str | None = None,
|
|
286
401
|
response_shape: QueryResponseShape | None = None,
|
|
287
402
|
include_data: bool | None = None,
|
|
288
403
|
include_data_url: bool | None = None,
|
|
@@ -304,7 +419,7 @@ class Query:
|
|
|
304
419
|
query: The natural-language question to answer
|
|
305
420
|
tools: Optional tool IDs to use (auto-discover if not provided)
|
|
306
421
|
favorites_only: Restrict auto-discovery to favorite tools for this request
|
|
307
|
-
|
|
422
|
+
agent_model_id: Optional model ID for the main librarian agent loop
|
|
308
423
|
response_shape: Structured response mode (`answer_with_evidence` or
|
|
309
424
|
`evidence_only`; server default `answer_with_evidence`)
|
|
310
425
|
include_data: Include execution data inline in the query response
|
|
@@ -345,8 +460,8 @@ class Query:
|
|
|
345
460
|
)
|
|
346
461
|
if favorites_only is not None:
|
|
347
462
|
request_body["favoritesOnly"] = favorites_only
|
|
348
|
-
if
|
|
349
|
-
request_body["
|
|
463
|
+
if agent_model_id is not None:
|
|
464
|
+
request_body["agentModelId"] = agent_model_id
|
|
350
465
|
if response_shape is not None:
|
|
351
466
|
request_body["responseShape"] = response_shape
|
|
352
467
|
if include_data is not None:
|
|
@@ -43,6 +43,27 @@ McpToolSurface = Literal["answer", "execute", "both"]
|
|
|
43
43
|
McpToolLatencyClass = Literal["instant", "fast", "slow", "streaming"]
|
|
44
44
|
ExecuteSessionStatus = Literal["open", "closed", "expired"]
|
|
45
45
|
SuggestedPromptSource = Literal["contributor", "platform", "sdk"]
|
|
46
|
+
DEFAULT_AGENT_MODEL_ID = "kimi-k2.6-model"
|
|
47
|
+
AGENT_MODEL_IDS = (
|
|
48
|
+
"kimi-k2.6-model",
|
|
49
|
+
"glm-5.2-model",
|
|
50
|
+
"deepseek-v4-pro-model",
|
|
51
|
+
"deepseek-v4-flash-model",
|
|
52
|
+
"qwen-3.7-plus-model",
|
|
53
|
+
"qwen-3.7-max-model",
|
|
54
|
+
"gpt-5.5-model",
|
|
55
|
+
"claude-opus-model",
|
|
56
|
+
)
|
|
57
|
+
AgentModelId = Literal[
|
|
58
|
+
"kimi-k2.6-model",
|
|
59
|
+
"glm-5.2-model",
|
|
60
|
+
"deepseek-v4-pro-model",
|
|
61
|
+
"deepseek-v4-flash-model",
|
|
62
|
+
"qwen-3.7-plus-model",
|
|
63
|
+
"qwen-3.7-max-model",
|
|
64
|
+
"gpt-5.5-model",
|
|
65
|
+
"claude-opus-model",
|
|
66
|
+
]
|
|
46
67
|
|
|
47
68
|
|
|
48
69
|
class McpToolRateLimitHints(BaseModel):
|
|
@@ -674,7 +695,7 @@ class QueryOptions(BaseModel):
|
|
|
674
695
|
Attributes:
|
|
675
696
|
query: The natural-language question to answer
|
|
676
697
|
tools: Optional tool IDs to use (auto-discover if not provided)
|
|
677
|
-
|
|
698
|
+
agent_model_id: Optional model ID for the main librarian agent loop
|
|
678
699
|
include_data: Include execution data inline in the query response
|
|
679
700
|
include_data_url: Persist execution data to blob and return URL
|
|
680
701
|
include_developer_trace: Include machine-readable developer runtime traces
|
|
@@ -703,10 +724,14 @@ class QueryOptions(BaseModel):
|
|
|
703
724
|
"Ignored when explicit tools are provided."
|
|
704
725
|
),
|
|
705
726
|
)
|
|
706
|
-
|
|
727
|
+
agent_model_id: AgentModelId | str | None = Field(
|
|
707
728
|
default=None,
|
|
708
|
-
alias="
|
|
709
|
-
description=
|
|
729
|
+
alias="agentModelId",
|
|
730
|
+
description=(
|
|
731
|
+
"Optional model ID for the main librarian agent loop. Controls the "
|
|
732
|
+
"merged iterative execution + final response stage; internal tool "
|
|
733
|
+
"selection remains managed by the server."
|
|
734
|
+
),
|
|
710
735
|
)
|
|
711
736
|
response_shape: QueryResponseShape | None = Field(
|
|
712
737
|
default=None,
|
|
@@ -714,21 +739,21 @@ class QueryOptions(BaseModel):
|
|
|
714
739
|
description=(
|
|
715
740
|
"Structured response mode. Defaults to `answer_with_evidence` on the "
|
|
716
741
|
"server when omitted. The runtime always produces a grounded result "
|
|
717
|
-
"(
|
|
718
|
-
"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. "
|
|
719
744
|
"`answer_with_evidence` returns prose plus the structured grounding "
|
|
720
745
|
"(chat parity); `evidence_only` returns grounding only with no prose "
|
|
721
|
-
"(the agent-harness shape, with
|
|
722
|
-
"
|
|
746
|
+
"(the agent-harness shape, with bounded evidence, computed_artifacts, "
|
|
747
|
+
"and data_url/canonical_data_ref references)."
|
|
723
748
|
),
|
|
724
749
|
)
|
|
725
750
|
include_data: bool | None = Field(
|
|
726
751
|
default=None,
|
|
727
752
|
alias="includeData",
|
|
728
753
|
description=(
|
|
729
|
-
"Include
|
|
730
|
-
"response_shape
|
|
731
|
-
"
|
|
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."
|
|
732
757
|
),
|
|
733
758
|
)
|
|
734
759
|
include_data_url: bool | None = Field(
|
|
@@ -1672,7 +1697,7 @@ class QueryResult(BaseModel):
|
|
|
1672
1697
|
tools_used: Tools that were used to answer the query
|
|
1673
1698
|
cost: Cost breakdown
|
|
1674
1699
|
duration_ms: Total duration in milliseconds
|
|
1675
|
-
data:
|
|
1700
|
+
data: Bounded execution data when include_data is enabled
|
|
1676
1701
|
data_url: Optional blob URL for execution data (when include_data_url is enabled)
|
|
1677
1702
|
computed_artifacts: Optional chart artifacts emitted by code interpreter
|
|
1678
1703
|
developer_trace: Optional machine-readable Developer Mode trace
|
|
@@ -1690,9 +1715,9 @@ class QueryResult(BaseModel):
|
|
|
1690
1715
|
data: Any | None = Field(
|
|
1691
1716
|
default=None,
|
|
1692
1717
|
description=(
|
|
1693
|
-
"
|
|
1694
|
-
"
|
|
1695
|
-
"
|
|
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."
|
|
1696
1721
|
),
|
|
1697
1722
|
)
|
|
1698
1723
|
data_url: str | None = Field(
|
|
@@ -1861,6 +1886,40 @@ class QueryApiSuccessResponse(BaseModel):
|
|
|
1861
1886
|
model_config = {"populate_by_name": True}
|
|
1862
1887
|
|
|
1863
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
|
+
|
|
1864
1923
|
class QueryStreamToolStatusEvent(BaseModel):
|
|
1865
1924
|
"""Emitted when a tool starts or changes execution status."""
|
|
1866
1925
|
|
|
@@ -468,7 +468,7 @@ class TestQueryRun:
|
|
|
468
468
|
mock_stream.return_value = _make_done_stream_response(success_with_data)
|
|
469
469
|
result = await client.query.run(
|
|
470
470
|
query="Analyze whale activity",
|
|
471
|
-
|
|
471
|
+
agent_model_id="kimi-k2.6-model",
|
|
472
472
|
response_shape="answer_with_evidence",
|
|
473
473
|
include_data=True,
|
|
474
474
|
include_data_url=True,
|
|
@@ -482,7 +482,7 @@ class TestQueryRun:
|
|
|
482
482
|
"query": "Analyze whale activity",
|
|
483
483
|
"tools": None,
|
|
484
484
|
"stream": True,
|
|
485
|
-
"
|
|
485
|
+
"agentModelId": "kimi-k2.6-model",
|
|
486
486
|
"responseShape": "answer_with_evidence",
|
|
487
487
|
"includeData": True,
|
|
488
488
|
"includeDataUrl": True,
|
|
@@ -571,7 +571,9 @@ class TestQueryRun:
|
|
|
571
571
|
def test_query_options_supports_public_aliases(self) -> None:
|
|
572
572
|
"""QueryOptions accepts supported public aliases."""
|
|
573
573
|
trace_alias = QueryOptions(query="test", includeDeveloperTrace=True)
|
|
574
|
-
|
|
574
|
+
agent_model_alias = QueryOptions(
|
|
575
|
+
query="test", agentModelId="kimi-k2.6-model"
|
|
576
|
+
)
|
|
575
577
|
resume_alias = QueryOptions(
|
|
576
578
|
query="test",
|
|
577
579
|
resumeFrom={
|
|
@@ -596,10 +598,10 @@ class TestQueryRun:
|
|
|
596
598
|
assert (
|
|
597
599
|
trace_alias.model_dump(by_alias=True)["includeDeveloperTrace"] is True
|
|
598
600
|
)
|
|
599
|
-
assert
|
|
601
|
+
assert agent_model_alias.agent_model_id == "kimi-k2.6-model"
|
|
600
602
|
assert (
|
|
601
|
-
|
|
602
|
-
== "
|
|
603
|
+
agent_model_alias.model_dump(by_alias=True)["agentModelId"]
|
|
604
|
+
== "kimi-k2.6-model"
|
|
603
605
|
)
|
|
604
606
|
assert response_shape_alias.response_shape == "evidence_only"
|
|
605
607
|
assert (
|
|
@@ -1216,7 +1218,7 @@ class TestQueryStream:
|
|
|
1216
1218
|
events = []
|
|
1217
1219
|
async for event in client.query.stream(
|
|
1218
1220
|
query="test",
|
|
1219
|
-
|
|
1221
|
+
agent_model_id="claude-sonnet-model",
|
|
1220
1222
|
include_data=True,
|
|
1221
1223
|
include_data_url=True,
|
|
1222
1224
|
include_developer_trace=True,
|
|
@@ -1225,7 +1227,7 @@ class TestQueryStream:
|
|
|
1225
1227
|
|
|
1226
1228
|
call_kwargs = mock_stream.call_args
|
|
1227
1229
|
assert (
|
|
1228
|
-
call_kwargs[1]["json_body"]["
|
|
1230
|
+
call_kwargs[1]["json_body"]["agentModelId"]
|
|
1229
1231
|
== "claude-sonnet-model"
|
|
1230
1232
|
)
|
|
1231
1233
|
assert call_kwargs[1]["json_body"]["includeData"] is True
|
|
@@ -1464,3 +1466,104 @@ class TestQueryStream:
|
|
|
1464
1466
|
events.append(event)
|
|
1465
1467
|
|
|
1466
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.16.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.16.0 → ctxprotocol-0.18.0}/examples/server/hummingbot-contributor/env.example
RENAMED
|
File without changes
|
{ctxprotocol-0.16.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
|