snowflake-cortex-agent-sdk 0.0.2__tar.gz → 0.0.3__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 (65) hide show
  1. snowflake_cortex_agent_sdk-0.0.3/PKG-INFO +203 -0
  2. snowflake_cortex_agent_sdk-0.0.3/README.md +175 -0
  3. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/__init__.py +5 -1
  4. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_base_client.py +64 -7
  5. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_constants.py +5 -0
  6. snowflake_cortex_agent_sdk-0.0.3/cortex_agent_sdk/_partner_attribution.py +18 -0
  7. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_request_options.py +16 -1
  8. snowflake_cortex_agent_sdk-0.0.3/cortex_agent_sdk/_telemetry.py +24 -0
  9. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_transport.py +3 -2
  10. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_version.py +1 -1
  11. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/client.py +31 -5
  12. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/conversation.py +217 -22
  13. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/_internal/query.py +22 -0
  14. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/_internal/transport/subprocess_cli.py +10 -0
  15. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/client.py +28 -0
  16. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/types.py +11 -0
  17. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/resources/agent.py +62 -23
  18. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/resources/coding_agent.py +17 -5
  19. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/resources/feedback.py +17 -7
  20. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/resources/runs.py +15 -7
  21. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/resources/threads.py +52 -34
  22. snowflake_cortex_agent_sdk-0.0.3/cortex_agent_sdk/testing.py +98 -0
  23. snowflake_cortex_agent_sdk-0.0.3/cortex_agent_sdk/turn.py +192 -0
  24. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/views/async_run.py +20 -5
  25. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/pyproject.toml +1 -1
  26. snowflake_cortex_agent_sdk-0.0.2/PKG-INFO +0 -118
  27. snowflake_cortex_agent_sdk-0.0.2/README.md +0 -90
  28. snowflake_cortex_agent_sdk-0.0.2/cortex_agent_sdk/turn.py +0 -85
  29. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/.gitignore +0 -0
  30. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/LICENSE +0 -0
  31. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_generated/__init__.py +0 -0
  32. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_generated/agent_runs.py +0 -0
  33. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_generated/data_agent.py +0 -0
  34. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_generated/events.py +0 -0
  35. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_generated/lite_agent.py +0 -0
  36. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_generated/threads.py +0 -0
  37. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_resource.py +0 -0
  38. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/_types.py +0 -0
  39. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/api.py +0 -0
  40. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/api_response.py +0 -0
  41. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/auth.py +0 -0
  42. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/constants.py +0 -0
  43. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/__init__.py +0 -0
  44. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/_errors.py +0 -0
  45. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/_internal/__init__.py +0 -0
  46. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/_internal/message_parser.py +0 -0
  47. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/_internal/transport/__init__.py +0 -0
  48. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/fork_session.py +0 -0
  49. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/py.typed +0 -0
  50. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/cortexcode/query.py +0 -0
  51. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/errors.py +0 -0
  52. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/models.py +0 -0
  53. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/origin.py +0 -0
  54. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/public_client.py +0 -0
  55. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/py.typed +0 -0
  56. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/resources/__init__.py +0 -0
  57. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/stream.py +0 -0
  58. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/streaming/__init__.py +0 -0
  59. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/streaming/parser.py +0 -0
  60. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/streaming/run_stream.py +0 -0
  61. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/threads_page.py +0 -0
  62. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/views/__init__.py +0 -0
  63. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/views/agent_response.py +0 -0
  64. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/views/content_view.py +0 -0
  65. {snowflake_cortex_agent_sdk-0.0.2 → snowflake_cortex_agent_sdk-0.0.3}/cortex_agent_sdk/views/thread_message.py +0 -0
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.5
2
+ Name: snowflake-cortex-agent-sdk
3
+ Version: 0.0.3
4
+ Summary: Python SDK for the Snowflake Cortex Agent REST API (agent:run streaming, threads, feedback)
5
+ Author-email: "Snowflake, Inc." <support@snowflake.com>
6
+ License: See LICENSE
7
+ License-File: LICENSE
8
+ Keywords: cortex,cortex-agent,openapi,sdk,snowflake
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: anyio>=4.0
14
+ Requires-Dist: httpx-sse>=0.4
15
+ Requires-Dist: httpx>=0.27
16
+ Requires-Dist: mcp<2,>=1.0
17
+ Requires-Dist: pydantic>=2.0
18
+ Requires-Dist: snowflake-connector-python[secure-local-storage]<5,>=4.7.1
19
+ Requires-Dist: typing-extensions>=4.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: datamodel-code-generator>=0.28; extra == 'dev'
22
+ Requires-Dist: mypy>=1.0; extra == 'dev'
23
+ Requires-Dist: pytest-asyncio>=0.20.0; extra == 'dev'
24
+ Requires-Dist: pytest-timeout>=2.0.0; extra == 'dev'
25
+ Requires-Dist: pytest>=8.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.6; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Snowflake Cortex Agent SDK for Python
30
+
31
+ Python SDK for the Snowflake Cortex Agent REST API. Covers the current public surface:
32
+
33
+ - Durable lite, data-agent, and coding-agent conversations
34
+ - Named + versioned data agents through the direct API client
35
+ - Coding-agent runs with sandbox, bash, skills, and workspace mounts
36
+ - Threads, background runs, streaming, and feedback through `cortex_agent_sdk.api`
37
+ - High-level `Conversation.send()` -> `Turn` abstraction
38
+ - Route-test helpers in `cortex_agent_sdk.testing` (see `docs/13-testing.md`)
39
+ - Curated `AgentResponse` content and usage view
40
+ - Synchronous and asyncio clients
41
+ - Claude-shaped local Cortex Code sessions via `cortex_agent_sdk.cortexcode`
42
+
43
+ ## Install
44
+
45
+ [View `snowflake-cortex-agent-sdk` on PyPI](https://pypi.org/project/snowflake-cortex-agent-sdk/).
46
+
47
+ ```bash
48
+ pip install snowflake-cortex-agent-sdk
49
+ ```
50
+
51
+ Requires Python 3.10 or newer.
52
+
53
+ ## Quickstart
54
+
55
+ ```python
56
+ import os
57
+ from cortex_agent_sdk import (
58
+ AgentResponse,
59
+ Conversation,
60
+ ConversationMessage,
61
+ CortexAgentClient,
62
+ Turn,
63
+ )
64
+
65
+ client: CortexAgentClient = CortexAgentClient(
66
+ account="my_account",
67
+ auth=os.environ["SNOWFLAKE_PAT"],
68
+ )
69
+
70
+ conversation: Conversation = client.conversations.create(
71
+ origin_application="cortex_agent_sdk",
72
+ agent={"type": "inline", "spec": {"models": {"orchestration": "claude-sonnet-4-5"}}},
73
+ )
74
+
75
+ turn: Turn = conversation.send("What is Snowflake Arctic?")
76
+ print(f"run: {turn.id}")
77
+
78
+ answer: AgentResponse = turn.result()
79
+ print(answer.text)
80
+
81
+ history: list[ConversationMessage] = conversation.history()
82
+ ```
83
+
84
+ Set a default agent when creating a conversation. A send can override it for one
85
+ turn when needed:
86
+
87
+ ```python
88
+ inline_turn: Turn = conversation.send(
89
+ "Summarize the context.",
90
+ agent={"type": "inline", "spec": {"models": {"orchestration": "auto"}}},
91
+ )
92
+
93
+ data_turn: Turn = conversation.send(
94
+ "Which products grew fastest?",
95
+ agent={
96
+ "type": "data",
97
+ "spec": {"database": "ANALYTICS", "schema": "PUBLIC", "name": "SALES_AGENT"},
98
+ },
99
+ )
100
+
101
+ coding_turn: Turn = conversation.send(
102
+ "Fix the failing test.",
103
+ agent={
104
+ "type": "coding",
105
+ "spec": {
106
+ "models": {"orchestration": "claude-sonnet-4-5"},
107
+ "permission_policy": "always_allow",
108
+ "workspace_mounts": [{"name": "USER$MY_USER.PUBLIC.DEFAULT$"}],
109
+ },
110
+ },
111
+ )
112
+ ```
113
+
114
+ Use `turn.events()` for event-level progress and `turn.cancel()` to stop a run.
115
+
116
+ ## Async
117
+
118
+ The asynchronous API mirrors the sync API:
119
+
120
+ ```python
121
+ from cortex_agent_sdk import AgentResponse, AsyncConversation, AsyncCortexAgentClient, AsyncTurn
122
+
123
+ async with AsyncCortexAgentClient(account="my_account", auth="<token>") as client:
124
+ conversation: AsyncConversation = await client.conversations.create(
125
+ origin_application="cortex_agent_sdk",
126
+ agent={"type": "inline", "spec": {"models": {"orchestration": "auto"}}},
127
+ )
128
+ turn: AsyncTurn = await conversation.send("Analyze this data.")
129
+ answer: AgentResponse = await turn.result()
130
+ ```
131
+
132
+ ## Cortex Code sessions (`cortex_agent_sdk.cortexcode`)
133
+
134
+ `cortex_agent_sdk.cortexcode` is the Cortex Code SDK for driving the local
135
+ `cortex` binary from Python. It exposes one-shot `query()` calls and long-lived
136
+ `CortexCodeSDKClient` sessions with permissions, hooks, MCP, plugins, structured
137
+ output, and session controls.
138
+
139
+ ```python
140
+ import asyncio
141
+ from cortex_agent_sdk.cortexcode import AssistantMessage, ResultMessage, query
142
+
143
+ async def main() -> None:
144
+ async for message in query(prompt="Summarize this repository"):
145
+ if isinstance(message, AssistantMessage):
146
+ for block in message.content:
147
+ if hasattr(block, "text"):
148
+ print(block.text, end="")
149
+ elif isinstance(message, ResultMessage):
150
+ print(f"\nDone: {message.subtype}")
151
+
152
+ asyncio.run(main())
153
+ ```
154
+
155
+ This entry point runs the local Cortex Code CLI. For server-side integrations,
156
+ use the package-root `CortexAgentClient` with a coding-agent conversation
157
+ instead.
158
+
159
+ ## Resources at a glance
160
+
161
+ | Resource | Purpose |
162
+ | --- | --- |
163
+ | `client.conversations` | Create, attach, list, and search conversations |
164
+ | `client.feedback` | Send feedback for a data-agent turn |
165
+
166
+ `Conversation` binds a thread to an inline, data, or coding agent and hides
167
+ message and run plumbing. `send()` returns a `Turn` with `id`, `result()`,
168
+ `events()`, and `cancel()`.
169
+
170
+ For direct REST request shapes, streaming modes, raw response wrappers, or SSE
171
+ parsing, import `CortexAgentClient` and related types from
172
+ `cortex_agent_sdk.api`.
173
+
174
+ Separate entry point, not a `client.*` resource:
175
+
176
+ | Entry point | Purpose |
177
+ | --- | --- |
178
+ | `cortex_agent_sdk.cortexcode` | Python SDK for the local Cortex Code CLI: permissions, hooks, MCP, plugins, and session controls |
179
+
180
+ ## Auth at a glance
181
+
182
+ Authentication headers are rebuilt for every attempt. `refresh_auth` runs only
183
+ for recognized token/session expiry responses, including legacy `200` JSON error
184
+ envelopes; by default the SDK refreshes once
185
+ and replays the request once. SDK-generated errors do not include credential
186
+ values.
187
+
188
+ | Class | For | Header shape |
189
+ | --- | --- | --- |
190
+ | `PatAuth` (or string shorthand) | Programmatic Access Token | `Authorization: Bearer <token>` + `X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN` |
191
+ | `OAuthAuth` | External OAuth / Snowflake OAuth access tokens | `Authorization: Bearer <token>` + `X-Snowflake-Authorization-Token-Type: OAUTH` |
192
+ | `KeyPairJwtAuth` | Key-pair JWT auth | `Authorization: Bearer <token>` + `X-Snowflake-Authorization-Token-Type: KEYPAIR_JWT` |
193
+ | `SnowflakeSessionTokenAuth` | `snowflake-connector-python` session tokens | `Authorization: Snowflake Token="<token>"` |
194
+ | `CallableAuth` | Lazy token minting | Bearer + configurable token-type header |
195
+ | Custom `Authenticator` | Anything else | You return the header map |
196
+
197
+ ## Status
198
+
199
+ Preview. Expect breaking changes as the underlying API stabilizes.
200
+
201
+ ## License
202
+
203
+ See `LICENSE`. Use is governed by your Snowflake customer agreement.
@@ -0,0 +1,175 @@
1
+ # Snowflake Cortex Agent SDK for Python
2
+
3
+ Python SDK for the Snowflake Cortex Agent REST API. Covers the current public surface:
4
+
5
+ - Durable lite, data-agent, and coding-agent conversations
6
+ - Named + versioned data agents through the direct API client
7
+ - Coding-agent runs with sandbox, bash, skills, and workspace mounts
8
+ - Threads, background runs, streaming, and feedback through `cortex_agent_sdk.api`
9
+ - High-level `Conversation.send()` -> `Turn` abstraction
10
+ - Route-test helpers in `cortex_agent_sdk.testing` (see `docs/13-testing.md`)
11
+ - Curated `AgentResponse` content and usage view
12
+ - Synchronous and asyncio clients
13
+ - Claude-shaped local Cortex Code sessions via `cortex_agent_sdk.cortexcode`
14
+
15
+ ## Install
16
+
17
+ [View `snowflake-cortex-agent-sdk` on PyPI](https://pypi.org/project/snowflake-cortex-agent-sdk/).
18
+
19
+ ```bash
20
+ pip install snowflake-cortex-agent-sdk
21
+ ```
22
+
23
+ Requires Python 3.10 or newer.
24
+
25
+ ## Quickstart
26
+
27
+ ```python
28
+ import os
29
+ from cortex_agent_sdk import (
30
+ AgentResponse,
31
+ Conversation,
32
+ ConversationMessage,
33
+ CortexAgentClient,
34
+ Turn,
35
+ )
36
+
37
+ client: CortexAgentClient = CortexAgentClient(
38
+ account="my_account",
39
+ auth=os.environ["SNOWFLAKE_PAT"],
40
+ )
41
+
42
+ conversation: Conversation = client.conversations.create(
43
+ origin_application="cortex_agent_sdk",
44
+ agent={"type": "inline", "spec": {"models": {"orchestration": "claude-sonnet-4-5"}}},
45
+ )
46
+
47
+ turn: Turn = conversation.send("What is Snowflake Arctic?")
48
+ print(f"run: {turn.id}")
49
+
50
+ answer: AgentResponse = turn.result()
51
+ print(answer.text)
52
+
53
+ history: list[ConversationMessage] = conversation.history()
54
+ ```
55
+
56
+ Set a default agent when creating a conversation. A send can override it for one
57
+ turn when needed:
58
+
59
+ ```python
60
+ inline_turn: Turn = conversation.send(
61
+ "Summarize the context.",
62
+ agent={"type": "inline", "spec": {"models": {"orchestration": "auto"}}},
63
+ )
64
+
65
+ data_turn: Turn = conversation.send(
66
+ "Which products grew fastest?",
67
+ agent={
68
+ "type": "data",
69
+ "spec": {"database": "ANALYTICS", "schema": "PUBLIC", "name": "SALES_AGENT"},
70
+ },
71
+ )
72
+
73
+ coding_turn: Turn = conversation.send(
74
+ "Fix the failing test.",
75
+ agent={
76
+ "type": "coding",
77
+ "spec": {
78
+ "models": {"orchestration": "claude-sonnet-4-5"},
79
+ "permission_policy": "always_allow",
80
+ "workspace_mounts": [{"name": "USER$MY_USER.PUBLIC.DEFAULT$"}],
81
+ },
82
+ },
83
+ )
84
+ ```
85
+
86
+ Use `turn.events()` for event-level progress and `turn.cancel()` to stop a run.
87
+
88
+ ## Async
89
+
90
+ The asynchronous API mirrors the sync API:
91
+
92
+ ```python
93
+ from cortex_agent_sdk import AgentResponse, AsyncConversation, AsyncCortexAgentClient, AsyncTurn
94
+
95
+ async with AsyncCortexAgentClient(account="my_account", auth="<token>") as client:
96
+ conversation: AsyncConversation = await client.conversations.create(
97
+ origin_application="cortex_agent_sdk",
98
+ agent={"type": "inline", "spec": {"models": {"orchestration": "auto"}}},
99
+ )
100
+ turn: AsyncTurn = await conversation.send("Analyze this data.")
101
+ answer: AgentResponse = await turn.result()
102
+ ```
103
+
104
+ ## Cortex Code sessions (`cortex_agent_sdk.cortexcode`)
105
+
106
+ `cortex_agent_sdk.cortexcode` is the Cortex Code SDK for driving the local
107
+ `cortex` binary from Python. It exposes one-shot `query()` calls and long-lived
108
+ `CortexCodeSDKClient` sessions with permissions, hooks, MCP, plugins, structured
109
+ output, and session controls.
110
+
111
+ ```python
112
+ import asyncio
113
+ from cortex_agent_sdk.cortexcode import AssistantMessage, ResultMessage, query
114
+
115
+ async def main() -> None:
116
+ async for message in query(prompt="Summarize this repository"):
117
+ if isinstance(message, AssistantMessage):
118
+ for block in message.content:
119
+ if hasattr(block, "text"):
120
+ print(block.text, end="")
121
+ elif isinstance(message, ResultMessage):
122
+ print(f"\nDone: {message.subtype}")
123
+
124
+ asyncio.run(main())
125
+ ```
126
+
127
+ This entry point runs the local Cortex Code CLI. For server-side integrations,
128
+ use the package-root `CortexAgentClient` with a coding-agent conversation
129
+ instead.
130
+
131
+ ## Resources at a glance
132
+
133
+ | Resource | Purpose |
134
+ | --- | --- |
135
+ | `client.conversations` | Create, attach, list, and search conversations |
136
+ | `client.feedback` | Send feedback for a data-agent turn |
137
+
138
+ `Conversation` binds a thread to an inline, data, or coding agent and hides
139
+ message and run plumbing. `send()` returns a `Turn` with `id`, `result()`,
140
+ `events()`, and `cancel()`.
141
+
142
+ For direct REST request shapes, streaming modes, raw response wrappers, or SSE
143
+ parsing, import `CortexAgentClient` and related types from
144
+ `cortex_agent_sdk.api`.
145
+
146
+ Separate entry point, not a `client.*` resource:
147
+
148
+ | Entry point | Purpose |
149
+ | --- | --- |
150
+ | `cortex_agent_sdk.cortexcode` | Python SDK for the local Cortex Code CLI: permissions, hooks, MCP, plugins, and session controls |
151
+
152
+ ## Auth at a glance
153
+
154
+ Authentication headers are rebuilt for every attempt. `refresh_auth` runs only
155
+ for recognized token/session expiry responses, including legacy `200` JSON error
156
+ envelopes; by default the SDK refreshes once
157
+ and replays the request once. SDK-generated errors do not include credential
158
+ values.
159
+
160
+ | Class | For | Header shape |
161
+ | --- | --- | --- |
162
+ | `PatAuth` (or string shorthand) | Programmatic Access Token | `Authorization: Bearer <token>` + `X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN` |
163
+ | `OAuthAuth` | External OAuth / Snowflake OAuth access tokens | `Authorization: Bearer <token>` + `X-Snowflake-Authorization-Token-Type: OAUTH` |
164
+ | `KeyPairJwtAuth` | Key-pair JWT auth | `Authorization: Bearer <token>` + `X-Snowflake-Authorization-Token-Type: KEYPAIR_JWT` |
165
+ | `SnowflakeSessionTokenAuth` | `snowflake-connector-python` session tokens | `Authorization: Snowflake Token="<token>"` |
166
+ | `CallableAuth` | Lazy token minting | Bearer + configurable token-type header |
167
+ | Custom `Authenticator` | Anything else | You return the header map |
168
+
169
+ ## Status
170
+
171
+ Preview. Expect breaking changes as the underlying API stabilizes.
172
+
173
+ ## License
174
+
175
+ See `LICENSE`. Use is governed by your Snowflake customer agreement.
@@ -55,6 +55,7 @@ from .conversation import (
55
55
  ConversationMessage,
56
56
  DataAgentSpec,
57
57
  InlineAgentSpec,
58
+ PermissionDecision,
58
59
  )
59
60
  from .errors import (
60
61
  APIConnectionAbortedError,
@@ -74,7 +75,7 @@ from .errors import (
74
75
  UnprocessableEntityError,
75
76
  )
76
77
  from .public_client import AsyncCortexAgentClient, CortexAgentClient
77
- from .turn import AsyncTurn, Turn
78
+ from .turn import AsyncTurn, ClientToolUse, PermissionRequest, Turn
78
79
  from .views.agent_response import AgentResponseView as AgentResponse
79
80
 
80
81
  __all__ = [
@@ -89,6 +90,7 @@ __all__ = [
89
90
  "ConversationInput",
90
91
  "ConversationMessage",
91
92
  "CodingAgentSpec",
93
+ "ClientToolUse",
92
94
  "DataAgentSpec",
93
95
  "InlineAgentSpec",
94
96
  "CortexAgentClient",
@@ -101,6 +103,8 @@ __all__ = [
101
103
  "KeyPairJwtAuth",
102
104
  "OAuthAuth",
103
105
  "PatAuth",
106
+ "PermissionRequest",
107
+ "PermissionDecision",
104
108
  "SnowflakeSessionTokenAuth",
105
109
  "SnowflakeTokenAuth",
106
110
  "SnowflakeTokenType",
@@ -24,6 +24,7 @@ import platform
24
24
  import random
25
25
  import time
26
26
  from collections.abc import Mapping
27
+ from dataclasses import replace
27
28
  from typing import Any
28
29
  from urllib.parse import urlparse
29
30
 
@@ -31,6 +32,7 @@ import httpx
31
32
 
32
33
  from ._constants import (
33
34
  DEFAULT_MAX_RETRIES,
35
+ DEFAULT_STREAM_READ_TIMEOUT,
34
36
  DEFAULT_TIMEOUT,
35
37
  INITIAL_RETRY_DELAY,
36
38
  MAX_RETRY_AFTER_DELAY,
@@ -39,7 +41,9 @@ from ._constants import (
39
41
  RETRY_STATUSES,
40
42
  SHOULD_RETRY_HEADER,
41
43
  )
44
+ from ._partner_attribution import validate_partner_application
42
45
  from ._request_options import RequestOptions, RequestOptionsLike, normalize_options
46
+ from ._telemetry import SDK_OPERATION_HEADER, identity_headers
43
47
  from ._transport import (
44
48
  AuthRefreshContext,
45
49
  RetryPolicy,
@@ -47,7 +51,7 @@ from ._transport import (
47
51
  is_transport_retry_safe_by_default,
48
52
  merge_retry_policies,
49
53
  )
50
- from ._types import NotGiven, Omit, given_or
54
+ from ._types import NotGiven, Omit, given_or, is_given, not_given
51
55
  from ._version import __version__
52
56
  from .api_response import APIResponse
53
57
  from .auth import Authenticator, coerce_authenticator
@@ -78,11 +82,14 @@ class BaseClient:
78
82
  base_url: str | None,
79
83
  auth: str | Authenticator | Any,
80
84
  timeout: float | httpx.Timeout | None | NotGiven,
85
+ stream_read_timeout: float | None | NotGiven = not_given,
81
86
  retry_policy: RetryPolicy | None,
82
87
  refresh_auth: Any,
83
88
  auth_refresh_max_retries: int,
84
89
  default_headers: Mapping[str, str] | None,
85
90
  default_query: Mapping[str, Any] | None,
91
+ partner_application: str | None = None,
92
+ sdk_surface: str = "agent",
86
93
  ) -> None:
87
94
  if base_url is None and account is None:
88
95
  raise CortexAgentError("Either `account` or `base_url` is required.")
@@ -102,7 +109,37 @@ class BaseClient:
102
109
  self.refresh_auth = refresh_auth
103
110
  self.auth_refresh_max_retries = auth_refresh_max_retries
104
111
  self.timeout: float | httpx.Timeout | None = given_or(timeout, DEFAULT_TIMEOUT)
105
- self.default_headers: dict[str, str] = dict(default_headers or {})
112
+ # ``None`` disables the gap timeout. Omitted uses the 10-minute default.
113
+ self.stream_read_timeout: float | None = given_or(
114
+ stream_read_timeout, DEFAULT_STREAM_READ_TIMEOUT
115
+ )
116
+ identity = identity_headers(surface=sdk_surface)
117
+ application = validate_partner_application(partner_application)
118
+ configured_user_agent = next(
119
+ (
120
+ value
121
+ for name, value in (default_headers or {}).items()
122
+ if name.lower() == "user-agent"
123
+ ),
124
+ None,
125
+ )
126
+ if (
127
+ application is not None
128
+ and configured_user_agent is not None
129
+ and configured_user_agent != application
130
+ ):
131
+ raise CortexAgentError(
132
+ "Configure partner attribution with `partner_application`, not a "
133
+ "conflicting `default_headers['User-Agent']`."
134
+ )
135
+ attribution_headers = {"User-Agent": application} if application else {}
136
+ self.partner_application = application
137
+ self.sdk_surface = sdk_surface
138
+ self.default_headers: dict[str, str] = {
139
+ **identity,
140
+ **attribution_headers,
141
+ **dict(default_headers or {}),
142
+ }
106
143
  self.default_query: dict[str, Any] = dict(default_query or {})
107
144
  # Kept for copy()/with_options(), which must reproduce the original args.
108
145
  self._account = account
@@ -146,6 +183,8 @@ class BaseClient:
146
183
  headers.update(opts.headers)
147
184
  if opts.extra_headers:
148
185
  headers.update(opts.extra_headers)
186
+ if is_given(opts.sdk_operation):
187
+ headers[SDK_OPERATION_HEADER] = str(opts.sdk_operation)
149
188
  if has_body:
150
189
  headers.setdefault("Content-Type", "application/json")
151
190
  # `omit` removes a header the SDK would otherwise send.
@@ -178,13 +217,14 @@ class BaseClient:
178
217
  base = given_or(opts.timeout, self.timeout)
179
218
  if not stream:
180
219
  return base
181
- # A long-running SSE response legitimately has long gaps between chunks,
182
- # so `read` is unbounded unless the caller asks for a cap.
220
+ # A long-running SSE response legitimately has long gaps between chunks.
221
+ # `read` uses the client default (10 minutes) unless this request sets
222
+ # `stream_read_timeout`. Explicit ``None`` waits indefinitely.
183
223
  connect = base.connect if isinstance(base, httpx.Timeout) else base
184
224
  return httpx.Timeout(
185
225
  None,
186
226
  connect=connect,
187
- read=given_or(opts.stream_read_timeout, None),
227
+ read=given_or(opts.stream_read_timeout, self.stream_read_timeout),
188
228
  write=connect,
189
229
  )
190
230
 
@@ -287,7 +327,11 @@ class BaseClient:
287
327
  ) -> AuthRefreshContext | None:
288
328
  if self.refresh_auth is None or attempt > self.auth_refresh_max_retries:
289
329
  return None
290
- if response.status_code not in {401, 403}:
330
+ # A successful SSE response is a live stream; only a non-SSE 200 can
331
+ # be the legacy JSON expired-session envelope.
332
+ if stream and response.status_code == 200 and _is_sse_response(response):
333
+ return None
334
+ if response.status_code not in {200, 401, 403}:
291
335
  return None
292
336
  response_body = self._read_response_body(response)
293
337
  if not is_auth_expired_response(response.status_code, response_body):
@@ -313,7 +357,11 @@ class BaseClient:
313
357
  ) -> AuthRefreshContext | None:
314
358
  if self.refresh_auth is None or attempt > self.auth_refresh_max_retries:
315
359
  return None
316
- if response.status_code not in {401, 403}:
360
+ # A successful SSE response is a live stream; only a non-SSE 200 can
361
+ # be the legacy JSON expired-session envelope.
362
+ if stream and response.status_code == 200 and _is_sse_response(response):
363
+ return None
364
+ if response.status_code not in {200, 401, 403}:
317
365
  return None
318
366
  response_body = await self._aread_response_body(response)
319
367
  if not is_auth_expired_response(response.status_code, response_body):
@@ -362,6 +410,8 @@ class BaseClient:
362
410
  ) -> tuple[RequestOptions, dict[str, Any], _ResolvedRetryPolicy, Any, Any]:
363
411
  """Resolve options once, before the retry loop."""
364
412
  opts = normalize_options(options)
413
+ if not is_given(opts.sdk_operation):
414
+ opts = replace(opts, sdk_operation="client.request")
365
415
  return (
366
416
  opts,
367
417
  self._build_query(params, opts),
@@ -400,11 +450,14 @@ class BaseClient:
400
450
  "base_url": self.base_url,
401
451
  "auth": self.auth,
402
452
  "timeout": self.timeout,
453
+ "stream_read_timeout": self.stream_read_timeout,
403
454
  "retry_policy": self.retry_policy,
404
455
  "refresh_auth": self.refresh_auth,
405
456
  "auth_refresh_max_retries": self.auth_refresh_max_retries,
406
457
  "default_headers": self.default_headers,
407
458
  "default_query": self.default_query,
459
+ "partner_application": self.partner_application,
460
+ "sdk_surface": self.sdk_surface,
408
461
  }
409
462
 
410
463
 
@@ -742,6 +795,10 @@ def _is_local_host(hostname: str | None) -> bool:
742
795
  return hostname in _LOCAL_HOSTS or (hostname is not None and hostname.endswith(".local"))
743
796
 
744
797
 
798
+ def _is_sse_response(response: httpx.Response) -> bool:
799
+ return "text/event-stream" in response.headers.get("content-type", "").lower()
800
+
801
+
745
802
  def _validate_base_url(base_url: str) -> str:
746
803
  """Reject a base URL that would put the credential on the wire in clear text.
747
804
 
@@ -16,6 +16,11 @@ import httpx
16
16
  #: ``APITimeoutError``s; a short *connect* timeout still fails fast on a bad host.
17
17
  DEFAULT_TIMEOUT: Final = httpx.Timeout(600.0, connect=5.0)
18
18
 
19
+ #: Max gap between SSE chunks before a stream fails. Matches the 10-minute
20
+ #: total timeout: long enough for a quiet tool call, short enough that a dead
21
+ #: socket does not wait forever. Pass ``stream_read_timeout=None`` to disable.
22
+ DEFAULT_STREAM_READ_TIMEOUT: Final = 600.0
23
+
19
24
  DEFAULT_MAX_RETRIES: Final = 2
20
25
 
21
26
  #: First retry delay, doubled per attempt up to :data:`MAX_RETRY_DELAY`.
@@ -0,0 +1,18 @@
1
+ """Partner attribution helpers for REST requests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .errors import CortexAgentError
6
+
7
+
8
+ def validate_partner_application(value: str | None) -> str | None:
9
+ if value is None:
10
+ return None
11
+ if not value:
12
+ raise CortexAgentError("`partner_application` must not be empty.")
13
+ if "\r" in value or "\n" in value:
14
+ raise CortexAgentError("`partner_application` must not contain CR or LF characters.")
15
+ return value
16
+
17
+
18
+ __all__ = ["validate_partner_application"]
@@ -72,6 +72,9 @@ class RequestOptions:
72
72
  #: next event boundary. The async client uses task cancellation instead.
73
73
  cancel: threading.Event | None | NotGiven = field(default=not_given, repr=False)
74
74
 
75
+ #: Internal stable, low-cardinality public SDK operation name.
76
+ sdk_operation: str | NotGiven = field(default=not_given, repr=False)
77
+
75
78
  def merge(self, other: RequestOptions | None) -> RequestOptions:
76
79
  """A copy with every *given* field of ``other`` layered on top."""
77
80
  if other is None:
@@ -155,4 +158,16 @@ def with_overrides(
155
158
  return replace(base, **overrides) if overrides else base
156
159
 
157
160
 
158
- __all__ = ["RequestOptions", "RequestOptionsLike", "normalize_options", "with_overrides"]
161
+ def with_sdk_operation(options: RequestOptionsLike, operation: str) -> RequestOptions:
162
+ """Set the semantic operation unless an outer SDK wrapper already set it."""
163
+ base = normalize_options(options)
164
+ return base if is_given(base.sdk_operation) else replace(base, sdk_operation=operation)
165
+
166
+
167
+ __all__ = [
168
+ "RequestOptions",
169
+ "RequestOptionsLike",
170
+ "normalize_options",
171
+ "with_overrides",
172
+ "with_sdk_operation",
173
+ ]
@@ -0,0 +1,24 @@
1
+ """Bounded request metadata used for SDK telemetry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._version import __version__
6
+
7
+ SDK_OPERATION_HEADER = "x-cortex-agent-sdk-operation"
8
+ SDK_SURFACE_HEADER = "x-cortex-agent-sdk-surface"
9
+
10
+
11
+ def identity_headers(*, surface: str = "agent") -> dict[str, str]:
12
+ return {
13
+ "User-Agent": f"cortex-agent-sdk-python/{__version__}",
14
+ "x-cortex-agent-sdk-lang": "python",
15
+ "x-cortex-agent-sdk-version": __version__,
16
+ SDK_SURFACE_HEADER: surface,
17
+ }
18
+
19
+
20
+ __all__ = [
21
+ "SDK_OPERATION_HEADER",
22
+ "SDK_SURFACE_HEADER",
23
+ "identity_headers",
24
+ ]