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.
- {swytchcode_runtime-1.0.1/swytchcode_runtime.egg-info → swytchcode_runtime-1.1.2}/PKG-INFO +81 -25
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/README.md +80 -24
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/pyproject.toml +1 -1
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/__init__.py +4 -2
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/cli.py +3 -1
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/client.py +8 -7
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/discover.py +3 -1
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/errors.py +10 -1
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/exec.py +34 -2
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/manage.py +1 -0
- swytchcode_runtime-1.1.2/swytchcode_runtime/prompts.py +19 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/anthropic.py +1 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/base.py +6 -3
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/crewai.py +2 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/langgraph.py +2 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/openai_agents.py +2 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/vercel.py +1 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/schema.py +6 -7
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2/swytchcode_runtime.egg-info}/PKG-INFO +81 -25
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/SOURCES.txt +3 -0
- swytchcode_runtime-1.1.2/tests/test_exec_errors.py +36 -0
- swytchcode_runtime-1.1.2/tests/test_prompts.py +21 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/LICENSE.md +0 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/setup.cfg +0 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/__init__.py +0 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/dependency_links.txt +0 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/top_level.txt +0 -0
- {swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/tests/test_alias.py +0 -0
- {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.
|
|
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
|
|
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
|
|
26
|
-
2. `$PATH` lookup via `shutil.which`
|
|
27
|
-
3. Common install paths
|
|
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**
|
|
56
|
-
- **params**
|
|
57
|
-
- **Authorization**
|
|
58
|
-
- **headers**
|
|
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**
|
|
85
|
-
- **env**
|
|
86
|
-
- **raw**
|
|
87
|
-
- **dry_run**
|
|
88
|
-
- **allow_raw**
|
|
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` |
|
|
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.
|
|
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
|
-
#
|
|
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-
|
|
238
|
+
model="claude-sonnet-5",
|
|
186
239
|
max_tokens=1024,
|
|
240
|
+
system=system,
|
|
187
241
|
tools=tools,
|
|
188
|
-
messages=[{"role": "user", "content": "
|
|
242
|
+
messages=[{"role": "user", "content": "Star the swytchcodehq/swytchcode-examples repo on GitHub for me."}],
|
|
189
243
|
)
|
|
190
244
|
|
|
191
|
-
|
|
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
|
|
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
|
|
8
|
-
2. `$PATH` lookup via `shutil.which`
|
|
9
|
-
3. Common install paths
|
|
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**
|
|
38
|
-
- **params**
|
|
39
|
-
- **Authorization**
|
|
40
|
-
- **headers**
|
|
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**
|
|
67
|
-
- **env**
|
|
68
|
-
- **raw**
|
|
69
|
-
- **dry_run**
|
|
70
|
-
- **allow_raw**
|
|
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` |
|
|
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.
|
|
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
|
-
#
|
|
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-
|
|
220
|
+
model="claude-sonnet-5",
|
|
168
221
|
max_tokens=1024,
|
|
222
|
+
system=system,
|
|
169
223
|
tools=tools,
|
|
170
|
-
messages=[{"role": "user", "content": "
|
|
224
|
+
messages=[{"role": "user", "content": "Star the swytchcodehq/swytchcode-examples repo on GitHub for me."}],
|
|
171
225
|
)
|
|
172
226
|
|
|
173
|
-
|
|
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()
|
|
@@ -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 .
|
|
6
|
+
from .prompts import TOOL_USE_INSTRUCTIONS
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
|
8
|
-
"
|
|
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
|
|
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:
|
|
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:
|
|
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__(
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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
|
+
)
|
|
@@ -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`.
|
|
9
|
-
|
|
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
|
|
17
|
+
from typing import Any
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
@dataclass
|
{swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/crewai.py
RENAMED
|
@@ -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")
|
{swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/langgraph.py
RENAMED
|
@@ -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,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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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] = (
|
|
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.
|
|
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
|
|
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
|
|
26
|
-
2. `$PATH` lookup via `shutil.which`
|
|
27
|
-
3. Common install paths
|
|
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**
|
|
56
|
-
- **params**
|
|
57
|
-
- **Authorization**
|
|
58
|
-
- **headers**
|
|
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**
|
|
85
|
-
- **env**
|
|
86
|
-
- **raw**
|
|
87
|
-
- **dry_run**
|
|
88
|
-
- **allow_raw**
|
|
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` |
|
|
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.
|
|
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
|
-
#
|
|
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-
|
|
238
|
+
model="claude-sonnet-5",
|
|
186
239
|
max_tokens=1024,
|
|
240
|
+
system=system,
|
|
187
241
|
tools=tools,
|
|
188
|
-
messages=[{"role": "user", "content": "
|
|
242
|
+
messages=[{"role": "user", "content": "Star the swytchcodehq/swytchcode-examples repo on GitHub for me."}],
|
|
189
243
|
)
|
|
190
244
|
|
|
191
|
-
|
|
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()
|
{swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/SOURCES.txt
RENAMED
|
@@ -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()
|
|
File without changes
|
|
File without changes
|
{swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime/providers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{swytchcode_runtime-1.0.1 → swytchcode_runtime-1.1.2}/swytchcode_runtime.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|