swytchcode-runtime 1.0.1__tar.gz → 1.1.2__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 (29) hide show
  1. {swytchcode_runtime-1.0.1/swytchcode_runtime.egg-info → swytchcode_runtime-1.1.2}/PKG-INFO +81 -25
  2. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/README.md +80 -24
  3. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/pyproject.toml +1 -1
  4. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/__init__.py +4 -2
  5. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/cli.py +3 -1
  6. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/client.py +8 -7
  7. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/discover.py +3 -1
  8. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/errors.py +10 -1
  9. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/exec.py +34 -2
  10. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/manage.py +1 -0
  11. swytchcode_runtime-1.1.2/swytchcode_runtime/prompts.py +19 -0
  12. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/anthropic.py +1 -0
  13. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/base.py +6 -3
  14. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/crewai.py +2 -0
  15. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/langgraph.py +2 -0
  16. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/openai_agents.py +2 -0
  17. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/vercel.py +1 -0
  18. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/schema.py +6 -7
  19. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2/swytchcode_runtime.egg-info}/PKG-INFO +81 -25
  20. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/SOURCES.txt +3 -0
  21. swytchcode_runtime-1.1.2/tests/test_exec_errors.py +36 -0
  22. swytchcode_runtime-1.1.2/tests/test_prompts.py +21 -0
  23. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/LICENSE.md +0 -0
  24. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/setup.cfg +0 -0
  25. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/__init__.py +0 -0
  26. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/dependency_links.txt +0 -0
  27. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/top_level.txt +0 -0
  28. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/tests/test_alias.py +0 -0
  29. {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/tests/test_schema_routing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swytchcode-runtime
3
- Version: 1.0.1
3
+ Version: 1.1.2
4
4
  Summary: Thin runtime wrapper around the Swytchcode CLI
5
5
  Author: Swytchcode
6
6
  License: MIT
@@ -20,11 +20,11 @@ Dynamic: license-file
20
20
 
21
21
  Thin runtime wrapper around the Swytchcode CLI. Calls `swytchcode exec` for you so you can stay in Python without shell boilerplate.
22
22
 
23
- **Requires:** The `swytchcode` CLI must be installed. The binary is located automatically — no configuration needed in most environments. Resolution order:
23
+ **Requires:** The `swytchcode` CLI must be installed. The binary is located automatically - no configuration needed in most environments. Resolution order:
24
24
 
25
- 1. `SWYTCHCODE_BIN` env var — explicit override.
26
- 2. `$PATH` lookup via `shutil.which` — the standard system resolution.
27
- 3. Common install paths — `~/.local/bin`, `/usr/local/bin` (Unix) or `%LOCALAPPDATA%\Programs\swytchcode\bin` (Windows).
25
+ 1. `SWYTCHCODE_BIN` env var - explicit override.
26
+ 2. `$PATH` lookup via `shutil.which` - the standard system resolution.
27
+ 3. Common install paths - `~/.local/bin`, `/usr/local/bin` (Unix) or `%LOCALAPPDATA%\Programs\swytchcode\bin` (Windows).
28
28
 
29
29
  ## Install
30
30
 
@@ -52,10 +52,10 @@ result = exec("api.account.create", {"email": "test@example.com"})
52
52
  Equivalent to: `swytchcode exec api.account.create --json` with args on stdin.
53
53
 
54
54
  **Request input (args):** The second argument is the kernel **args** object (sent as JSON on stdin). Use this shape so the kernel builds the request correctly:
55
- - **body** — Request body (dict).
56
- - **params** — Query/path params (e.g. `{"id": "cluster-123"}`).
57
- - **Authorization** — Auth header value (e.g. `"Bearer token123"`).
58
- - **headers** — Additional request headers (e.g. `{"X-Request-Id": "abc-123"}`).
55
+ - **body** - Request body (dict).
56
+ - **params** - Query/path params (e.g. `{"id": "cluster-123"}`).
57
+ - **Authorization** - Auth header value (e.g. `"Bearer token123"`).
58
+ - **headers** - Additional request headers (e.g. `{"X-Request-Id": "abc-123"}`).
59
59
  - Other top-level keys are passed as query params.
60
60
 
61
61
  Example with body, params, and headers:
@@ -81,20 +81,22 @@ output = exec("api.report.export", {"id": "123"}, raw=True)
81
81
 
82
82
  ### Options
83
83
 
84
- - **cwd** – Working directory for the process (default: current directory).
85
- - **env** – Extra environment variables (merged with `os.environ`).
86
- - **raw** – If `True`, use `--raw` and return stdout as a string.
87
- - **dry_run** – If `True`, pass `--dry-run` to the CLI; request details (method, url, headers, body) are output instead of calling the server.
88
- - **allow_raw** – If `True`, pass `--allow-raw` to the CLI; required for executing raw methods (kernel has this disabled by default).
84
+ - **cwd** - Working directory for the process (default: current directory).
85
+ - **env** - Extra environment variables (merged with `os.environ`).
86
+ - **raw** - If `True`, use `--raw` and return stdout as a string.
87
+ - **dry_run** - If `True`, pass `--dry-run` to the CLI; request details (method, url, headers, body) are output instead of calling the server.
88
+ - **allow_raw** - If `True`, pass `--allow-raw` to the CLI; required for executing raw methods (kernel has this disabled by default).
89
89
 
90
90
  This runtime invokes `swytchcode exec [canonical_id]` with the flags above. For full exec behavior, see the Swytchcode kernel documentation.
91
91
 
92
92
  ### Environment variables
93
93
 
94
+ This runtime itself needs no environment configuration to run - all auth lives in the CLI's own session (`swytchcode login`, stored under `~/.swytchcode/`) or in `.swytchcode/` in your project. The variables below are for the rarer cases where you need to override that:
95
+
94
96
  | Variable | Description |
95
97
  |----------|-------------|
96
98
  | `SWYTCHCODE_BIN` | Override the resolved binary path. Set this only when automatic resolution does not find the correct binary (e.g. non-standard install locations or virtualised environments). |
97
- | `SWYTCHCODE_TOKEN` | Auth token passed to the CLI via the process environment. |
99
+ | `SWYTCHCODE_TOKEN` | Service-token auth for headless environments (CI, servers) where an interactive `swytchcode login` isn't possible. Not needed for local development once you've run `swytchcode login`. |
98
100
 
99
101
  ### Error handling
100
102
 
@@ -111,6 +113,14 @@ except Exception as e:
111
113
  raise
112
114
  ```
113
115
 
116
+ On a non-zero exit the CLI writes a classified JSON error to stderr; `exec()` parses it into a clean `.message` and a structured `.details` dict (`category`, `retryable`, `suggested_action`, `docs_url`) instead of leaving you to parse the raw JSON yourself:
117
+
118
+ ```python
119
+ except SwytchcodeError as e:
120
+ if e.details and e.details.get("category") == "auth":
121
+ print(e.details.get("suggested_action"))
122
+ ```
123
+
114
124
  ## What this library is
115
125
 
116
126
  - A thin process wrapper over the Swytchcode shell.
@@ -151,9 +161,48 @@ For full, production-ready examples across all major frameworks, check out the [
151
161
 
152
162
  On top of `exec`, the runtime exposes a small agentic surface that turns Swytchcode tools into the native tool objects each agent framework expects.
153
163
 
164
+ ### Tool-use guidance - `TOOL_USE_INSTRUCTIONS`
165
+
166
+ Without an explicit nudge, models can be conservative about side-effecting actions (starring a repo, sending a payment, creating an issue) - they'll describe what they *would* do instead of actually calling the tool. `TOOL_USE_INSTRUCTIONS` is a short, framework-agnostic string that fixes this; concatenate it into whatever your provider calls its system prompt / instructions. It's scoped to only the tools this library provides, so it's safe to combine with instructions for other, unrelated tools in the same system prompt:
167
+
168
+ ```python
169
+ from swytchcode_runtime import TOOL_USE_INSTRUCTIONS
170
+
171
+ system = f"You are a helpful assistant.\n\n{TOOL_USE_INSTRUCTIONS}"
172
+ ```
173
+
154
174
  ### Quickstart: Anthropic SDK
155
175
 
156
- Here is a clean example of building a simple agent using the Anthropic SDK. We use `python-dotenv` to load environment variables (like `ANTHROPIC_API_KEY`).
176
+ Here is a clean example of building a simple agent using the Anthropic SDK. It stars the [Swytchcode Examples repo](https://github.com/swytchcodehq/swytchcode-examples) on GitHub - a genuine OAuth-connected action (not just an API key passed on the request), so the setup below covers the real one-time flow: installing the CLI, logging in, and connecting a GitHub account.
177
+
178
+ **One-time setup** (run once per machine/project):
179
+
180
+ ```bash
181
+ # 1. Install the CLI (macOS/Linux; see https://cli.swytchcode.com for other platforms)
182
+ curl -fsSL https://cli.swytchcode.com/install.sh | sh
183
+
184
+ # 2. Scaffold .swytchcode/ + tooling.json in your project
185
+ swytchcode init
186
+
187
+ # 3. Log in (opens a browser; creates your Swytchcode session)
188
+ swytchcode login
189
+
190
+ # 4. Fetch the GitHub integration
191
+ swytchcode get github
192
+
193
+ # 5. Enable the "star a repo" tool - the trust boundary for what this project can call
194
+ swytchcode add github.user.starred.update
195
+
196
+ # 6. Connect your GitHub account (opens a browser for the OAuth flow)
197
+ swytchcode auth connect github
198
+ ```
199
+
200
+ Then add your Anthropic key to a `.env` file in your project root (used by `python-dotenv` below):
201
+
202
+ ```bash
203
+ # .env
204
+ ANTHROPIC_API_KEY=sk-ant-...
205
+ ```
157
206
 
158
207
  **Installation:**
159
208
  ```bash
@@ -166,29 +215,36 @@ pip install swytchcode-runtime anthropic python-dotenv
166
215
  import os
167
216
  from dotenv import load_dotenv
168
217
  import anthropic
169
- from swytchcode_runtime import Swytchcode
218
+ from swytchcode_runtime import Swytchcode, TOOL_USE_INSTRUCTIONS
170
219
  from swytchcode_runtime.providers.anthropic import AnthropicProvider
171
220
 
172
221
  load_dotenv() # Loads .env automatically
173
222
 
174
223
  def run_agent():
175
224
  client = anthropic.Anthropic()
176
-
225
+
177
226
  # 1. Initialize Swytchcode with the Anthropic provider
178
227
  swx = Swytchcode(provider=AnthropicProvider())
179
-
180
- # 2. Fetch the tools you want your agent to use (e.g., Stripe tools)
181
- tools = swx.tools.get(toolkits=["stripe"])
182
228
 
183
- # 3. Pass them to Claude
229
+ # 2. Fetch the tools you want your agent to use (e.g., GitHub tools)
230
+ tools = swx.tools.get(toolkits=["github"])
231
+
232
+ # 3. Build the system prompt: your own instructions plus TOOL_USE_INSTRUCTIONS,
233
+ # which tells Claude to call the tool directly for action requests instead of
234
+ # just describing what it would do
235
+ system = f"You are a helpful assistant.\n\n{TOOL_USE_INSTRUCTIONS}"
236
+
184
237
  response = client.messages.create(
185
- model="claude-3-5-sonnet-latest",
238
+ model="claude-sonnet-5",
186
239
  max_tokens=1024,
240
+ system=system,
187
241
  tools=tools,
188
- messages=[{"role": "user", "content": "Refund charge ch_123 for $20."}],
242
+ messages=[{"role": "user", "content": "Star the swytchcodehq/swytchcode-examples repo on GitHub for me."}],
189
243
  )
190
244
 
191
- print(response)
245
+ # 4. Run any tool calls Claude made and send the results back
246
+ results = swx.handle_tool_calls(response)
247
+ print(results)
192
248
 
193
249
  if __name__ == "__main__":
194
250
  run_agent()
@@ -2,11 +2,11 @@
2
2
 
3
3
  Thin runtime wrapper around the Swytchcode CLI. Calls `swytchcode exec` for you so you can stay in Python without shell boilerplate.
4
4
 
5
- **Requires:** The `swytchcode` CLI must be installed. The binary is located automatically — no configuration needed in most environments. Resolution order:
5
+ **Requires:** The `swytchcode` CLI must be installed. The binary is located automatically - no configuration needed in most environments. Resolution order:
6
6
 
7
- 1. `SWYTCHCODE_BIN` env var — explicit override.
8
- 2. `$PATH` lookup via `shutil.which` — the standard system resolution.
9
- 3. Common install paths — `~/.local/bin`, `/usr/local/bin` (Unix) or `%LOCALAPPDATA%\Programs\swytchcode\bin` (Windows).
7
+ 1. `SWYTCHCODE_BIN` env var - explicit override.
8
+ 2. `$PATH` lookup via `shutil.which` - the standard system resolution.
9
+ 3. Common install paths - `~/.local/bin`, `/usr/local/bin` (Unix) or `%LOCALAPPDATA%\Programs\swytchcode\bin` (Windows).
10
10
 
11
11
  ## Install
12
12
 
@@ -34,10 +34,10 @@ result = exec("api.account.create", {"email": "test@example.com"})
34
34
  Equivalent to: `swytchcode exec api.account.create --json` with args on stdin.
35
35
 
36
36
  **Request input (args):** The second argument is the kernel **args** object (sent as JSON on stdin). Use this shape so the kernel builds the request correctly:
37
- - **body** — Request body (dict).
38
- - **params** — Query/path params (e.g. `{"id": "cluster-123"}`).
39
- - **Authorization** — Auth header value (e.g. `"Bearer token123"`).
40
- - **headers** — Additional request headers (e.g. `{"X-Request-Id": "abc-123"}`).
37
+ - **body** - Request body (dict).
38
+ - **params** - Query/path params (e.g. `{"id": "cluster-123"}`).
39
+ - **Authorization** - Auth header value (e.g. `"Bearer token123"`).
40
+ - **headers** - Additional request headers (e.g. `{"X-Request-Id": "abc-123"}`).
41
41
  - Other top-level keys are passed as query params.
42
42
 
43
43
  Example with body, params, and headers:
@@ -63,20 +63,22 @@ output = exec("api.report.export", {"id": "123"}, raw=True)
63
63
 
64
64
  ### Options
65
65
 
66
- - **cwd** – Working directory for the process (default: current directory).
67
- - **env** – Extra environment variables (merged with `os.environ`).
68
- - **raw** – If `True`, use `--raw` and return stdout as a string.
69
- - **dry_run** – If `True`, pass `--dry-run` to the CLI; request details (method, url, headers, body) are output instead of calling the server.
70
- - **allow_raw** – If `True`, pass `--allow-raw` to the CLI; required for executing raw methods (kernel has this disabled by default).
66
+ - **cwd** - Working directory for the process (default: current directory).
67
+ - **env** - Extra environment variables (merged with `os.environ`).
68
+ - **raw** - If `True`, use `--raw` and return stdout as a string.
69
+ - **dry_run** - If `True`, pass `--dry-run` to the CLI; request details (method, url, headers, body) are output instead of calling the server.
70
+ - **allow_raw** - If `True`, pass `--allow-raw` to the CLI; required for executing raw methods (kernel has this disabled by default).
71
71
 
72
72
  This runtime invokes `swytchcode exec [canonical_id]` with the flags above. For full exec behavior, see the Swytchcode kernel documentation.
73
73
 
74
74
  ### Environment variables
75
75
 
76
+ This runtime itself needs no environment configuration to run - all auth lives in the CLI's own session (`swytchcode login`, stored under `~/.swytchcode/`) or in `.swytchcode/` in your project. The variables below are for the rarer cases where you need to override that:
77
+
76
78
  | Variable | Description |
77
79
  |----------|-------------|
78
80
  | `SWYTCHCODE_BIN` | Override the resolved binary path. Set this only when automatic resolution does not find the correct binary (e.g. non-standard install locations or virtualised environments). |
79
- | `SWYTCHCODE_TOKEN` | Auth token passed to the CLI via the process environment. |
81
+ | `SWYTCHCODE_TOKEN` | Service-token auth for headless environments (CI, servers) where an interactive `swytchcode login` isn't possible. Not needed for local development once you've run `swytchcode login`. |
80
82
 
81
83
  ### Error handling
82
84
 
@@ -93,6 +95,14 @@ except Exception as e:
93
95
  raise
94
96
  ```
95
97
 
98
+ On a non-zero exit the CLI writes a classified JSON error to stderr; `exec()` parses it into a clean `.message` and a structured `.details` dict (`category`, `retryable`, `suggested_action`, `docs_url`) instead of leaving you to parse the raw JSON yourself:
99
+
100
+ ```python
101
+ except SwytchcodeError as e:
102
+ if e.details and e.details.get("category") == "auth":
103
+ print(e.details.get("suggested_action"))
104
+ ```
105
+
96
106
  ## What this library is
97
107
 
98
108
  - A thin process wrapper over the Swytchcode shell.
@@ -133,9 +143,48 @@ For full, production-ready examples across all major frameworks, check out the [
133
143
 
134
144
  On top of `exec`, the runtime exposes a small agentic surface that turns Swytchcode tools into the native tool objects each agent framework expects.
135
145
 
146
+ ### Tool-use guidance - `TOOL_USE_INSTRUCTIONS`
147
+
148
+ Without an explicit nudge, models can be conservative about side-effecting actions (starring a repo, sending a payment, creating an issue) - they'll describe what they *would* do instead of actually calling the tool. `TOOL_USE_INSTRUCTIONS` is a short, framework-agnostic string that fixes this; concatenate it into whatever your provider calls its system prompt / instructions. It's scoped to only the tools this library provides, so it's safe to combine with instructions for other, unrelated tools in the same system prompt:
149
+
150
+ ```python
151
+ from swytchcode_runtime import TOOL_USE_INSTRUCTIONS
152
+
153
+ system = f"You are a helpful assistant.\n\n{TOOL_USE_INSTRUCTIONS}"
154
+ ```
155
+
136
156
  ### Quickstart: Anthropic SDK
137
157
 
138
- Here is a clean example of building a simple agent using the Anthropic SDK. We use `python-dotenv` to load environment variables (like `ANTHROPIC_API_KEY`).
158
+ Here is a clean example of building a simple agent using the Anthropic SDK. It stars the [Swytchcode Examples repo](https://github.com/swytchcodehq/swytchcode-examples) on GitHub - a genuine OAuth-connected action (not just an API key passed on the request), so the setup below covers the real one-time flow: installing the CLI, logging in, and connecting a GitHub account.
159
+
160
+ **One-time setup** (run once per machine/project):
161
+
162
+ ```bash
163
+ # 1. Install the CLI (macOS/Linux; see https://cli.swytchcode.com for other platforms)
164
+ curl -fsSL https://cli.swytchcode.com/install.sh | sh
165
+
166
+ # 2. Scaffold .swytchcode/ + tooling.json in your project
167
+ swytchcode init
168
+
169
+ # 3. Log in (opens a browser; creates your Swytchcode session)
170
+ swytchcode login
171
+
172
+ # 4. Fetch the GitHub integration
173
+ swytchcode get github
174
+
175
+ # 5. Enable the "star a repo" tool - the trust boundary for what this project can call
176
+ swytchcode add github.user.starred.update
177
+
178
+ # 6. Connect your GitHub account (opens a browser for the OAuth flow)
179
+ swytchcode auth connect github
180
+ ```
181
+
182
+ Then add your Anthropic key to a `.env` file in your project root (used by `python-dotenv` below):
183
+
184
+ ```bash
185
+ # .env
186
+ ANTHROPIC_API_KEY=sk-ant-...
187
+ ```
139
188
 
140
189
  **Installation:**
141
190
  ```bash
@@ -148,29 +197,36 @@ pip install swytchcode-runtime anthropic python-dotenv
148
197
  import os
149
198
  from dotenv import load_dotenv
150
199
  import anthropic
151
- from swytchcode_runtime import Swytchcode
200
+ from swytchcode_runtime import Swytchcode, TOOL_USE_INSTRUCTIONS
152
201
  from swytchcode_runtime.providers.anthropic import AnthropicProvider
153
202
 
154
203
  load_dotenv() # Loads .env automatically
155
204
 
156
205
  def run_agent():
157
206
  client = anthropic.Anthropic()
158
-
207
+
159
208
  # 1. Initialize Swytchcode with the Anthropic provider
160
209
  swx = Swytchcode(provider=AnthropicProvider())
161
-
162
- # 2. Fetch the tools you want your agent to use (e.g., Stripe tools)
163
- tools = swx.tools.get(toolkits=["stripe"])
164
210
 
165
- # 3. Pass them to Claude
211
+ # 2. Fetch the tools you want your agent to use (e.g., GitHub tools)
212
+ tools = swx.tools.get(toolkits=["github"])
213
+
214
+ # 3. Build the system prompt: your own instructions plus TOOL_USE_INSTRUCTIONS,
215
+ # which tells Claude to call the tool directly for action requests instead of
216
+ # just describing what it would do
217
+ system = f"You are a helpful assistant.\n\n{TOOL_USE_INSTRUCTIONS}"
218
+
166
219
  response = client.messages.create(
167
- model="claude-3-5-sonnet-latest",
220
+ model="claude-sonnet-5",
168
221
  max_tokens=1024,
222
+ system=system,
169
223
  tools=tools,
170
- messages=[{"role": "user", "content": "Refund charge ch_123 for $20."}],
224
+ messages=[{"role": "user", "content": "Star the swytchcodehq/swytchcode-examples repo on GitHub for me."}],
171
225
  )
172
226
 
173
- print(response)
227
+ # 4. Run any tool calls Claude made and send the results back
228
+ results = swx.handle_tool_calls(response)
229
+ print(results)
174
230
 
175
231
  if __name__ == "__main__":
176
232
  run_agent()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swytchcode-runtime"
7
- version = "1.0.1"
7
+ version = "1.1.2"
8
8
  description = "Thin runtime wrapper around the Swytchcode CLI"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,13 +1,15 @@
1
1
  """Thin runtime wrapper around the Swytchcode CLI."""
2
2
 
3
+ from .client import Swytchcode
3
4
  from .errors import SwytchcodeError, is_swytchcode_error
4
5
  from .exec import exec_ as exec
5
- from .client import Swytchcode
6
+ from .prompts import TOOL_USE_INSTRUCTIONS
6
7
 
7
8
  __all__ = [
8
- "exec",
9
+ "TOOL_USE_INSTRUCTIONS",
9
10
  "Swytchcode",
10
11
  "SwytchcodeError",
12
+ "exec",
11
13
  "is_swytchcode_error",
12
14
  ]
13
15
 
@@ -1,13 +1,14 @@
1
1
  """Run a swytchcode CLI subcommand with --json and parse the output."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  import json
5
6
  import os
6
7
  import subprocess
7
8
  from typing import Any
8
9
 
9
- from .exec import _resolve_bin
10
10
  from .errors import SwytchcodeError
11
+ from .exec import _resolve_bin
11
12
 
12
13
 
13
14
  def run_cli(
@@ -32,6 +33,7 @@ def run_cli(
32
33
  cwd=cwd or os.getcwd(),
33
34
  env=run_env,
34
35
  timeout=timeout,
36
+ check=False,
35
37
  )
36
38
  except FileNotFoundError as e:
37
39
  raise SwytchcodeError(
@@ -1,16 +1,17 @@
1
1
  """High-level agentic client on top of the existing exec()."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import Any, Optional
5
4
 
6
- import re
7
5
  import hashlib
6
+ import re
7
+ from typing import Any
8
8
 
9
- from . import discover as _discover, schema as _schema, manage as _manage
9
+ from . import discover as _discover
10
+ from . import manage as _manage
11
+ from . import schema as _schema
10
12
  from .exec import exec_ as _exec
11
13
  from .providers.base import Provider, Tool
12
14
 
13
-
14
15
  MAX_TOOL_NAME_LEN = 64 # OpenAI and Anthropic strict limit
15
16
 
16
17
 
@@ -107,7 +108,7 @@ def _split_by_location(inputs: Any, flat_args: dict) -> dict:
107
108
 
108
109
 
109
110
  class _Tools:
110
- def __init__(self, client: "Swytchcode"):
111
+ def __init__(self, client: Swytchcode):
111
112
  self._c = client
112
113
  # Maps a sanitized tool name (dots -> underscores) back to its canonical
113
114
  # ID, populated as tools are built. Used to reverse names in
@@ -199,7 +200,7 @@ class _Tools:
199
200
 
200
201
 
201
202
  class Swytchcode:
202
- def __init__(self, provider: Optional[Provider] = None):
203
+ def __init__(self, provider: Provider | None = None):
203
204
  self.provider = provider
204
205
  self.tools = _Tools(self)
205
206
 
@@ -229,7 +230,7 @@ class Swytchcode:
229
230
  )
230
231
  )
231
232
  is_error = False
232
- except Exception as e:
233
+ except Exception as e: # noqa: BLE001
233
234
  content = f"Error executing {cid}: {e}"
234
235
  is_error = True
235
236
  results.append(
@@ -1,7 +1,9 @@
1
1
  """Find tools by intent and read their schemas."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  import logging
6
+
5
7
  from .cli import run_cli
6
8
 
7
9
  logger = logging.getLogger(__name__)
@@ -19,7 +21,7 @@ def info(canonical_id: str) -> dict:
19
21
  if isinstance(result, list):
20
22
  return result[0] if result else {}
21
23
  return result
22
- except Exception as e:
24
+ except Exception as e: # noqa: BLE001
23
25
  # Broad by design: any CLI/parse failure degrades to an empty schema
24
26
  # rather than breaking tool discovery. Logged (not printed) so it
25
27
  # respects the host app's logging config.
@@ -6,10 +6,19 @@ from typing import Any
6
6
  class SwytchcodeError(Exception):
7
7
  """Raised when exec() fails: spawn error, non-zero exit, or invalid JSON output."""
8
8
 
9
- def __init__(self, message: str, cause: Any = None) -> None:
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ cause: Any = None,
13
+ details: dict[str, Any] | None = None,
14
+ ) -> None:
10
15
  super().__init__(message)
11
16
  self.message = message
12
17
  self.cause = cause
18
+ # Structured detail parsed from the CLI's classified JSON error (stderr),
19
+ # when available: category, retryable, suggested_action, docs_url. None for
20
+ # spawn errors, signals, or CLI versions predating this format.
21
+ self.details = details
13
22
 
14
23
  def __str__(self) -> str:
15
24
  if self.cause is not None:
@@ -50,7 +50,26 @@ def _resolve_bin() -> str:
50
50
  return "swytchcode" # fall through; subprocess.run reports FileNotFoundError if still missing
51
51
 
52
52
 
53
- def exec_( # noqa: A001 - shadowing intentional for API consistency with JS/Go
53
+ def _parse_classified_error(stderr: str) -> dict[str, Any] | None:
54
+ """
55
+ The CLI writes a classified JSON error to stderr on every non-zero exit that
56
+ isn't a signal (see internal/kernel/errors.go's WriteClassifiedError) - always
57
+ JSON, even in --raw mode. Parsing it here turns a raw JSON blob into a clean
58
+ message plus structured details, instead of leaving the developer to see
59
+ '{"error": "...", ...}' as the exception message.
60
+ """
61
+ if not stderr:
62
+ return None
63
+ try:
64
+ obj = json.loads(stderr)
65
+ except json.JSONDecodeError:
66
+ return None
67
+ if isinstance(obj, dict) and isinstance(obj.get("error"), str):
68
+ return obj
69
+ return None
70
+
71
+
72
+ def exec_(
54
73
  canonical_id: str,
55
74
  input: Any = None,
56
75
  *,
@@ -117,6 +136,7 @@ def exec_( # noqa: A001 - shadowing intentional for API consistency with JS/Go
117
136
  cwd=cwd or os.getcwd(),
118
137
  env=run_env,
119
138
  timeout=timeout,
139
+ check=False,
120
140
  )
121
141
  except subprocess.TimeoutExpired as e:
122
142
  raise SwytchcodeError(
@@ -128,7 +148,7 @@ def exec_( # noqa: A001 - shadowing intentional for API consistency with JS/Go
128
148
  if "SWYTCHCODE_BIN" in os.environ
129
149
  else "install it with: npm install -g swytchcode (or set SWYTCHCODE_BIN=/path/to/binary)"
130
150
  )
131
- raise SwytchcodeError(f"Failed to spawn swytchcode — {hint}", e) from e
151
+ raise SwytchcodeError(f"Failed to spawn swytchcode - {hint}", e) from e
132
152
  except OSError as e:
133
153
  raise SwytchcodeError("Failed to run swytchcode", e) from e
134
154
 
@@ -136,6 +156,18 @@ def exec_( # noqa: A001 - shadowing intentional for API consistency with JS/Go
136
156
  stdout = result.stdout.decode("utf-8", errors="replace")
137
157
 
138
158
  if result.returncode != 0:
159
+ classified = _parse_classified_error(stderr)
160
+ if classified:
161
+ msg = classified["error"]
162
+ if classified.get("suggested_action"):
163
+ msg = f"{msg} - {classified['suggested_action']}"
164
+ details = {
165
+ "category": classified.get("category"),
166
+ "retryable": classified.get("retryable"),
167
+ "suggested_action": classified.get("suggested_action"),
168
+ "docs_url": classified.get("docs_url"),
169
+ }
170
+ raise SwytchcodeError(msg, result.returncode, details)
139
171
  msg = stderr or "swytchcode exec failed"
140
172
  raise SwytchcodeError(msg, result.returncode)
141
173
 
@@ -1,6 +1,7 @@
1
1
  """Management commands wrapper for the SDK."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  from .cli import run_cli
5
6
 
6
7
 
@@ -0,0 +1,19 @@
1
+ """Tool-use guidance for models - see TOOL_USE_INSTRUCTIONS."""
2
+
3
+ # Models can be conservative about side-effecting actions (e.g. starring a repo,
4
+ # sending a payment) when a system prompt doesn't explicitly grant permission to
5
+ # act - they describe what they would do instead of calling the tool. Concatenate
6
+ # this into your system prompt / instructions to fix that.
7
+ #
8
+ # Scoped deliberately to only the tools this library provides: a combined system
9
+ # prompt often carries instructions for other, unrelated tools too, and a vaguer
10
+ # "call tools directly" instruction could be read as applying to those as well.
11
+ TOOL_USE_INSTRUCTIONS = (
12
+ "The tools below are provided by Swytchcode and represent real actions on connected "
13
+ "services (e.g. starring a repo, creating an issue, sending a payment). When the "
14
+ "user's request describes one of these actions, call the matching tool directly and "
15
+ "let it run - do not just describe what you would do, ask for confirmation first, or "
16
+ "wait for clarification if the request is already unambiguous. This guidance applies "
17
+ "only to the Swytchcode tools provided here - it does not affect how you use any "
18
+ "other tools available to you."
19
+ )
@@ -1,6 +1,7 @@
1
1
  """Anthropic Claude provider (Non-agentic / raw tool use schemas)."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  from .base import Provider, Tool
5
6
 
6
7
 
@@ -5,13 +5,16 @@ native tool object, and `format_tools` maps over a list. Execution is carried on
5
5
  the Tool itself via its `execute` closure (which runs the Swytchcode CLI): agentic
6
6
  frameworks (OpenAI Agents, Vercel, LangGraph, CrewAI) invoke that closure directly
7
7
  during their own loop, while non-agentic APIs (Anthropic) run it through
8
- `Swytchcode.handle_tool_calls`. Note this is deliberately NOT Composio's two-class
9
- agentic/non-agentic split - execution lives on the tool, not an injected function.
8
+ `Swytchcode.handle_tool_calls`. Execution deliberately lives on the tool itself
9
+ rather than an injected function, so both agentic and non-agentic callers share
10
+ one code path instead of needing a separate split.
10
11
  """
11
12
 
12
13
  from __future__ import annotations
14
+
15
+ from collections.abc import Callable
13
16
  from dataclasses import dataclass
14
- from typing import Any, Callable
17
+ from typing import Any
15
18
 
16
19
 
17
20
  @dataclass
@@ -1,12 +1,14 @@
1
1
  """CrewAI provider: neutral tools -> BaseTool subclasses."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  from .base import Provider, Tool
5
6
 
6
7
 
7
8
  class CrewAIProvider(Provider):
8
9
  def format_tool(self, tool: Tool):
9
10
  from crewai.tools import BaseTool
11
+
10
12
  from ..schema import to_pydantic_model
11
13
 
12
14
  args_schema_cls = to_pydantic_model(tool.input_schema, tool.name + "Schema")
@@ -1,12 +1,14 @@
1
1
  """LangGraph provider: neutral tools -> LangChain StructuredTool objects."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  from .base import Provider, Tool
5
6
 
6
7
 
7
8
  class LangGraphProvider(Provider):
8
9
  def format_tool(self, tool: Tool):
9
10
  from langchain_core.tools import StructuredTool
11
+
10
12
  from ..schema import to_pydantic_model
11
13
 
12
14
  return StructuredTool.from_function(
@@ -1,8 +1,10 @@
1
1
  """OpenAI Agents SDK provider."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  import asyncio
5
6
  import json
7
+
6
8
  from .base import Provider, Tool
7
9
 
8
10
 
@@ -1,6 +1,7 @@
1
1
  """Vercel AI SDK provider (Python equivalent)."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  from .base import Provider, Tool
5
6
 
6
7
 
@@ -1,7 +1,8 @@
1
1
  """Simplify a tool's input schema for the LLM: expose all fields, mark required ones."""
2
2
 
3
3
  from __future__ import annotations
4
- from typing import Any, Optional
4
+
5
+ from typing import Any
5
6
 
6
7
 
7
8
  def simplify(inputs: Any) -> dict:
@@ -24,9 +25,7 @@ def simplify(inputs: Any) -> dict:
24
25
  t = "number"
25
26
  elif t == "bool":
26
27
  t = "boolean"
27
- elif t == "object":
28
- t = "object"
29
- elif t == "any":
28
+ elif t == "object" or t == "any":
30
29
  t = "object"
31
30
  elif t.startswith("[]"):
32
31
  t = "array"
@@ -45,7 +44,7 @@ def simplify(inputs: Any) -> dict:
45
44
  if is_required:
46
45
  required.append(name)
47
46
 
48
- # Composio-style rule: expose ALL fields to the model and list only the
47
+ # rule: expose ALL fields to the model and list only the
49
48
  # truly-required ones in `required`. A required-only approach hid optional
50
49
  # fields - which left all-optional tools (e.g. Stripe) with an empty schema
51
50
  # so the model called them with no arguments, and blinded the model to
@@ -108,8 +107,8 @@ def to_pydantic_model(schema: dict, name: str = "ArgsSchema") -> Any:
108
107
  if field_name in required:
109
108
  fields[field_name] = (field_type, ...)
110
109
  else:
111
- # Optional[...] so an explicit null is accepted (pydantic v2 rejects
110
+ # `X | None` so an explicit null is accepted (pydantic v2 rejects
112
111
  # None for a bare non-optional annotation).
113
- fields[field_name] = (Optional[field_type], None)
112
+ fields[field_name] = (field_type | None, None)
114
113
 
115
114
  return create_model(name, **fields)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swytchcode-runtime
3
- Version: 1.0.1
3
+ Version: 1.1.2
4
4
  Summary: Thin runtime wrapper around the Swytchcode CLI
5
5
  Author: Swytchcode
6
6
  License: MIT
@@ -20,11 +20,11 @@ Dynamic: license-file
20
20
 
21
21
  Thin runtime wrapper around the Swytchcode CLI. Calls `swytchcode exec` for you so you can stay in Python without shell boilerplate.
22
22
 
23
- **Requires:** The `swytchcode` CLI must be installed. The binary is located automatically — no configuration needed in most environments. Resolution order:
23
+ **Requires:** The `swytchcode` CLI must be installed. The binary is located automatically - no configuration needed in most environments. Resolution order:
24
24
 
25
- 1. `SWYTCHCODE_BIN` env var — explicit override.
26
- 2. `$PATH` lookup via `shutil.which` — the standard system resolution.
27
- 3. Common install paths — `~/.local/bin`, `/usr/local/bin` (Unix) or `%LOCALAPPDATA%\Programs\swytchcode\bin` (Windows).
25
+ 1. `SWYTCHCODE_BIN` env var - explicit override.
26
+ 2. `$PATH` lookup via `shutil.which` - the standard system resolution.
27
+ 3. Common install paths - `~/.local/bin`, `/usr/local/bin` (Unix) or `%LOCALAPPDATA%\Programs\swytchcode\bin` (Windows).
28
28
 
29
29
  ## Install
30
30
 
@@ -52,10 +52,10 @@ result = exec("api.account.create", {"email": "test@example.com"})
52
52
  Equivalent to: `swytchcode exec api.account.create --json` with args on stdin.
53
53
 
54
54
  **Request input (args):** The second argument is the kernel **args** object (sent as JSON on stdin). Use this shape so the kernel builds the request correctly:
55
- - **body** — Request body (dict).
56
- - **params** — Query/path params (e.g. `{"id": "cluster-123"}`).
57
- - **Authorization** — Auth header value (e.g. `"Bearer token123"`).
58
- - **headers** — Additional request headers (e.g. `{"X-Request-Id": "abc-123"}`).
55
+ - **body** - Request body (dict).
56
+ - **params** - Query/path params (e.g. `{"id": "cluster-123"}`).
57
+ - **Authorization** - Auth header value (e.g. `"Bearer token123"`).
58
+ - **headers** - Additional request headers (e.g. `{"X-Request-Id": "abc-123"}`).
59
59
  - Other top-level keys are passed as query params.
60
60
 
61
61
  Example with body, params, and headers:
@@ -81,20 +81,22 @@ output = exec("api.report.export", {"id": "123"}, raw=True)
81
81
 
82
82
  ### Options
83
83
 
84
- - **cwd** – Working directory for the process (default: current directory).
85
- - **env** – Extra environment variables (merged with `os.environ`).
86
- - **raw** – If `True`, use `--raw` and return stdout as a string.
87
- - **dry_run** – If `True`, pass `--dry-run` to the CLI; request details (method, url, headers, body) are output instead of calling the server.
88
- - **allow_raw** – If `True`, pass `--allow-raw` to the CLI; required for executing raw methods (kernel has this disabled by default).
84
+ - **cwd** - Working directory for the process (default: current directory).
85
+ - **env** - Extra environment variables (merged with `os.environ`).
86
+ - **raw** - If `True`, use `--raw` and return stdout as a string.
87
+ - **dry_run** - If `True`, pass `--dry-run` to the CLI; request details (method, url, headers, body) are output instead of calling the server.
88
+ - **allow_raw** - If `True`, pass `--allow-raw` to the CLI; required for executing raw methods (kernel has this disabled by default).
89
89
 
90
90
  This runtime invokes `swytchcode exec [canonical_id]` with the flags above. For full exec behavior, see the Swytchcode kernel documentation.
91
91
 
92
92
  ### Environment variables
93
93
 
94
+ This runtime itself needs no environment configuration to run - all auth lives in the CLI's own session (`swytchcode login`, stored under `~/.swytchcode/`) or in `.swytchcode/` in your project. The variables below are for the rarer cases where you need to override that:
95
+
94
96
  | Variable | Description |
95
97
  |----------|-------------|
96
98
  | `SWYTCHCODE_BIN` | Override the resolved binary path. Set this only when automatic resolution does not find the correct binary (e.g. non-standard install locations or virtualised environments). |
97
- | `SWYTCHCODE_TOKEN` | Auth token passed to the CLI via the process environment. |
99
+ | `SWYTCHCODE_TOKEN` | Service-token auth for headless environments (CI, servers) where an interactive `swytchcode login` isn't possible. Not needed for local development once you've run `swytchcode login`. |
98
100
 
99
101
  ### Error handling
100
102
 
@@ -111,6 +113,14 @@ except Exception as e:
111
113
  raise
112
114
  ```
113
115
 
116
+ On a non-zero exit the CLI writes a classified JSON error to stderr; `exec()` parses it into a clean `.message` and a structured `.details` dict (`category`, `retryable`, `suggested_action`, `docs_url`) instead of leaving you to parse the raw JSON yourself:
117
+
118
+ ```python
119
+ except SwytchcodeError as e:
120
+ if e.details and e.details.get("category") == "auth":
121
+ print(e.details.get("suggested_action"))
122
+ ```
123
+
114
124
  ## What this library is
115
125
 
116
126
  - A thin process wrapper over the Swytchcode shell.
@@ -151,9 +161,48 @@ For full, production-ready examples across all major frameworks, check out the [
151
161
 
152
162
  On top of `exec`, the runtime exposes a small agentic surface that turns Swytchcode tools into the native tool objects each agent framework expects.
153
163
 
164
+ ### Tool-use guidance - `TOOL_USE_INSTRUCTIONS`
165
+
166
+ Without an explicit nudge, models can be conservative about side-effecting actions (starring a repo, sending a payment, creating an issue) - they'll describe what they *would* do instead of actually calling the tool. `TOOL_USE_INSTRUCTIONS` is a short, framework-agnostic string that fixes this; concatenate it into whatever your provider calls its system prompt / instructions. It's scoped to only the tools this library provides, so it's safe to combine with instructions for other, unrelated tools in the same system prompt:
167
+
168
+ ```python
169
+ from swytchcode_runtime import TOOL_USE_INSTRUCTIONS
170
+
171
+ system = f"You are a helpful assistant.\n\n{TOOL_USE_INSTRUCTIONS}"
172
+ ```
173
+
154
174
  ### Quickstart: Anthropic SDK
155
175
 
156
- Here is a clean example of building a simple agent using the Anthropic SDK. We use `python-dotenv` to load environment variables (like `ANTHROPIC_API_KEY`).
176
+ Here is a clean example of building a simple agent using the Anthropic SDK. It stars the [Swytchcode Examples repo](https://github.com/swytchcodehq/swytchcode-examples) on GitHub - a genuine OAuth-connected action (not just an API key passed on the request), so the setup below covers the real one-time flow: installing the CLI, logging in, and connecting a GitHub account.
177
+
178
+ **One-time setup** (run once per machine/project):
179
+
180
+ ```bash
181
+ # 1. Install the CLI (macOS/Linux; see https://cli.swytchcode.com for other platforms)
182
+ curl -fsSL https://cli.swytchcode.com/install.sh | sh
183
+
184
+ # 2. Scaffold .swytchcode/ + tooling.json in your project
185
+ swytchcode init
186
+
187
+ # 3. Log in (opens a browser; creates your Swytchcode session)
188
+ swytchcode login
189
+
190
+ # 4. Fetch the GitHub integration
191
+ swytchcode get github
192
+
193
+ # 5. Enable the "star a repo" tool - the trust boundary for what this project can call
194
+ swytchcode add github.user.starred.update
195
+
196
+ # 6. Connect your GitHub account (opens a browser for the OAuth flow)
197
+ swytchcode auth connect github
198
+ ```
199
+
200
+ Then add your Anthropic key to a `.env` file in your project root (used by `python-dotenv` below):
201
+
202
+ ```bash
203
+ # .env
204
+ ANTHROPIC_API_KEY=sk-ant-...
205
+ ```
157
206
 
158
207
  **Installation:**
159
208
  ```bash
@@ -166,29 +215,36 @@ pip install swytchcode-runtime anthropic python-dotenv
166
215
  import os
167
216
  from dotenv import load_dotenv
168
217
  import anthropic
169
- from swytchcode_runtime import Swytchcode
218
+ from swytchcode_runtime import Swytchcode, TOOL_USE_INSTRUCTIONS
170
219
  from swytchcode_runtime.providers.anthropic import AnthropicProvider
171
220
 
172
221
  load_dotenv() # Loads .env automatically
173
222
 
174
223
  def run_agent():
175
224
  client = anthropic.Anthropic()
176
-
225
+
177
226
  # 1. Initialize Swytchcode with the Anthropic provider
178
227
  swx = Swytchcode(provider=AnthropicProvider())
179
-
180
- # 2. Fetch the tools you want your agent to use (e.g., Stripe tools)
181
- tools = swx.tools.get(toolkits=["stripe"])
182
228
 
183
- # 3. Pass them to Claude
229
+ # 2. Fetch the tools you want your agent to use (e.g., GitHub tools)
230
+ tools = swx.tools.get(toolkits=["github"])
231
+
232
+ # 3. Build the system prompt: your own instructions plus TOOL_USE_INSTRUCTIONS,
233
+ # which tells Claude to call the tool directly for action requests instead of
234
+ # just describing what it would do
235
+ system = f"You are a helpful assistant.\n\n{TOOL_USE_INSTRUCTIONS}"
236
+
184
237
  response = client.messages.create(
185
- model="claude-3-5-sonnet-latest",
238
+ model="claude-sonnet-5",
186
239
  max_tokens=1024,
240
+ system=system,
187
241
  tools=tools,
188
- messages=[{"role": "user", "content": "Refund charge ch_123 for $20."}],
242
+ messages=[{"role": "user", "content": "Star the swytchcodehq/swytchcode-examples repo on GitHub for me."}],
189
243
  )
190
244
 
191
- print(response)
245
+ # 4. Run any tool calls Claude made and send the results back
246
+ results = swx.handle_tool_calls(response)
247
+ print(results)
192
248
 
193
249
  if __name__ == "__main__":
194
250
  run_agent()
@@ -8,6 +8,7 @@ swytchcode_runtime/discover.py
8
8
  swytchcode_runtime/errors.py
9
9
  swytchcode_runtime/exec.py
10
10
  swytchcode_runtime/manage.py
11
+ swytchcode_runtime/prompts.py
11
12
  swytchcode_runtime/schema.py
12
13
  swytchcode_runtime.egg-info/PKG-INFO
13
14
  swytchcode_runtime.egg-info/SOURCES.txt
@@ -21,4 +22,6 @@ swytchcode_runtime/providers/langgraph.py
21
22
  swytchcode_runtime/providers/openai_agents.py
22
23
  swytchcode_runtime/providers/vercel.py
23
24
  tests/test_alias.py
25
+ tests/test_exec_errors.py
26
+ tests/test_prompts.py
24
27
  tests/test_schema_routing.py
@@ -0,0 +1,36 @@
1
+ import json
2
+ import unittest
3
+
4
+ from swytchcode_runtime.exec import _parse_classified_error
5
+
6
+
7
+ class TestParseClassifiedError(unittest.TestCase):
8
+ def test_extracts_the_clis_classified_json_error_from_stderr(self):
9
+ stderr = json.dumps(
10
+ {
11
+ "error": "missing credentials for github - run `swytchcode auth connect github`",
12
+ "category": "auth",
13
+ "retryable": False,
14
+ "suggested_action": "to access registry features, run: swytchcode login",
15
+ "docs_url": "https://docs.swytchcode.com/auth",
16
+ }
17
+ )
18
+ parsed = _parse_classified_error(stderr)
19
+ self.assertEqual(
20
+ parsed["error"],
21
+ "missing credentials for github - run `swytchcode auth connect github`",
22
+ )
23
+ self.assertEqual(parsed["category"], "auth")
24
+ self.assertEqual(
25
+ parsed["suggested_action"],
26
+ "to access registry features, run: swytchcode login",
27
+ )
28
+
29
+ def test_returns_none_for_non_json_or_shapeless_stderr(self):
30
+ self.assertIsNone(_parse_classified_error(""))
31
+ self.assertIsNone(_parse_classified_error("a plain-text panic, not JSON"))
32
+ self.assertIsNone(_parse_classified_error('{"not_error_field": true}'))
33
+
34
+
35
+ if __name__ == "__main__":
36
+ unittest.main()
@@ -0,0 +1,21 @@
1
+ import re
2
+ import unittest
3
+
4
+ from swytchcode_runtime import TOOL_USE_INSTRUCTIONS
5
+
6
+
7
+ class TestToolUseInstructions(unittest.TestCase):
8
+ def test_is_exported_and_instructs_the_model_to_call_tools(self):
9
+ self.assertIsInstance(TOOL_USE_INSTRUCTIONS, str)
10
+ self.assertTrue(len(TOOL_USE_INSTRUCTIONS) > 0)
11
+ self.assertRegex(TOOL_USE_INSTRUCTIONS, re.compile("call the matching tool", re.I))
12
+
13
+ def test_scopes_itself_to_swytchcode_tools_only(self):
14
+ self.assertRegex(
15
+ TOOL_USE_INSTRUCTIONS,
16
+ re.compile(r"does not affect how you use any\s+other tools", re.I),
17
+ )
18
+
19
+
20
+ if __name__ == "__main__":
21
+ unittest.main()