morphsdk 0.2.5__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.
- morphsdk-0.2.5/.gitignore +9 -0
- morphsdk-0.2.5/PKG-INFO +226 -0
- morphsdk-0.2.5/README.md +176 -0
- morphsdk-0.2.5/morphsdk/__init__.py +54 -0
- morphsdk-0.2.5/morphsdk/_agent/__init__.py +64 -0
- morphsdk-0.2.5/morphsdk/_agent/config.py +52 -0
- morphsdk-0.2.5/morphsdk/_agent/explore.py +276 -0
- morphsdk-0.2.5/morphsdk/_agent/github.py +57 -0
- morphsdk-0.2.5/morphsdk/_agent/helpers.py +133 -0
- morphsdk-0.2.5/morphsdk/_agent/parser.py +163 -0
- morphsdk-0.2.5/morphsdk/_agent/runner.py +524 -0
- morphsdk-0.2.5/morphsdk/_agent/tools.py +171 -0
- morphsdk-0.2.5/morphsdk/_agent/types.py +126 -0
- morphsdk-0.2.5/morphsdk/_base.py +309 -0
- morphsdk-0.2.5/morphsdk/_client.py +245 -0
- morphsdk-0.2.5/morphsdk/_config.py +37 -0
- morphsdk-0.2.5/morphsdk/_constants.py +53 -0
- morphsdk-0.2.5/morphsdk/_errors.py +111 -0
- morphsdk-0.2.5/morphsdk/_providers/__init__.py +36 -0
- morphsdk-0.2.5/morphsdk/_providers/_filter.py +92 -0
- morphsdk-0.2.5/morphsdk/_providers/base.py +94 -0
- morphsdk-0.2.5/morphsdk/_providers/code_storage_http.py +104 -0
- morphsdk-0.2.5/morphsdk/_providers/local.py +270 -0
- morphsdk-0.2.5/morphsdk/_providers/remote.py +161 -0
- morphsdk-0.2.5/morphsdk/_version.py +1 -0
- morphsdk-0.2.5/morphsdk/adapters/__init__.py +1 -0
- morphsdk-0.2.5/morphsdk/adapters/anthropic.py +360 -0
- morphsdk-0.2.5/morphsdk/adapters/langchain.py +120 -0
- morphsdk-0.2.5/morphsdk/adapters/openai.py +500 -0
- morphsdk-0.2.5/morphsdk/py.typed +0 -0
- morphsdk-0.2.5/morphsdk/resources/__init__.py +0 -0
- morphsdk-0.2.5/morphsdk/resources/browser.py +919 -0
- morphsdk-0.2.5/morphsdk/resources/compact.py +133 -0
- morphsdk-0.2.5/morphsdk/resources/edit.py +506 -0
- morphsdk-0.2.5/morphsdk/resources/explore.py +333 -0
- morphsdk-0.2.5/morphsdk/resources/git.py +861 -0
- morphsdk-0.2.5/morphsdk/resources/github.py +1214 -0
- morphsdk-0.2.5/morphsdk/resources/grep.py +583 -0
- morphsdk-0.2.5/morphsdk/resources/mobile.py +134 -0
- morphsdk-0.2.5/morphsdk/resources/reflex.py +414 -0
- morphsdk-0.2.5/morphsdk/resources/router.py +124 -0
- morphsdk-0.2.5/morphsdk/resources/search.py +110 -0
- morphsdk-0.2.5/morphsdk/tracing/__init__.py +70 -0
- morphsdk-0.2.5/morphsdk/tracing/_otel.py +101 -0
- morphsdk-0.2.5/morphsdk/tracing/core.py +249 -0
- morphsdk-0.2.5/morphsdk/tracing/interaction.py +284 -0
- morphsdk-0.2.5/morphsdk/tracing/otel.py +75 -0
- morphsdk-0.2.5/morphsdk/tracing/reflex.py +58 -0
- morphsdk-0.2.5/morphsdk/tracing/types.py +163 -0
- morphsdk-0.2.5/morphsdk/types/__init__.py +140 -0
- morphsdk-0.2.5/morphsdk/types/browser.py +118 -0
- morphsdk-0.2.5/morphsdk/types/compact.py +41 -0
- morphsdk-0.2.5/morphsdk/types/edit.py +31 -0
- morphsdk-0.2.5/morphsdk/types/explore.py +42 -0
- morphsdk-0.2.5/morphsdk/types/git.py +25 -0
- morphsdk-0.2.5/morphsdk/types/github.py +111 -0
- morphsdk-0.2.5/morphsdk/types/grep.py +41 -0
- morphsdk-0.2.5/morphsdk/types/mobile.py +25 -0
- morphsdk-0.2.5/morphsdk/types/reflex.py +137 -0
- morphsdk-0.2.5/morphsdk/types/router.py +21 -0
- morphsdk-0.2.5/morphsdk/types/search.py +33 -0
- morphsdk-0.2.5/pyproject.toml +81 -0
- morphsdk-0.2.5/tests/__init__.py +0 -0
- morphsdk-0.2.5/tests/integration/__init__.py +0 -0
- morphsdk-0.2.5/tests/integration/test_async_integration.py +40 -0
- morphsdk-0.2.5/tests/integration/test_explore_integration.py +72 -0
- morphsdk-0.2.5/tests/integration/test_grep_agent_integration.py +90 -0
- morphsdk-0.2.5/tests/integration/test_integration.py +102 -0
- morphsdk-0.2.5/tests/integration/test_reflex_integration.py +50 -0
- morphsdk-0.2.5/tests/test_adapters.py +68 -0
- morphsdk-0.2.5/tests/test_async_client.py +610 -0
- morphsdk-0.2.5/tests/test_browser.py +185 -0
- morphsdk-0.2.5/tests/test_client.py +86 -0
- morphsdk-0.2.5/tests/test_compact_params.py +86 -0
- morphsdk-0.2.5/tests/test_debug_logging.py +83 -0
- morphsdk-0.2.5/tests/test_errors.py +84 -0
- morphsdk-0.2.5/tests/test_explore.py +323 -0
- morphsdk-0.2.5/tests/test_grep_agent.py +455 -0
- morphsdk-0.2.5/tests/test_providers.py +359 -0
- morphsdk-0.2.5/tests/test_reflex.py +432 -0
- morphsdk-0.2.5/tests/test_tracing.py +475 -0
- morphsdk-0.2.5/tests/test_types.py +166 -0
morphsdk-0.2.5/PKG-INFO
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: morphsdk
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Morph SDK - AI-powered code editing, search, browser automation, and more
|
|
5
|
+
Project-URL: Homepage, https://morphllm.com
|
|
6
|
+
Project-URL: Documentation, https://docs.morphllm.com
|
|
7
|
+
Author-email: Morph <support@morphllm.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: eval-type-backport>=0.2; python_version < '3.10'
|
|
21
|
+
Requires-Dist: gitpython>=3.1
|
|
22
|
+
Requires-Dist: httpx>=0.25
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: anthropic>=0.30; extra == 'all'
|
|
26
|
+
Requires-Dist: langchain-core>=0.2; extra == 'all'
|
|
27
|
+
Requires-Dist: openai>=1.0; extra == 'all'
|
|
28
|
+
Requires-Dist: opentelemetry-api>=1.41; extra == 'all'
|
|
29
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.41; extra == 'all'
|
|
30
|
+
Requires-Dist: opentelemetry-sdk>=1.41; extra == 'all'
|
|
31
|
+
Requires-Dist: traceloop-sdk>=0.36; extra == 'all'
|
|
32
|
+
Provides-Extra: anthropic
|
|
33
|
+
Requires-Dist: anthropic>=0.30; extra == 'anthropic'
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
38
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.3; extra == 'dev'
|
|
40
|
+
Provides-Extra: langchain
|
|
41
|
+
Requires-Dist: langchain-core>=0.2; extra == 'langchain'
|
|
42
|
+
Provides-Extra: openai
|
|
43
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
44
|
+
Provides-Extra: otel
|
|
45
|
+
Requires-Dist: opentelemetry-api>=1.41; extra == 'otel'
|
|
46
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.41; extra == 'otel'
|
|
47
|
+
Requires-Dist: opentelemetry-sdk>=1.41; extra == 'otel'
|
|
48
|
+
Requires-Dist: traceloop-sdk>=0.36; extra == 'otel'
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# Morph Python SDK
|
|
52
|
+
|
|
53
|
+
One typed client for Morph's specialized agent models: merge code edits with Fast Apply at ~10,500 tok/s, search a repo with WarpGrep, compress context with the compactor, route between models, and run browser tasks. Synchronous and async, Python 3.9+.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install morphsdk
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quickstart
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from morphsdk import Morph
|
|
63
|
+
|
|
64
|
+
morph = Morph(api_key="sk-...") # or set MORPH_API_KEY
|
|
65
|
+
|
|
66
|
+
result = morph.edit.file(
|
|
67
|
+
path="src/app.py",
|
|
68
|
+
instruction="Add a retry decorator to fetch_user",
|
|
69
|
+
code_edit="# ... existing code ...\n@retry(max_attempts=3)\ndef fetch_user(user_id):",
|
|
70
|
+
)
|
|
71
|
+
print(result.changes) # lines added / removed / modified
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The API key is read from `MORPH_API_KEY` if you do not pass `api_key=`. Get one at [morphllm.com](https://morphllm.com).
|
|
75
|
+
|
|
76
|
+
## Fast Apply: merge edits onto a file
|
|
77
|
+
|
|
78
|
+
`edit.file` reads the file, sends the original plus your edit snippet to the merge API, and writes the result back. Use `auto_write=False` to get the merged text without touching disk.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
result = morph.edit.file(
|
|
82
|
+
path="src/app.py",
|
|
83
|
+
instruction="Rename the handler to on_request",
|
|
84
|
+
code_edit="# ... existing code ...\ndef on_request(req):\n # ... existing code ...",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Code-in, code-out, no file I/O:
|
|
88
|
+
merged = morph.edit.code(
|
|
89
|
+
instruction="Add type hints",
|
|
90
|
+
original_code="def add(a, b):\n return a + b",
|
|
91
|
+
code_edit="def add(a: int, b: int) -> int:\n # ... existing code ...",
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## WarpGrep: search a codebase
|
|
96
|
+
|
|
97
|
+
`grep.search` runs the WarpGrep agent loop over a local repo. Pass `stream=True` to iterate per-turn steps before the final result. `search_github` does the same against a public repo.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
result = morph.grep.search(
|
|
101
|
+
query="where is the auth middleware registered?",
|
|
102
|
+
repo_root="/path/to/repo",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
for step in morph.grep.search(query="rate limiter", repo_root="/path/to/repo", stream=True):
|
|
106
|
+
print(step)
|
|
107
|
+
|
|
108
|
+
result = morph.grep.search_github(
|
|
109
|
+
query="how does the router pick a model?",
|
|
110
|
+
github="morphllm/landing",
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Explore: multi-turn codebase Q&A
|
|
115
|
+
|
|
116
|
+
`explore.run` answers a natural-language question about a repository and returns a summary plus the contexts it found. `thoroughness` is `quick`, `medium`, or `thorough`.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
result = morph.explore.run(
|
|
120
|
+
query="how does the WarpGrep agent loop call the model each turn?",
|
|
121
|
+
repo_root="/path/to/repo",
|
|
122
|
+
thoroughness="quick",
|
|
123
|
+
)
|
|
124
|
+
print(result.summary)
|
|
125
|
+
for ctx in result.contexts:
|
|
126
|
+
print(ctx)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Compact: compress context
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
result = morph.compact(
|
|
133
|
+
input="...long transcript or code...",
|
|
134
|
+
query="what matters for the next step",
|
|
135
|
+
compression_ratio=0.5,
|
|
136
|
+
)
|
|
137
|
+
print(result)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Router: pick a model for a prompt
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
choice = morph.router.select_model(input="Explain quicksort")
|
|
144
|
+
print(choice)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## More resources
|
|
148
|
+
|
|
149
|
+
The same client exposes `morph.search` (semantic codebase search by `repo_id`), `morph.browser` (`browser.run(task=..., url=...)`), `morph.git`, `morph.github`, `morph.mobile`, and `morph.reflex` (per-turn classifiers, OpenAI-fine-tuning-compatible). See the [docs](https://docs.morphllm.com) for the full surface.
|
|
150
|
+
|
|
151
|
+
## Async
|
|
152
|
+
|
|
153
|
+
`AsyncMorph` mirrors `Morph` with every method as `async def`; streaming methods return an `AsyncIterator`.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import asyncio
|
|
157
|
+
from morphsdk import AsyncMorph
|
|
158
|
+
|
|
159
|
+
async def main():
|
|
160
|
+
async with AsyncMorph(api_key="sk-...") as morph:
|
|
161
|
+
result = await morph.edit.file(
|
|
162
|
+
path="src/app.py",
|
|
163
|
+
instruction="Fix the off-by-one",
|
|
164
|
+
code_edit="# ... existing code ...",
|
|
165
|
+
)
|
|
166
|
+
print(result.changes)
|
|
167
|
+
|
|
168
|
+
asyncio.run(main())
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Framework adapters
|
|
172
|
+
|
|
173
|
+
Install the extra for your agent framework to get ready-made tool definitions.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pip install "morphsdk[openai]" # OpenAI tool schemas
|
|
177
|
+
pip install "morphsdk[anthropic]" # Anthropic tool schemas
|
|
178
|
+
pip install "morphsdk[langchain]" # LangChain tools
|
|
179
|
+
pip install "morphsdk[otel]" # OpenTelemetry tracing
|
|
180
|
+
pip install "morphsdk[all]" # everything above
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from morphsdk.adapters.openai import edit_file_tool_def
|
|
185
|
+
|
|
186
|
+
tools = [edit_file_tool_def()]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Errors
|
|
190
|
+
|
|
191
|
+
Every failure raises a subclass of `MorphError`, so you can catch one type or branch on specifics.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from morphsdk import (
|
|
195
|
+
MorphError,
|
|
196
|
+
AuthenticationError,
|
|
197
|
+
RateLimitError,
|
|
198
|
+
ValidationError,
|
|
199
|
+
NotFoundError,
|
|
200
|
+
PermissionDeniedError,
|
|
201
|
+
APIConnectionError,
|
|
202
|
+
APITimeoutError,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
morph.edit.file(path="src/app.py", instruction="...", code_edit="...")
|
|
207
|
+
except RateLimitError:
|
|
208
|
+
... # back off and retry
|
|
209
|
+
except MorphError as err:
|
|
210
|
+
... # everything else
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
`Morph(...)` and `AsyncMorph(...)` accept:
|
|
216
|
+
|
|
217
|
+
| Argument | Default | Purpose |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| `api_key` | `MORPH_API_KEY` | API key. Required. |
|
|
220
|
+
| `timeout` | `30.0` | Per-request timeout in seconds. |
|
|
221
|
+
| `max_retries` | `3` | Retries on transient failures. |
|
|
222
|
+
| `debug` | `False` | Log requests to stderr (`MORPH_DEBUG=1`). |
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
morphsdk-0.2.5/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Morph Python SDK
|
|
2
|
+
|
|
3
|
+
One typed client for Morph's specialized agent models: merge code edits with Fast Apply at ~10,500 tok/s, search a repo with WarpGrep, compress context with the compactor, route between models, and run browser tasks. Synchronous and async, Python 3.9+.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install morphsdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from morphsdk import Morph
|
|
13
|
+
|
|
14
|
+
morph = Morph(api_key="sk-...") # or set MORPH_API_KEY
|
|
15
|
+
|
|
16
|
+
result = morph.edit.file(
|
|
17
|
+
path="src/app.py",
|
|
18
|
+
instruction="Add a retry decorator to fetch_user",
|
|
19
|
+
code_edit="# ... existing code ...\n@retry(max_attempts=3)\ndef fetch_user(user_id):",
|
|
20
|
+
)
|
|
21
|
+
print(result.changes) # lines added / removed / modified
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The API key is read from `MORPH_API_KEY` if you do not pass `api_key=`. Get one at [morphllm.com](https://morphllm.com).
|
|
25
|
+
|
|
26
|
+
## Fast Apply: merge edits onto a file
|
|
27
|
+
|
|
28
|
+
`edit.file` reads the file, sends the original plus your edit snippet to the merge API, and writes the result back. Use `auto_write=False` to get the merged text without touching disk.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
result = morph.edit.file(
|
|
32
|
+
path="src/app.py",
|
|
33
|
+
instruction="Rename the handler to on_request",
|
|
34
|
+
code_edit="# ... existing code ...\ndef on_request(req):\n # ... existing code ...",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Code-in, code-out, no file I/O:
|
|
38
|
+
merged = morph.edit.code(
|
|
39
|
+
instruction="Add type hints",
|
|
40
|
+
original_code="def add(a, b):\n return a + b",
|
|
41
|
+
code_edit="def add(a: int, b: int) -> int:\n # ... existing code ...",
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## WarpGrep: search a codebase
|
|
46
|
+
|
|
47
|
+
`grep.search` runs the WarpGrep agent loop over a local repo. Pass `stream=True` to iterate per-turn steps before the final result. `search_github` does the same against a public repo.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
result = morph.grep.search(
|
|
51
|
+
query="where is the auth middleware registered?",
|
|
52
|
+
repo_root="/path/to/repo",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
for step in morph.grep.search(query="rate limiter", repo_root="/path/to/repo", stream=True):
|
|
56
|
+
print(step)
|
|
57
|
+
|
|
58
|
+
result = morph.grep.search_github(
|
|
59
|
+
query="how does the router pick a model?",
|
|
60
|
+
github="morphllm/landing",
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Explore: multi-turn codebase Q&A
|
|
65
|
+
|
|
66
|
+
`explore.run` answers a natural-language question about a repository and returns a summary plus the contexts it found. `thoroughness` is `quick`, `medium`, or `thorough`.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
result = morph.explore.run(
|
|
70
|
+
query="how does the WarpGrep agent loop call the model each turn?",
|
|
71
|
+
repo_root="/path/to/repo",
|
|
72
|
+
thoroughness="quick",
|
|
73
|
+
)
|
|
74
|
+
print(result.summary)
|
|
75
|
+
for ctx in result.contexts:
|
|
76
|
+
print(ctx)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Compact: compress context
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
result = morph.compact(
|
|
83
|
+
input="...long transcript or code...",
|
|
84
|
+
query="what matters for the next step",
|
|
85
|
+
compression_ratio=0.5,
|
|
86
|
+
)
|
|
87
|
+
print(result)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Router: pick a model for a prompt
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
choice = morph.router.select_model(input="Explain quicksort")
|
|
94
|
+
print(choice)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## More resources
|
|
98
|
+
|
|
99
|
+
The same client exposes `morph.search` (semantic codebase search by `repo_id`), `morph.browser` (`browser.run(task=..., url=...)`), `morph.git`, `morph.github`, `morph.mobile`, and `morph.reflex` (per-turn classifiers, OpenAI-fine-tuning-compatible). See the [docs](https://docs.morphllm.com) for the full surface.
|
|
100
|
+
|
|
101
|
+
## Async
|
|
102
|
+
|
|
103
|
+
`AsyncMorph` mirrors `Morph` with every method as `async def`; streaming methods return an `AsyncIterator`.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import asyncio
|
|
107
|
+
from morphsdk import AsyncMorph
|
|
108
|
+
|
|
109
|
+
async def main():
|
|
110
|
+
async with AsyncMorph(api_key="sk-...") as morph:
|
|
111
|
+
result = await morph.edit.file(
|
|
112
|
+
path="src/app.py",
|
|
113
|
+
instruction="Fix the off-by-one",
|
|
114
|
+
code_edit="# ... existing code ...",
|
|
115
|
+
)
|
|
116
|
+
print(result.changes)
|
|
117
|
+
|
|
118
|
+
asyncio.run(main())
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Framework adapters
|
|
122
|
+
|
|
123
|
+
Install the extra for your agent framework to get ready-made tool definitions.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pip install "morphsdk[openai]" # OpenAI tool schemas
|
|
127
|
+
pip install "morphsdk[anthropic]" # Anthropic tool schemas
|
|
128
|
+
pip install "morphsdk[langchain]" # LangChain tools
|
|
129
|
+
pip install "morphsdk[otel]" # OpenTelemetry tracing
|
|
130
|
+
pip install "morphsdk[all]" # everything above
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from morphsdk.adapters.openai import edit_file_tool_def
|
|
135
|
+
|
|
136
|
+
tools = [edit_file_tool_def()]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Errors
|
|
140
|
+
|
|
141
|
+
Every failure raises a subclass of `MorphError`, so you can catch one type or branch on specifics.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from morphsdk import (
|
|
145
|
+
MorphError,
|
|
146
|
+
AuthenticationError,
|
|
147
|
+
RateLimitError,
|
|
148
|
+
ValidationError,
|
|
149
|
+
NotFoundError,
|
|
150
|
+
PermissionDeniedError,
|
|
151
|
+
APIConnectionError,
|
|
152
|
+
APITimeoutError,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
morph.edit.file(path="src/app.py", instruction="...", code_edit="...")
|
|
157
|
+
except RateLimitError:
|
|
158
|
+
... # back off and retry
|
|
159
|
+
except MorphError as err:
|
|
160
|
+
... # everything else
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
`Morph(...)` and `AsyncMorph(...)` accept:
|
|
166
|
+
|
|
167
|
+
| Argument | Default | Purpose |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `api_key` | `MORPH_API_KEY` | API key. Required. |
|
|
170
|
+
| `timeout` | `30.0` | Per-request timeout in seconds. |
|
|
171
|
+
| `max_retries` | `3` | Retries on transient failures. |
|
|
172
|
+
| `debug` | `False` | Log requests to stderr (`MORPH_DEBUG=1`). |
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Morph SDK -- AI-powered code editing, search, browser automation, and more.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
from morphsdk import Morph
|
|
6
|
+
|
|
7
|
+
morph = Morph(api_key="sk-...")
|
|
8
|
+
|
|
9
|
+
# Edit files
|
|
10
|
+
result = morph.edit.file(path="app.py", instruction="Fix bug", code_edit="...")
|
|
11
|
+
|
|
12
|
+
# Search code
|
|
13
|
+
result = morph.search.code(query="authentication", repo_id="my-project")
|
|
14
|
+
|
|
15
|
+
# Browser automation
|
|
16
|
+
result = morph.browser.run(task="Test login", url="https://app.example.com")
|
|
17
|
+
|
|
18
|
+
# Context compression
|
|
19
|
+
result = morph.compact(input="Long text to compress...")
|
|
20
|
+
|
|
21
|
+
# Model routing
|
|
22
|
+
result = morph.router.select_model(input="Explain quicksort")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from ._client import AsyncMorph, Morph
|
|
26
|
+
from ._errors import (
|
|
27
|
+
APIConnectionError,
|
|
28
|
+
APITimeoutError,
|
|
29
|
+
AuthenticationError,
|
|
30
|
+
InternalError,
|
|
31
|
+
MorphError,
|
|
32
|
+
NotFoundError,
|
|
33
|
+
PermissionDeniedError,
|
|
34
|
+
RateLimitError,
|
|
35
|
+
ValidationError,
|
|
36
|
+
)
|
|
37
|
+
from ._version import __version__
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"__version__",
|
|
41
|
+
# Clients
|
|
42
|
+
"Morph",
|
|
43
|
+
"AsyncMorph",
|
|
44
|
+
# Errors
|
|
45
|
+
"MorphError",
|
|
46
|
+
"AuthenticationError",
|
|
47
|
+
"PermissionDeniedError",
|
|
48
|
+
"NotFoundError",
|
|
49
|
+
"RateLimitError",
|
|
50
|
+
"ValidationError",
|
|
51
|
+
"APIConnectionError",
|
|
52
|
+
"APITimeoutError",
|
|
53
|
+
"InternalError",
|
|
54
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Agent orchestration internals (WarpGrep multi-turn loop, Explore subagent).
|
|
2
|
+
|
|
3
|
+
The WarpGrep agent core is **async** -- it awaits the async providers and a
|
|
4
|
+
single ``httpx`` chat-completions call per turn. ``run_warp_grep`` /
|
|
5
|
+
``run_warp_grep_streaming`` are the public entry points consumed by the sync
|
|
6
|
+
``GrepResource`` (via :func:`asyncio.run` / a thread bridge) and, in a later
|
|
7
|
+
wave, directly by the async client.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .explore import (
|
|
13
|
+
DEFAULT_MAX_TURNS as EXPLORE_DEFAULT_MAX_TURNS,
|
|
14
|
+
)
|
|
15
|
+
from .explore import (
|
|
16
|
+
DEFAULT_THOROUGHNESS,
|
|
17
|
+
ExploreContext,
|
|
18
|
+
ExploreMessageEvent,
|
|
19
|
+
ExploreRunResult,
|
|
20
|
+
ExploreStepEvent,
|
|
21
|
+
ExploreThoroughness,
|
|
22
|
+
run_explore,
|
|
23
|
+
run_explore_streaming,
|
|
24
|
+
)
|
|
25
|
+
from .runner import (
|
|
26
|
+
TOOL_SPECS,
|
|
27
|
+
call_model,
|
|
28
|
+
run_warp_grep,
|
|
29
|
+
run_warp_grep_streaming,
|
|
30
|
+
)
|
|
31
|
+
from .types import (
|
|
32
|
+
AgentFinish,
|
|
33
|
+
AgentRunResult,
|
|
34
|
+
ChatMessage,
|
|
35
|
+
FinishFileSpec,
|
|
36
|
+
ResolvedContext,
|
|
37
|
+
ToolCallRef,
|
|
38
|
+
WarpGrepExecutionMetrics,
|
|
39
|
+
WarpGrepStep,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"run_warp_grep",
|
|
44
|
+
"run_warp_grep_streaming",
|
|
45
|
+
"call_model",
|
|
46
|
+
"TOOL_SPECS",
|
|
47
|
+
"run_explore",
|
|
48
|
+
"run_explore_streaming",
|
|
49
|
+
"ExploreRunResult",
|
|
50
|
+
"ExploreStepEvent",
|
|
51
|
+
"ExploreMessageEvent",
|
|
52
|
+
"ExploreContext",
|
|
53
|
+
"ExploreThoroughness",
|
|
54
|
+
"EXPLORE_DEFAULT_MAX_TURNS",
|
|
55
|
+
"DEFAULT_THOROUGHNESS",
|
|
56
|
+
"AgentRunResult",
|
|
57
|
+
"AgentFinish",
|
|
58
|
+
"ChatMessage",
|
|
59
|
+
"FinishFileSpec",
|
|
60
|
+
"ResolvedContext",
|
|
61
|
+
"ToolCallRef",
|
|
62
|
+
"WarpGrepStep",
|
|
63
|
+
"WarpGrepExecutionMetrics",
|
|
64
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Agent loop configuration constants.
|
|
2
|
+
|
|
3
|
+
Cross-checked against the TypeScript ``AGENT_CONFIG`` (``agent/config.ts``).
|
|
4
|
+
Every value here matches ``_constants.py`` **except the timeout**: the TS default
|
|
5
|
+
is 60_000 ms, while ``_constants.WARP_GREP_TIMEOUT`` is 30.0 s. The TS value is
|
|
6
|
+
authoritative for the agent loop, so we override it module-locally here (the
|
|
7
|
+
shared ``_constants`` module is owned elsewhere and intentionally left untouched).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from morphsdk._constants import (
|
|
15
|
+
WARP_GREP_MAX_CONTEXT_CHARS,
|
|
16
|
+
WARP_GREP_MAX_LIST_DEPTH,
|
|
17
|
+
WARP_GREP_MAX_LIST_RESULTS,
|
|
18
|
+
WARP_GREP_MAX_OUTPUT_LINES,
|
|
19
|
+
WARP_GREP_MAX_READ_LINES,
|
|
20
|
+
WARP_GREP_MAX_TURNS,
|
|
21
|
+
WARP_GREP_MODEL,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
DEFAULT_MODEL = WARP_GREP_MODEL
|
|
25
|
+
MAX_TURNS = WARP_GREP_MAX_TURNS
|
|
26
|
+
MAX_CONTEXT_CHARS = WARP_GREP_MAX_CONTEXT_CHARS
|
|
27
|
+
MAX_OUTPUT_LINES = WARP_GREP_MAX_OUTPUT_LINES
|
|
28
|
+
MAX_LIST_RESULTS = WARP_GREP_MAX_LIST_RESULTS
|
|
29
|
+
MAX_READ_LINES = WARP_GREP_MAX_READ_LINES
|
|
30
|
+
MAX_LIST_DEPTH = WARP_GREP_MAX_LIST_DEPTH
|
|
31
|
+
|
|
32
|
+
# TS AGENT_CONFIG.TIMEOUT_MS default is 60_000 ms (overridable via env). We expose
|
|
33
|
+
# it in **seconds** to match the rest of the Python SDK's timeout convention.
|
|
34
|
+
_DEFAULT_TIMEOUT_S = 60.0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_timeout_s() -> float:
|
|
38
|
+
"""Mirror TS ``parseEnvTimeout(MORPH_WARP_GREP_TIMEOUT, 60_000)`` (ms env -> s)."""
|
|
39
|
+
raw = os.environ.get("MORPH_WARP_GREP_TIMEOUT")
|
|
40
|
+
if not raw:
|
|
41
|
+
return _DEFAULT_TIMEOUT_S
|
|
42
|
+
try:
|
|
43
|
+
ms = int(raw)
|
|
44
|
+
except ValueError:
|
|
45
|
+
return _DEFAULT_TIMEOUT_S
|
|
46
|
+
return ms / 1000.0 if ms > 0 else _DEFAULT_TIMEOUT_S
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
DEFAULT_TIMEOUT_S = _resolve_timeout_s()
|
|
50
|
+
|
|
51
|
+
# Default code-search host for GitHub repo resolution + code-storage commands.
|
|
52
|
+
DEFAULT_CODE_SEARCH_URL = "https://morphllm.com"
|