morphsdk 0.2.5__py3-none-any.whl
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/__init__.py +54 -0
- morphsdk/_agent/__init__.py +64 -0
- morphsdk/_agent/config.py +52 -0
- morphsdk/_agent/explore.py +276 -0
- morphsdk/_agent/github.py +57 -0
- morphsdk/_agent/helpers.py +133 -0
- morphsdk/_agent/parser.py +163 -0
- morphsdk/_agent/runner.py +524 -0
- morphsdk/_agent/tools.py +171 -0
- morphsdk/_agent/types.py +126 -0
- morphsdk/_base.py +309 -0
- morphsdk/_client.py +245 -0
- morphsdk/_config.py +37 -0
- morphsdk/_constants.py +53 -0
- morphsdk/_errors.py +111 -0
- morphsdk/_providers/__init__.py +36 -0
- morphsdk/_providers/_filter.py +92 -0
- morphsdk/_providers/base.py +94 -0
- morphsdk/_providers/code_storage_http.py +104 -0
- morphsdk/_providers/local.py +270 -0
- morphsdk/_providers/remote.py +161 -0
- morphsdk/_version.py +1 -0
- morphsdk/adapters/__init__.py +1 -0
- morphsdk/adapters/anthropic.py +360 -0
- morphsdk/adapters/langchain.py +120 -0
- morphsdk/adapters/openai.py +500 -0
- morphsdk/py.typed +0 -0
- morphsdk/resources/__init__.py +0 -0
- morphsdk/resources/browser.py +919 -0
- morphsdk/resources/compact.py +133 -0
- morphsdk/resources/edit.py +506 -0
- morphsdk/resources/explore.py +333 -0
- morphsdk/resources/git.py +861 -0
- morphsdk/resources/github.py +1214 -0
- morphsdk/resources/grep.py +583 -0
- morphsdk/resources/mobile.py +134 -0
- morphsdk/resources/reflex.py +414 -0
- morphsdk/resources/router.py +124 -0
- morphsdk/resources/search.py +110 -0
- morphsdk/tracing/__init__.py +70 -0
- morphsdk/tracing/_otel.py +101 -0
- morphsdk/tracing/core.py +249 -0
- morphsdk/tracing/interaction.py +284 -0
- morphsdk/tracing/otel.py +75 -0
- morphsdk/tracing/reflex.py +58 -0
- morphsdk/tracing/types.py +163 -0
- morphsdk/types/__init__.py +140 -0
- morphsdk/types/browser.py +118 -0
- morphsdk/types/compact.py +41 -0
- morphsdk/types/edit.py +31 -0
- morphsdk/types/explore.py +42 -0
- morphsdk/types/git.py +25 -0
- morphsdk/types/github.py +111 -0
- morphsdk/types/grep.py +41 -0
- morphsdk/types/mobile.py +25 -0
- morphsdk/types/reflex.py +137 -0
- morphsdk/types/router.py +21 -0
- morphsdk/types/search.py +33 -0
- morphsdk-0.2.5.dist-info/METADATA +226 -0
- morphsdk-0.2.5.dist-info/RECORD +61 -0
- morphsdk-0.2.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"""OpenAI framework adapter -- generates ChatCompletionTool-compatible dicts.
|
|
2
|
+
|
|
3
|
+
Each tool definition follows the OpenAI function-calling schema::
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
"type": "function",
|
|
7
|
+
"function": {
|
|
8
|
+
"name": "...",
|
|
9
|
+
"description": "...",
|
|
10
|
+
"parameters": { JSON Schema }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
The :class:`OpenAITool` wrapper pairs the definition with an ``execute()``
|
|
15
|
+
method so callers can dispatch tool calls in a single object.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from collections.abc import ItemsView, Iterator, KeysView, ValuesView
|
|
22
|
+
from typing import Any, Callable
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Tool descriptions -- mirrored from the TypeScript SDK
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
EDIT_FILE_DESCRIPTION = (
|
|
29
|
+
"Use this tool to make an edit to an existing file.\n\n"
|
|
30
|
+
"This will be read by a less intelligent model, which will quickly apply the edit. "
|
|
31
|
+
"You should make it clear what the edit is, while also minimizing the unchanged "
|
|
32
|
+
"code you write.\n\n"
|
|
33
|
+
"When writing the edit, you should specify each edit in sequence, with the special comment "
|
|
34
|
+
"// ... existing code ... to represent unchanged code in between edited lines.\n\n"
|
|
35
|
+
"For example:\n\n"
|
|
36
|
+
"// ... existing code ...\nFIRST_EDIT\n// ... existing code ...\nSECOND_EDIT\n"
|
|
37
|
+
"// ... existing code ...\nTHIRD_EDIT\n// ... existing code ...\n\n"
|
|
38
|
+
"You should still bias towards repeating as few lines of the original file as "
|
|
39
|
+
"possible to convey the change.\n"
|
|
40
|
+
"But, each edit should contain minimally sufficient context of unchanged lines around the code "
|
|
41
|
+
"you're editing to resolve ambiguity.\n\n"
|
|
42
|
+
"DO NOT omit spans of pre-existing code (or comments) without using the "
|
|
43
|
+
"// ... existing code ... "
|
|
44
|
+
"comment to indicate its absence. If you omit the existing code comment, the "
|
|
45
|
+
"model may inadvertently "
|
|
46
|
+
"delete these lines.\n\n"
|
|
47
|
+
"If you plan on deleting a section, you must provide context before and after to delete it.\n\n"
|
|
48
|
+
"Make sure it is clear what the edit should be, and where it should be applied.\n"
|
|
49
|
+
"Make edits to a file in a single edit_file call instead of multiple edit_file "
|
|
50
|
+
"calls to the same file. "
|
|
51
|
+
"The apply model can handle many distinct edits at once."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
EDIT_FILE_SYSTEM_PROMPT = (
|
|
55
|
+
"When the user is asking for edits to their code, use the edit_file tool to "
|
|
56
|
+
"highlight the changes "
|
|
57
|
+
"necessary and add comments to indicate where unchanged code has been skipped. For example:\n\n"
|
|
58
|
+
"// ... existing code ...\n{{ edit_1 }}\n// ... existing code ...\n{{ edit_2 }}\n"
|
|
59
|
+
"// ... existing code ...\n\n"
|
|
60
|
+
"Often this will mean that the start/end of the file will be skipped, but that's okay! "
|
|
61
|
+
"Rewrite the entire file ONLY if specifically requested. Always provide a brief explanation of "
|
|
62
|
+
"the updates, unless the user specifically requests only the code.\n\n"
|
|
63
|
+
"These edit codeblocks are also read by a less intelligent language model, colloquially called "
|
|
64
|
+
"the apply model, to update the file. To help specify the edit to the apply model, you will be "
|
|
65
|
+
"very careful when generating the codeblock to not introduce ambiguity. You will specify all "
|
|
66
|
+
"unchanged regions (code and comments) of the file with \"// ... existing code ...\" comment "
|
|
67
|
+
"markers. This will ensure the apply model will not delete existing unchanged code or comments "
|
|
68
|
+
"when editing the file."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
CODEBASE_SEARCH_DESCRIPTION = (
|
|
72
|
+
"Semantic search that finds code by meaning, not exact text.\n\n"
|
|
73
|
+
"Use this to explore unfamiliar codebases or ask \"how/where/what\" questions:\n"
|
|
74
|
+
"- \"How does X work?\" - Find implementation details\n"
|
|
75
|
+
"- \"Where is Y handled?\" - Locate specific functionality\n"
|
|
76
|
+
"- \"What happens when Z?\" - Understand flow\n\n"
|
|
77
|
+
"The tool uses two-stage retrieval (embedding similarity + reranking) to find the most "
|
|
78
|
+
"semantically relevant code chunks.\n\n"
|
|
79
|
+
"Returns code chunks with file paths, line ranges, and full content ranked by relevance."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
WARP_GREP_DESCRIPTION = (
|
|
83
|
+
"Very fast code search exploration subagent (not a grep tool) that runs parallel grep "
|
|
84
|
+
"and file read calls over multiple turns to locate relevant files and line ranges. "
|
|
85
|
+
"The search term should be a targeted natural-language query describing what you are "
|
|
86
|
+
"trying to find or accomplish, e.g. "
|
|
87
|
+
"\"Find where authentication requests are handled in the Express routes\" or "
|
|
88
|
+
"\"How do callers of processOrder handle the error case?\". "
|
|
89
|
+
"Fill in extra context you can infer to make the query specific. "
|
|
90
|
+
"Do not pass bare keywords or symbol names -- use grep directly for exact symbol lookups. "
|
|
91
|
+
"Use this tool first when exploring unfamiliar code. The results may be partial -- "
|
|
92
|
+
"follow up with classical search tools or direct file reads if needed. "
|
|
93
|
+
"When a task requires exploration beyond a single known file, ALWAYS default to "
|
|
94
|
+
"warpgrep codebase search before other search mechanisms."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
GITHUB_SEARCH_DESCRIPTION = (
|
|
98
|
+
"Always use this when debugging errors in third-party dependencies to check for "
|
|
99
|
+
"upstream fixes, open issues, or newer code paths. "
|
|
100
|
+
"Code search exploration subagent for public GitHub repositories (not your local "
|
|
101
|
+
"workspace -- use codebase_search for that). "
|
|
102
|
+
"Runs parallel grep and file read calls to locate relevant files and line ranges "
|
|
103
|
+
"in external github repositories without cloning the repository. "
|
|
104
|
+
"Pass a targeted natural-language query, not bare keywords. Useful for understanding "
|
|
105
|
+
"how a library works or investigating upstream bugs. "
|
|
106
|
+
"You must provide either github_url or owner_repo (at least one required)."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
GITHUB_READ_FILE_DESCRIPTION = (
|
|
110
|
+
"Read a single file (or a line range) from a public GitHub repository. "
|
|
111
|
+
"ONLY USE THIS WHEN YOU ALREADY KNOW THE EXACT FILE PATH YOU WANT TO READ, "
|
|
112
|
+
"for example after finding it via github_codebase_search results. "
|
|
113
|
+
"Takes owner/repo shorthand and the file path within the repo. "
|
|
114
|
+
"Optionally specify startLine and endLine (1-based) to read a specific range. "
|
|
115
|
+
"Returns the file content with line numbers."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
BROWSER_TOOL_DESCRIPTION = (
|
|
119
|
+
"Test and verify your implemented code in a live browser. This tool executes "
|
|
120
|
+
"natural language test instructions and returns a video recording plus detailed "
|
|
121
|
+
"logs to help you debug issues.\n\n"
|
|
122
|
+
"## When to Use\n"
|
|
123
|
+
"Use this AFTER coding is complete to verify your implementation:\n"
|
|
124
|
+
"- You've finished writing/modifying code and need to verify it works\n"
|
|
125
|
+
"- You need to test user interactions end-to-end (clicks, forms, navigation)\n"
|
|
126
|
+
"- You want to catch runtime errors, console warnings, or network failures\n"
|
|
127
|
+
"- You need visual confirmation that UI elements render and behave correctly\n\n"
|
|
128
|
+
"## Requirements\n"
|
|
129
|
+
"- URL: Must be publicly accessible (use tunnels like ngrok, Cloudflare, or "
|
|
130
|
+
"deploy to staging)\n"
|
|
131
|
+
"- Timing: Use this after implementation, not during coding\n"
|
|
132
|
+
"- Complexity: Set maxSteps higher (20-30) for multi-step user workflows"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
MOBILE_TOOL_DESCRIPTION = (
|
|
136
|
+
"Execute mobile app automation tasks using natural language.\n\n"
|
|
137
|
+
"This tool automates iOS and Android mobile apps on real BrowserStack cloud devices. "
|
|
138
|
+
"It interprets your instructions and interacts with the app's UI to perform tasks."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# Tool definition factories
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
def edit_file_tool_def(*, description: str | None = None) -> dict[str, Any]:
|
|
147
|
+
"""Generate an OpenAI ChatCompletionTool dict for ``edit_file``."""
|
|
148
|
+
return {
|
|
149
|
+
"type": "function",
|
|
150
|
+
"function": {
|
|
151
|
+
"name": "edit_file",
|
|
152
|
+
"description": description or EDIT_FILE_DESCRIPTION,
|
|
153
|
+
"parameters": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {
|
|
156
|
+
"target_filepath": {
|
|
157
|
+
"type": "string",
|
|
158
|
+
"description": "The path of the target file to modify",
|
|
159
|
+
},
|
|
160
|
+
"instruction": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"description": (
|
|
163
|
+
"A single sentence written in the first person describing "
|
|
164
|
+
"what the agent is changing. Used to help disambiguate "
|
|
165
|
+
"uncertainty in the edit."
|
|
166
|
+
),
|
|
167
|
+
},
|
|
168
|
+
"code_edit": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"description": (
|
|
171
|
+
"Specify ONLY the precise lines of code that you wish to "
|
|
172
|
+
"edit. Use `// ... existing code ...` for unchanged sections."
|
|
173
|
+
),
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
"required": ["target_filepath", "instruction", "code_edit"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def codebase_search_tool_def(*, description: str | None = None) -> dict[str, Any]:
|
|
183
|
+
"""Generate an OpenAI ChatCompletionTool dict for ``codebase_search``."""
|
|
184
|
+
return {
|
|
185
|
+
"type": "function",
|
|
186
|
+
"function": {
|
|
187
|
+
"name": "codebase_search",
|
|
188
|
+
"description": description or CODEBASE_SEARCH_DESCRIPTION,
|
|
189
|
+
"parameters": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"properties": {
|
|
192
|
+
"query": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"description": (
|
|
195
|
+
"A complete question or description about what you want to "
|
|
196
|
+
"understand. Ask as if talking to a colleague: \"How does X "
|
|
197
|
+
"work?\", \"What happens when Y?\", \"Where is Z handled?\""
|
|
198
|
+
),
|
|
199
|
+
},
|
|
200
|
+
"target_directories": {
|
|
201
|
+
"type": "array",
|
|
202
|
+
"items": {"type": "string"},
|
|
203
|
+
"description": (
|
|
204
|
+
"Prefix directory paths to limit search scope "
|
|
205
|
+
"(single directory only, no glob patterns). "
|
|
206
|
+
"Use [] to search entire repo."
|
|
207
|
+
),
|
|
208
|
+
},
|
|
209
|
+
"explanation": {
|
|
210
|
+
"type": "string",
|
|
211
|
+
"description": (
|
|
212
|
+
"One sentence explanation as to why this tool is being used, "
|
|
213
|
+
"and how it contributes to the goal."
|
|
214
|
+
),
|
|
215
|
+
},
|
|
216
|
+
"limit": {
|
|
217
|
+
"type": "number",
|
|
218
|
+
"description": "Maximum results to return (default: 10)",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
"required": ["query", "target_directories", "explanation"],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def grep_tool_def(
|
|
228
|
+
*,
|
|
229
|
+
name: str = "codebase_search",
|
|
230
|
+
description: str | None = None,
|
|
231
|
+
) -> dict[str, Any]:
|
|
232
|
+
"""Generate an OpenAI ChatCompletionTool dict for the WarpGrep agent."""
|
|
233
|
+
return {
|
|
234
|
+
"type": "function",
|
|
235
|
+
"function": {
|
|
236
|
+
"name": name,
|
|
237
|
+
"description": description or WARP_GREP_DESCRIPTION,
|
|
238
|
+
"parameters": {
|
|
239
|
+
"type": "object",
|
|
240
|
+
"properties": {
|
|
241
|
+
"search_term": {
|
|
242
|
+
"type": "string",
|
|
243
|
+
"description": (
|
|
244
|
+
"Search problem statement that this subagent is "
|
|
245
|
+
"supposed to research for"
|
|
246
|
+
),
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
"required": ["search_term"],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def github_search_tool_def(*, description: str | None = None) -> dict[str, Any]:
|
|
256
|
+
"""Generate an OpenAI ChatCompletionTool dict for ``github_codebase_search``."""
|
|
257
|
+
return {
|
|
258
|
+
"type": "function",
|
|
259
|
+
"function": {
|
|
260
|
+
"name": "github_codebase_search",
|
|
261
|
+
"description": description or GITHUB_SEARCH_DESCRIPTION,
|
|
262
|
+
"parameters": {
|
|
263
|
+
"type": "object",
|
|
264
|
+
"properties": {
|
|
265
|
+
"search_term": {
|
|
266
|
+
"type": "string",
|
|
267
|
+
"description": (
|
|
268
|
+
"A targeted natural-language query describing what to find "
|
|
269
|
+
"(e.g. \"How does auth work for SSO users?\"). "
|
|
270
|
+
"Not a keyword or symbol name."
|
|
271
|
+
),
|
|
272
|
+
},
|
|
273
|
+
"github_url": {
|
|
274
|
+
"type": "string",
|
|
275
|
+
"description": (
|
|
276
|
+
"GitHub repository URL to search "
|
|
277
|
+
"(e.g. \"https://github.com/vercel/next.js\"). "
|
|
278
|
+
"You must provide either github_url or owner_repo."
|
|
279
|
+
),
|
|
280
|
+
},
|
|
281
|
+
"owner_repo": {
|
|
282
|
+
"type": "string",
|
|
283
|
+
"description": (
|
|
284
|
+
"Repository owner/repo shorthand (e.g. \"vercel/next.js\"). "
|
|
285
|
+
"You must provide either github_url or owner_repo."
|
|
286
|
+
),
|
|
287
|
+
},
|
|
288
|
+
"branch": {
|
|
289
|
+
"type": "string",
|
|
290
|
+
"description": (
|
|
291
|
+
"Branch to search (defaults to the repository default branch)"
|
|
292
|
+
),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
"required": ["search_term"],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def github_read_file_tool_def(*, description: str | None = None) -> dict[str, Any]:
|
|
302
|
+
"""Generate an OpenAI ChatCompletionTool dict for ``readfile_github_search``."""
|
|
303
|
+
return {
|
|
304
|
+
"type": "function",
|
|
305
|
+
"function": {
|
|
306
|
+
"name": "readfile_github_search",
|
|
307
|
+
"description": description or GITHUB_READ_FILE_DESCRIPTION,
|
|
308
|
+
"parameters": {
|
|
309
|
+
"type": "object",
|
|
310
|
+
"properties": {
|
|
311
|
+
"github": {
|
|
312
|
+
"type": "string",
|
|
313
|
+
"description": "owner/repo shorthand (e.g., \"vercel/next.js\")",
|
|
314
|
+
},
|
|
315
|
+
"path": {
|
|
316
|
+
"type": "string",
|
|
317
|
+
"description": (
|
|
318
|
+
"File path within the repository (e.g., \"src/server/index.ts\")"
|
|
319
|
+
),
|
|
320
|
+
},
|
|
321
|
+
"startLine": {
|
|
322
|
+
"type": "number",
|
|
323
|
+
"description": "Start line number (1-based). Omit to start from beginning.",
|
|
324
|
+
},
|
|
325
|
+
"endLine": {
|
|
326
|
+
"type": "number",
|
|
327
|
+
"description": (
|
|
328
|
+
"End line number (1-based, inclusive). Omit to read to the end."
|
|
329
|
+
),
|
|
330
|
+
},
|
|
331
|
+
"branch": {
|
|
332
|
+
"type": "string",
|
|
333
|
+
"description": (
|
|
334
|
+
"Branch to read from (defaults to the repository default branch)"
|
|
335
|
+
),
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
"required": ["github", "path"],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def browser_tool_def(*, description: str | None = None) -> dict[str, Any]:
|
|
345
|
+
"""Generate an OpenAI ChatCompletionTool dict for ``browser_task``."""
|
|
346
|
+
return {
|
|
347
|
+
"type": "function",
|
|
348
|
+
"function": {
|
|
349
|
+
"name": "browser_task",
|
|
350
|
+
"description": description or BROWSER_TOOL_DESCRIPTION,
|
|
351
|
+
"parameters": {
|
|
352
|
+
"type": "object",
|
|
353
|
+
"properties": {
|
|
354
|
+
"task": {
|
|
355
|
+
"type": "string",
|
|
356
|
+
"description": (
|
|
357
|
+
"Natural language description of what to do "
|
|
358
|
+
"(e.g., \"Test checkout flow for buying a pineapple\")"
|
|
359
|
+
),
|
|
360
|
+
},
|
|
361
|
+
"url": {
|
|
362
|
+
"type": "string",
|
|
363
|
+
"description": (
|
|
364
|
+
"Starting URL (e.g., https://3000-xyz.e2b.dev). "
|
|
365
|
+
"Required if navigating to a specific page."
|
|
366
|
+
),
|
|
367
|
+
},
|
|
368
|
+
"maxSteps": {
|
|
369
|
+
"type": "number",
|
|
370
|
+
"description": (
|
|
371
|
+
"Maximum number of browser actions to take (1-50). "
|
|
372
|
+
"Default: 10. Use 15-30 for complex flows."
|
|
373
|
+
),
|
|
374
|
+
},
|
|
375
|
+
"region": {
|
|
376
|
+
"type": "string",
|
|
377
|
+
"enum": ["sfo", "lon"],
|
|
378
|
+
"description": (
|
|
379
|
+
"Browserless region: sfo (US West Coast) or lon (Europe). Default: sfo."
|
|
380
|
+
),
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
"required": ["task"],
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def mobile_tool_def(*, description: str | None = None) -> dict[str, Any]:
|
|
390
|
+
"""Generate an OpenAI ChatCompletionTool dict for ``mobile_task``."""
|
|
391
|
+
return {
|
|
392
|
+
"type": "function",
|
|
393
|
+
"function": {
|
|
394
|
+
"name": "mobile_task",
|
|
395
|
+
"description": description or MOBILE_TOOL_DESCRIPTION,
|
|
396
|
+
"parameters": {
|
|
397
|
+
"type": "object",
|
|
398
|
+
"properties": {
|
|
399
|
+
"task": {
|
|
400
|
+
"type": "string",
|
|
401
|
+
"description": (
|
|
402
|
+
"Natural language description of what to do in the mobile app"
|
|
403
|
+
),
|
|
404
|
+
},
|
|
405
|
+
"app": {
|
|
406
|
+
"type": "string",
|
|
407
|
+
"description": (
|
|
408
|
+
"Application identifier (bundle ID or BrowserStack bs:// URL)"
|
|
409
|
+
),
|
|
410
|
+
},
|
|
411
|
+
"platform": {
|
|
412
|
+
"type": "string",
|
|
413
|
+
"enum": ["ios", "android"],
|
|
414
|
+
"description": "Target platform (default: ios)",
|
|
415
|
+
},
|
|
416
|
+
"max_steps": {
|
|
417
|
+
"type": "number",
|
|
418
|
+
"description": "Maximum agent steps (default: 50)",
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
"required": ["task", "app"],
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# ---------------------------------------------------------------------------
|
|
428
|
+
# OpenAITool -- pairs a definition with an executor
|
|
429
|
+
# ---------------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
class OpenAITool:
|
|
432
|
+
"""A tool definition paired with an executor for OpenAI chat completions.
|
|
433
|
+
|
|
434
|
+
Behaves like a dict (can be passed directly to ``tools=[tool]``) while
|
|
435
|
+
also exposing ``execute()`` and ``format_result()`` methods.
|
|
436
|
+
|
|
437
|
+
Example::
|
|
438
|
+
|
|
439
|
+
tool = morph.edit.as_openai_tool(base_dir="./src")
|
|
440
|
+
|
|
441
|
+
# Pass to OpenAI
|
|
442
|
+
response = client.chat.completions.create(
|
|
443
|
+
model="gpt-4o",
|
|
444
|
+
tools=[tool], # works because OpenAITool acts as a dict
|
|
445
|
+
messages=[...],
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# Execute
|
|
449
|
+
result = tool.execute(tool_call.function.arguments)
|
|
450
|
+
formatted = tool.format_result(result)
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
def __init__(
|
|
454
|
+
self,
|
|
455
|
+
definition: dict[str, Any],
|
|
456
|
+
executor: Callable[..., Any],
|
|
457
|
+
formatter: Callable[..., str],
|
|
458
|
+
system_prompt: str = "",
|
|
459
|
+
) -> None:
|
|
460
|
+
self._definition = definition
|
|
461
|
+
self._executor = executor
|
|
462
|
+
self._formatter = formatter
|
|
463
|
+
self.system_prompt = system_prompt
|
|
464
|
+
|
|
465
|
+
# -- dict protocol so OpenAI SDK accepts this as a tool ---
|
|
466
|
+
def __getitem__(self, key: str) -> Any:
|
|
467
|
+
return self._definition[key]
|
|
468
|
+
|
|
469
|
+
def __contains__(self, key: str) -> bool:
|
|
470
|
+
return key in self._definition
|
|
471
|
+
|
|
472
|
+
def __iter__(self) -> Iterator[str]:
|
|
473
|
+
return iter(self._definition)
|
|
474
|
+
|
|
475
|
+
def keys(self) -> KeysView[str]:
|
|
476
|
+
return self._definition.keys()
|
|
477
|
+
|
|
478
|
+
def items(self) -> ItemsView[str, Any]:
|
|
479
|
+
return self._definition.items()
|
|
480
|
+
|
|
481
|
+
def values(self) -> ValuesView[Any]:
|
|
482
|
+
return self._definition.values()
|
|
483
|
+
|
|
484
|
+
def to_dict(self) -> dict[str, Any]:
|
|
485
|
+
"""Return the raw tool definition dict."""
|
|
486
|
+
return self._definition
|
|
487
|
+
|
|
488
|
+
# -- execution ---
|
|
489
|
+
def execute(self, input: dict[str, Any] | str) -> Any:
|
|
490
|
+
"""Execute the tool with the given input.
|
|
491
|
+
|
|
492
|
+
*input* may be a dict or a JSON string (as returned by
|
|
493
|
+
``tool_call.function.arguments``).
|
|
494
|
+
"""
|
|
495
|
+
args: dict[str, Any] = json.loads(input) if isinstance(input, str) else input
|
|
496
|
+
return self._executor(**args)
|
|
497
|
+
|
|
498
|
+
def format_result(self, result: Any) -> str:
|
|
499
|
+
"""Format a result for passing back as a tool message."""
|
|
500
|
+
return self._formatter(result)
|
morphsdk/py.typed
ADDED
|
File without changes
|
|
File without changes
|