ctxprotocol 0.21.0__tar.gz → 0.22.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.
Files changed (58) hide show
  1. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/CHANGELOG.md +5 -0
  2. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/PKG-INFO +1 -1
  3. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/__init__.py +1 -1
  4. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/types.py +3 -0
  5. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/pyproject.toml +1 -1
  6. ctxprotocol-0.22.0/scripts/hermes-parity-sanity-check.py +153 -0
  7. ctxprotocol-0.22.0/scripts/hermes-parity-stream-check.py +96 -0
  8. ctxprotocol-0.22.0/scripts/medium-query.txt +1 -0
  9. ctxprotocol-0.22.0/scripts/saudi-oil-query.txt +16 -0
  10. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/.codexignore +0 -0
  11. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/.gitignore +0 -0
  12. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/README.md +0 -0
  13. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/auth/__init__.py +0 -0
  14. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/__init__.py +0 -0
  15. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/client.py +0 -0
  16. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/resources/__init__.py +0 -0
  17. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/resources/developer.py +0 -0
  18. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/resources/discovery.py +0 -0
  19. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/resources/query.py +0 -0
  20. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/client/resources/tools.py +0 -0
  21. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/context/__init__.py +0 -0
  22. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/context/hyperliquid.py +0 -0
  23. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/context/polymarket.py +0 -0
  24. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/context/wallet.py +0 -0
  25. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/contrib/__init__.py +0 -0
  26. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/contrib/search/__init__.py +0 -0
  27. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/contrib/search/core.py +0 -0
  28. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/contrib/search/trace.py +0 -0
  29. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/contrib/search/types.py +0 -0
  30. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/contrib/search/validation.py +0 -0
  31. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/handshake/__init__.py +0 -0
  32. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/ctxprotocol/py.typed +0 -0
  33. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/docs/README.md +0 -0
  34. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/execute_client.py +0 -0
  35. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/polymarket_query_trace_validation.py +0 -0
  36. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/query_client.py +0 -0
  37. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/test_favorites_only.py +0 -0
  38. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/test_get_tool.py +0 -0
  39. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/two_surfaces_client.py +0 -0
  40. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/validation/generate_contributor_search_validation_artifacts.py +0 -0
  41. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/validation/named-regression-iran-boots-on-ground.json +0 -0
  42. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/validation/named-regression-supreme-court-tariffs.json +0 -0
  43. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/validation/shared-capability-miss-unsupported-venue.json +0 -0
  44. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/validation/shared-generic-overlap-best-match.json +0 -0
  45. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/client/validation/shared-still-ambiguous-shortlist.json +0 -0
  46. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/server/hummingbot-contributor/README.md +0 -0
  47. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/server/hummingbot-contributor/env.example +0 -0
  48. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/server/hummingbot-contributor/requirements.txt +0 -0
  49. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/examples/server/hummingbot-contributor/server.py +0 -0
  50. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/polymarket-query-trace-results-py.json +0 -0
  51. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/tests/__init__.py +0 -0
  52. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/tests/contrib_search_validation_cases.py +0 -0
  53. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/tests/test_client.py +0 -0
  54. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/tests/test_contrib_search.py +0 -0
  55. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/tests/test_discovery.py +0 -0
  56. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/tests/test_query.py +0 -0
  57. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/tests/test_tools.py +0 -0
  58. {ctxprotocol-0.21.0 → ctxprotocol-0.22.0}/uv.lock +0 -0
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.22.0
4
+
5
+ - Added `bounded_explicit_empty_result_guardrail` to the query controller stop-reason literal type so strict consumers no longer fail validation when the platform returns that live stop reason.
6
+ - Platform note (no type change): `computed_artifacts` is now capped at 4 per response by the shared artifact emission policy, so SDK/MCP consumers receive exactly the artifact list the web app displays for the identical run.
7
+
3
8
  ## 0.21.0
4
9
 
5
10
  - `client.query.run()` is now backed by the durable job path (`start()` + `poll()`) instead of a held-open SSE connection. One call now reliably covers the full 1800s hosted compute ceiling and survives transient connection drops — the "sometimes works, sometimes times out on hard queries" failure mode is gone. `run()` accepts optional `interval_ms` / `timeout_ms` keyword arguments.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxprotocol
3
- Version: 0.21.0
3
+ Version: 0.22.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
@@ -31,7 +31,7 @@ Example:
31
31
  For more information, visit: https://ctxprotocol.com
32
32
  """
33
33
 
34
- __version__ = "0.21.0"
34
+ __version__ = "0.22.0"
35
35
 
36
36
  # Re-export everything from client module
37
37
  from ctxprotocol.client import (
@@ -47,6 +47,7 @@ DEFAULT_AGENT_MODEL_ID = "kimi-k2.6-model"
47
47
  AGENT_MODEL_IDS = (
48
48
  "kimi-k2.6-model",
49
49
  "glm-5.2-model",
50
+ "grok-4.5-model",
50
51
  "deepseek-v4-pro-model",
51
52
  "deepseek-v4-flash-model",
52
53
  "qwen-3.7-plus-model",
@@ -57,6 +58,7 @@ AGENT_MODEL_IDS = (
57
58
  AgentModelId = Literal[
58
59
  "kimi-k2.6-model",
59
60
  "glm-5.2-model",
61
+ "grok-4.5-model",
60
62
  "deepseek-v4-pro-model",
61
63
  "deepseek-v4-flash-model",
62
64
  "qwen-3.7-plus-model",
@@ -1461,6 +1463,7 @@ QueryControllerStopReason = Literal[
1461
1463
  "bounded_runtime_budget",
1462
1464
  "bounded_same_endpoint_guardrail",
1463
1465
  "bounded_upstream_abort_guardrail",
1466
+ "bounded_explicit_empty_result_guardrail",
1464
1467
  "capability_miss",
1465
1468
  ]
1466
1469
  QueryControllerIssueClass = Literal[
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ctxprotocol"
7
- version = "0.21.0"
7
+ version = "0.22.0"
8
8
  description = "Official Python SDK for the Context Protocol - Discover and execute AI tools programmatically"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env python3
2
+ """Throwaway Hermes-parity sanity check for ctxprotocol 0.21.0.
3
+
4
+ Mirrors ~/.hermes/workspace/ctx-gtm/scripts/ctxprotocol-query.py:
5
+ - same ContextClient timeouts
6
+ - same query.run() kwargs (no agent_model_id)
7
+ - same output contract (JSON + CHART_URL + COST_USD)
8
+
9
+ Reads CONTEXT_API_KEY from context-sdk/.env.local (or env).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import json
16
+ import os
17
+ import re
18
+ import sys
19
+
20
+ SDK_QUERY_TIMEOUT = 1860 # Hermes: 1800s ceiling + 60s grace
21
+ FAVORITES_ONLY = os.environ.get("CTX_FAVORITES_ONLY", "true").strip().lower() in (
22
+ "true",
23
+ "1",
24
+ "yes",
25
+ )
26
+
27
+ DEFAULT_QUERY = (
28
+ "What is the current price of Bitcoin? One sentence answer, no charts needed."
29
+ )
30
+
31
+
32
+ def load_api_key() -> str:
33
+ candidates = [
34
+ os.environ.get("CONTEXT_API_KEY"),
35
+ _read_env_file(
36
+ os.path.join(
37
+ os.path.dirname(__file__),
38
+ "..",
39
+ "..",
40
+ "context-sdk",
41
+ ".env.local",
42
+ )
43
+ ),
44
+ _read_env_file(
45
+ os.path.expanduser("~/.hermes/.env"),
46
+ ),
47
+ ]
48
+ for value in candidates:
49
+ if value:
50
+ return value
51
+ print(
52
+ "ERROR: CONTEXT_API_KEY not found in env, context-sdk/.env.local, or ~/.hermes/.env",
53
+ file=sys.stderr,
54
+ )
55
+ sys.exit(1)
56
+
57
+
58
+ def _read_env_file(path: str) -> str | None:
59
+ try:
60
+ with open(os.path.abspath(path)) as handle:
61
+ for line in handle:
62
+ line = line.strip()
63
+ if line.startswith("CONTEXT_API_KEY="):
64
+ return line.split("=", 1)[1].strip().strip('"').strip("'")
65
+ except OSError:
66
+ return None
67
+ return None
68
+
69
+
70
+ def _chart_urls_from_result(ans) -> list[str]:
71
+ urls: list[str] = []
72
+ for art in getattr(ans, "computed_artifacts", None) or []:
73
+ if getattr(art, "kind", None) == "image":
74
+ url = getattr(art, "url", None)
75
+ if url:
76
+ urls.append(url)
77
+ if not urls:
78
+ for text in (getattr(ans, "response", None), getattr(ans, "summary", None)):
79
+ if not text:
80
+ continue
81
+ for match in re.finditer(r"!\[.*?\]\((https?://[^)\s]+)\)", text):
82
+ urls.append(match.group(1))
83
+ seen: set[str] = set()
84
+ deduped: list[str] = []
85
+ for url in urls:
86
+ if url not in seen:
87
+ seen.add(url)
88
+ deduped.append(url)
89
+ return deduped
90
+
91
+
92
+ def emit(ans) -> None:
93
+ cost = getattr(ans, "cost", None)
94
+ total = getattr(cost, "total_cost_usd", None) if cost else None
95
+ payload = {
96
+ "response": getattr(ans, "response", None),
97
+ "summary": getattr(ans, "summary", None),
98
+ "cost_usd": total,
99
+ "data_url": getattr(ans, "data_url", None),
100
+ "tools_used": [
101
+ {"name": getattr(tool, "name", None), "id": getattr(tool, "id", None)}
102
+ for tool in (getattr(ans, "tools_used", None) or [])
103
+ ],
104
+ "outcome_type": getattr(ans, "outcome_type", None),
105
+ "stop_reason": getattr(ans, "stop_reason", None),
106
+ "duration_ms": getattr(ans, "duration_ms", None),
107
+ }
108
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
109
+
110
+ chart_urls = _chart_urls_from_result(ans)
111
+ if chart_urls:
112
+ for index, url in enumerate(chart_urls, start=1):
113
+ print(f"CHART_URL_{index}: {url}")
114
+ else:
115
+ print("CHART_URL_1: none")
116
+ print(f"COST_USD: {total if total is not None else 'unknown'}")
117
+
118
+
119
+ async def run_hermes_style_query(question: str) -> None:
120
+ from ctxprotocol import ContextClient, __version__
121
+
122
+ print(f"ctxprotocol version: {__version__}", file=sys.stderr)
123
+ print(f"favorites_only: {FAVORITES_ONLY}", file=sys.stderr)
124
+ print(f"query: {question[:120]}{'...' if len(question) > 120 else ''}", file=sys.stderr)
125
+
126
+ async with ContextClient(
127
+ api_key=load_api_key(),
128
+ request_timeout_seconds=float(SDK_QUERY_TIMEOUT),
129
+ stream_timeout_seconds=float(SDK_QUERY_TIMEOUT),
130
+ ) as client:
131
+ answer = await client.query.run(
132
+ query=question,
133
+ response_shape="answer_with_evidence",
134
+ include_data=False,
135
+ include_data_url=True,
136
+ favorites_only=FAVORITES_ONLY,
137
+ )
138
+ emit(answer)
139
+
140
+
141
+ def main() -> None:
142
+ question = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_QUERY
143
+ try:
144
+ asyncio.run(run_hermes_style_query(question))
145
+ except Exception as exc: # noqa: BLE001
146
+ print(json.dumps({"error": f"{type(exc).__name__}: {exc}"}, indent=2))
147
+ print("CHART_URL_1: none")
148
+ print("COST_USD: unknown")
149
+ sys.exit(1)
150
+
151
+
152
+ if __name__ == "__main__":
153
+ main()
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+ """Throwaway: same Hermes kwargs but via stream() (direct /api/v1/query SSE).
3
+
4
+ Use to compare job-backed run() vs held-open sync path on long queries.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import json
11
+ import os
12
+ import sys
13
+ import time
14
+
15
+ SDK_QUERY_TIMEOUT = 1860
16
+ FAVORITES_ONLY = True
17
+
18
+
19
+ def load_api_key() -> str:
20
+ path = os.path.abspath(
21
+ os.path.join(os.path.dirname(__file__), "..", "..", "context-sdk", ".env.local")
22
+ )
23
+ with open(path) as handle:
24
+ for line in handle:
25
+ if line.startswith("CONTEXT_API_KEY="):
26
+ return line.split("=", 1)[1].strip().strip('"').strip("'")
27
+ raise SystemExit("CONTEXT_API_KEY missing")
28
+
29
+
30
+ async def main() -> None:
31
+ query_path = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
32
+ os.path.dirname(__file__), "saudi-oil-query.txt"
33
+ )
34
+ with open(query_path) as handle:
35
+ query = handle.read().strip()
36
+
37
+ from ctxprotocol import ContextClient, __version__
38
+
39
+ print(f"stream path | ctxprotocol {__version__}", file=sys.stderr)
40
+ started = time.time()
41
+ async with ContextClient(
42
+ api_key=load_api_key(),
43
+ request_timeout_seconds=float(SDK_QUERY_TIMEOUT),
44
+ stream_timeout_seconds=float(SDK_QUERY_TIMEOUT),
45
+ ) as client:
46
+ result = None
47
+ async for event in client.query.stream(
48
+ query=query,
49
+ response_shape="answer_with_evidence",
50
+ include_data=False,
51
+ include_data_url=True,
52
+ favorites_only=FAVORITES_ONLY,
53
+ ):
54
+ if event.type == "done":
55
+ result = event.result
56
+ elif event.type == "error":
57
+ raise RuntimeError(getattr(event, "message", str(event)))
58
+
59
+ elapsed = time.time() - started
60
+ cost = getattr(getattr(result, "cost", None), "total_cost_usd", None)
61
+ charts = [
62
+ getattr(art, "url", None)
63
+ for art in (getattr(result, "computed_artifacts", None) or [])
64
+ if getattr(art, "kind", None) == "image" and getattr(art, "url", None)
65
+ ]
66
+ print(
67
+ json.dumps(
68
+ {
69
+ "elapsed_s": round(elapsed, 1),
70
+ "stop_reason": getattr(result, "stop_reason", None),
71
+ "cost_usd": cost,
72
+ "chart_count": len(charts),
73
+ "tools_used": [
74
+ getattr(t, "name", None)
75
+ for t in (getattr(result, "tools_used", None) or [])
76
+ ],
77
+ "response_preview": (getattr(result, "response", None) or "")[:800],
78
+ },
79
+ indent=2,
80
+ )
81
+ )
82
+ for index, url in enumerate(charts, start=1):
83
+ print(f"CHART_URL_{index}: {url}")
84
+ if not charts:
85
+ print("CHART_URL_1: none")
86
+ print(f"COST_USD: {cost}")
87
+
88
+
89
+ if __name__ == "__main__":
90
+ try:
91
+ asyncio.run(main())
92
+ except Exception as exc: # noqa: BLE001
93
+ print(json.dumps({"error": f"{type(exc).__name__}: {exc}"}, indent=2))
94
+ print("CHART_URL_1: none")
95
+ print("COST_USD: unknown")
96
+ sys.exit(1)
@@ -0,0 +1 @@
1
+ Compare the current price and 7-day trend of Bitcoin and Ethereum, and find one prediction market related to Bitcoin's price this year with its current odds. Provide a short verdict on whether crypto positioning looks bullish or bearish. Text only, no charts needed.
@@ -0,0 +1,16 @@
1
+ A news outlet reported: Saudi Arabia slashed the August price of its flagship Arab Light crude for Asia by $11 a barrel — the biggest reduction in at least 26 years. The move reflects weaker oil markets, rising global supply, and intensifying competition among Gulf producers.
2
+
3
+ I'm crafting a single tweet for a data-driven Twitter account (@ctxprotocol). The tweet needs one compelling data point and a chart that makes someone stop scrolling.
4
+
5
+ Search your available tools for data relevant to this story. Use whatever tools fit best — prediction markets, equities intelligence, crypto data, crypto positioning, trading intelligence, or any combination. You don't need to use every tool; pick the ones that produce the most insight for THIS specific story.
6
+
7
+ For each relevant market or asset you find, I need:
8
+ - Current key metrics (oil price, energy stock price, odds, spread, flow — whatever the tool provides)
9
+ - How has the key metric moved over the market's lifetime or let the backend determine the meaningful time range
10
+ - Positioning/flow: who is on which side? Is smart money aligned with or against the narrative?
11
+ - Catalyst date: when does this market resolve, or when is the next event that could move it? Include for every market/asset found.
12
+ - Follow-up assessment: is this worth revisiting in a future post? What event would trigger a followup?
13
+
14
+ Deliver a verdict: Was the market surprised by this news, or was it already priced in? What is the single most surprising data point? If you synthesized across multiple tools, what does the combined picture reveal that no single tool shows alone?
15
+
16
+ Analysis and Chart: You are a master quantitative analyst. Based on all the data you've gathered, what is the most meaningful analysis? Create the most compelling possible chart(s). When the data spans multiple distinct dimensions, splitting into focused charts often produces clearer visuals. Each chart should capture one dimension of the story clearly. Charts must have clean labels, readable legends, and professional styling. Include a labeled chart summary for EACH chart produced, describing what data it shows and the key visual takeaway.
File without changes
File without changes
File without changes
File without changes