python-codex 0.1.1__py3-none-any.whl → 0.1.3__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.
- pycodex/__init__.py +5 -1
- pycodex/agent.py +39 -41
- pycodex/cli.py +51 -43
- pycodex/collaboration.py +6 -7
- pycodex/compat.py +99 -0
- pycodex/context.py +87 -87
- pycodex/doctor.py +40 -40
- pycodex/model.py +69 -69
- pycodex/portable.py +33 -33
- pycodex/portable_server.py +22 -21
- pycodex/protocol.py +84 -86
- pycodex/runtime.py +36 -35
- pycodex/runtime_services.py +72 -69
- pycodex/tools/agent_tool_schemas.py +0 -2
- pycodex/tools/apply_patch_tool.py +43 -44
- pycodex/tools/base_tool.py +35 -36
- pycodex/tools/close_agent_tool.py +2 -4
- pycodex/tools/code_mode_manager.py +61 -61
- pycodex/tools/exec_command_tool.py +5 -6
- pycodex/tools/exec_runtime.js +3 -3
- pycodex/tools/exec_tool.py +3 -5
- pycodex/tools/grep_files_tool.py +10 -11
- pycodex/tools/list_dir_tool.py +8 -9
- pycodex/tools/read_file_tool.py +13 -14
- pycodex/tools/request_permissions_tool.py +2 -4
- pycodex/tools/request_user_input_tool.py +13 -14
- pycodex/tools/resume_agent_tool.py +2 -4
- pycodex/tools/send_input_tool.py +8 -9
- pycodex/tools/shell_command_tool.py +5 -6
- pycodex/tools/shell_tool.py +5 -6
- pycodex/tools/spawn_agent_tool.py +4 -5
- pycodex/tools/unified_exec_manager.py +79 -61
- pycodex/tools/update_plan_tool.py +4 -5
- pycodex/tools/view_image_tool.py +4 -5
- pycodex/tools/wait_agent_tool.py +2 -4
- pycodex/tools/wait_tool.py +4 -5
- pycodex/tools/web_search_tool.py +1 -3
- pycodex/tools/write_stdin_tool.py +4 -5
- pycodex/utils/dotenv.py +6 -6
- pycodex/utils/get_env.py +57 -34
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/visualize.py +79 -79
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/METADATA +15 -9
- python_codex-0.1.3.dist-info/RECORD +74 -0
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/WHEEL +1 -1
- responses_server/__init__.py +17 -0
- responses_server/__main__.py +5 -0
- responses_server/app.py +227 -0
- responses_server/config.py +63 -0
- responses_server/payload_processors.py +86 -0
- responses_server/server.py +63 -0
- responses_server/session_store.py +37 -0
- responses_server/stream_router.py +784 -0
- responses_server/tools/__init__.py +4 -0
- responses_server/tools/custom_adapter.py +235 -0
- responses_server/tools/web_search.py +263 -0
- python_codex-0.1.1.dist-info/RECORD +0 -62
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.1.dist-info → python_codex-0.1.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
import json
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CustomToolAdapterError(ValueError):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Mirrors the chat-completions apply_patch function tool text from
|
|
12
|
+
# `codex-rs/core/src/tools/handlers/apply_patch.rs`.
|
|
13
|
+
APPLY_PATCH_NAME = "apply_patch"
|
|
14
|
+
APPLY_PATCH_CHAT_INPUT_DESCRIPTION = "The entire contents of the apply_patch command"
|
|
15
|
+
APPLY_PATCH_CHAT_DESCRIPTION = """Use the `apply_patch` tool to edit files.
|
|
16
|
+
Your patch language is a stripped-down, file-oriented diff format designed to be easy to parse and safe to apply. You can think of it as a high-level envelope:
|
|
17
|
+
|
|
18
|
+
*** Begin Patch
|
|
19
|
+
[ one or more file sections ]
|
|
20
|
+
*** End Patch
|
|
21
|
+
|
|
22
|
+
Within that envelope, you get a sequence of file operations.
|
|
23
|
+
You MUST include a header to specify the action you are taking.
|
|
24
|
+
Each operation starts with one of three headers:
|
|
25
|
+
|
|
26
|
+
*** Add File: <path> - create a new file. Every following line is a + line (the initial contents).
|
|
27
|
+
*** Delete File: <path> - remove an existing file. Nothing follows.
|
|
28
|
+
*** Update File: <path> - patch an existing file in place (optionally with a rename).
|
|
29
|
+
|
|
30
|
+
May be immediately followed by *** Move to: <new path> if you want to rename the file.
|
|
31
|
+
Then one or more "hunks", each introduced by @@ (optionally followed by a hunk header).
|
|
32
|
+
Within a hunk each line starts with:
|
|
33
|
+
|
|
34
|
+
For instructions on [context_before] and [context_after]:
|
|
35
|
+
- By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change's [context_after] lines in the second change's [context_before] lines.
|
|
36
|
+
- If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have:
|
|
37
|
+
@@ class BaseClass
|
|
38
|
+
[3 lines of pre-context]
|
|
39
|
+
- [old_code]
|
|
40
|
+
+ [new_code]
|
|
41
|
+
[3 lines of post-context]
|
|
42
|
+
|
|
43
|
+
- If a code block is repeated so many times in a class or function such that even a single `@@` statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple `@@` statements to jump to the right context. For instance:
|
|
44
|
+
|
|
45
|
+
@@ class BaseClass
|
|
46
|
+
@@ \t def method():
|
|
47
|
+
[3 lines of pre-context]
|
|
48
|
+
- [old_code]
|
|
49
|
+
+ [new_code]
|
|
50
|
+
[3 lines of post-context]
|
|
51
|
+
|
|
52
|
+
The full grammar definition is below:
|
|
53
|
+
Patch := Begin { FileOp } End
|
|
54
|
+
Begin := "*** Begin Patch" NEWLINE
|
|
55
|
+
End := "*** End Patch" NEWLINE
|
|
56
|
+
FileOp := AddFile | DeleteFile | UpdateFile
|
|
57
|
+
AddFile := "*** Add File: " path NEWLINE { "+" line NEWLINE }
|
|
58
|
+
DeleteFile := "*** Delete File: " path NEWLINE
|
|
59
|
+
UpdateFile := "*** Update File: " path NEWLINE [ MoveTo ] { Hunk }
|
|
60
|
+
MoveTo := "*** Move to: " newPath NEWLINE
|
|
61
|
+
Hunk := "@@" [ header ] NEWLINE { HunkLine } [ "*** End of File" NEWLINE ]
|
|
62
|
+
HunkLine := (" " | "-" | "+") text NEWLINE
|
|
63
|
+
|
|
64
|
+
A full patch can combine several operations:
|
|
65
|
+
|
|
66
|
+
*** Begin Patch
|
|
67
|
+
*** Add File: hello.txt
|
|
68
|
+
+Hello world
|
|
69
|
+
*** Update File: src/app.py
|
|
70
|
+
*** Move to: src/main.py
|
|
71
|
+
@@ def greet():
|
|
72
|
+
-print("Hi")
|
|
73
|
+
+print("Hello, world!")
|
|
74
|
+
*** Delete File: obsolete.txt
|
|
75
|
+
*** End Patch
|
|
76
|
+
|
|
77
|
+
It is important to remember:
|
|
78
|
+
|
|
79
|
+
- You must include a header with your intended action (Add/Delete/Update)
|
|
80
|
+
- You must prefix new lines with `+` even when creating a new file
|
|
81
|
+
- File references can only be relative, NEVER ABSOLUTE.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def collect_custom_tool_names(raw_tools: 'object') -> 'typing.Set[str]':
|
|
86
|
+
names: 'typing.Set[str]' = set()
|
|
87
|
+
if not isinstance(raw_tools, list):
|
|
88
|
+
return names
|
|
89
|
+
for raw_tool in raw_tools:
|
|
90
|
+
if not isinstance(raw_tool, dict) or raw_tool.get("type") != "custom":
|
|
91
|
+
continue
|
|
92
|
+
name = _tool_name(raw_tool)
|
|
93
|
+
if name:
|
|
94
|
+
names.add(name)
|
|
95
|
+
return names
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_tool_definition(raw_tool: 'typing.Dict[str, object]') -> 'typing.Dict[str, object]':
|
|
99
|
+
name = _required_tool_name(raw_tool)
|
|
100
|
+
description = _build_description(raw_tool)
|
|
101
|
+
input_description = (
|
|
102
|
+
APPLY_PATCH_CHAT_INPUT_DESCRIPTION
|
|
103
|
+
if name == APPLY_PATCH_NAME
|
|
104
|
+
else "Raw tool input. Pass the freeform payload verbatim as a single string."
|
|
105
|
+
)
|
|
106
|
+
return {
|
|
107
|
+
"type": "function",
|
|
108
|
+
"name": name,
|
|
109
|
+
"function": {
|
|
110
|
+
"name": name,
|
|
111
|
+
"description": description,
|
|
112
|
+
"parameters": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"input": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": input_description,
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"required": ["input"],
|
|
121
|
+
"additionalProperties": False,
|
|
122
|
+
},
|
|
123
|
+
"strict": False,
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def build_tool_call(raw_item: 'typing.Dict[str, object]') -> 'typing.Dict[str, object]':
|
|
129
|
+
name = _required_item_name(raw_item)
|
|
130
|
+
return {
|
|
131
|
+
"id": str(raw_item.get("call_id", "")).strip() or name,
|
|
132
|
+
"type": "function",
|
|
133
|
+
"function": {
|
|
134
|
+
"name": name,
|
|
135
|
+
"arguments": json.dumps(
|
|
136
|
+
{"input": str(raw_item.get("input", "") or "")},
|
|
137
|
+
ensure_ascii=False,
|
|
138
|
+
separators=(",", ":"),
|
|
139
|
+
),
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def build_output_item(tool_call: 'typing.Dict[str, object]', index: 'int') -> 'typing.Dict[str, object]':
|
|
145
|
+
function = tool_call.get("function") or {}
|
|
146
|
+
if not isinstance(function, dict):
|
|
147
|
+
raise CustomToolAdapterError(
|
|
148
|
+
"outcomming custom tool call is missing function payload"
|
|
149
|
+
)
|
|
150
|
+
name = str(function.get("name", "")).strip()
|
|
151
|
+
if not name:
|
|
152
|
+
raise CustomToolAdapterError(
|
|
153
|
+
"outcomming custom tool call is missing `name`"
|
|
154
|
+
)
|
|
155
|
+
return {
|
|
156
|
+
"type": "custom_tool_call",
|
|
157
|
+
"call_id": str(tool_call.get("id", "")).strip() or f"call_{index}",
|
|
158
|
+
"name": name,
|
|
159
|
+
"input": extract_input_text(function.get("arguments")),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def extract_input_text(raw_arguments: 'object') -> 'str':
|
|
164
|
+
if isinstance(raw_arguments, dict):
|
|
165
|
+
parsed = deepcopy(raw_arguments)
|
|
166
|
+
else:
|
|
167
|
+
parsed = None
|
|
168
|
+
|
|
169
|
+
raw_text = raw_arguments if isinstance(raw_arguments, str) else None
|
|
170
|
+
if parsed is None and raw_text is not None:
|
|
171
|
+
try:
|
|
172
|
+
parsed = json.loads(raw_text or "{}")
|
|
173
|
+
except json.JSONDecodeError:
|
|
174
|
+
return raw_text
|
|
175
|
+
|
|
176
|
+
if isinstance(parsed, dict) and "input" in parsed:
|
|
177
|
+
value = parsed.get("input")
|
|
178
|
+
if isinstance(value, str):
|
|
179
|
+
return value
|
|
180
|
+
return json.dumps(value, ensure_ascii=False)
|
|
181
|
+
|
|
182
|
+
if raw_text is not None:
|
|
183
|
+
return raw_text
|
|
184
|
+
if parsed is not None:
|
|
185
|
+
return json.dumps(parsed, ensure_ascii=False)
|
|
186
|
+
return str(raw_arguments or "")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _build_description(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
190
|
+
name = _tool_name(raw_tool)
|
|
191
|
+
if name == APPLY_PATCH_NAME:
|
|
192
|
+
return APPLY_PATCH_CHAT_DESCRIPTION
|
|
193
|
+
|
|
194
|
+
description = str(raw_tool.get("description", "") or "").strip()
|
|
195
|
+
parts = [description] if description else []
|
|
196
|
+
parts.append(
|
|
197
|
+
"Chat-completions compatibility: provide the raw tool payload in the "
|
|
198
|
+
"`input` string field."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
raw_format = raw_tool.get("format")
|
|
202
|
+
if isinstance(raw_format, dict):
|
|
203
|
+
format_lines: 'typing.List[str]' = []
|
|
204
|
+
format_type = str(raw_format.get("type", "")).strip()
|
|
205
|
+
syntax = str(raw_format.get("syntax", "")).strip()
|
|
206
|
+
definition = str(raw_format.get("definition", "") or "").strip()
|
|
207
|
+
if format_type:
|
|
208
|
+
format_lines.append(f"Input format type: {format_type}")
|
|
209
|
+
if syntax:
|
|
210
|
+
format_lines.append(f"Input format syntax: {syntax}")
|
|
211
|
+
if definition:
|
|
212
|
+
format_lines.append("Input format definition:")
|
|
213
|
+
format_lines.append(definition)
|
|
214
|
+
if format_lines:
|
|
215
|
+
parts.append("\n".join(format_lines))
|
|
216
|
+
|
|
217
|
+
return "\n\n".join(parts)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _tool_name(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
221
|
+
return str(raw_tool.get("name", "")).strip()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _required_tool_name(raw_tool: 'typing.Dict[str, object]') -> 'str':
|
|
225
|
+
name = _tool_name(raw_tool)
|
|
226
|
+
if not name:
|
|
227
|
+
raise CustomToolAdapterError("custom tool definition is missing `name`")
|
|
228
|
+
return name
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _required_item_name(raw_item: 'typing.Dict[str, object]') -> 'str':
|
|
232
|
+
name = str(raw_item.get("name", "")).strip()
|
|
233
|
+
if not name:
|
|
234
|
+
raise CustomToolAdapterError("custom tool call is missing `name`")
|
|
235
|
+
return name
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from pycodex.protocol import JSONValue
|
|
6
|
+
from pycodex.tools.base_tool import BaseTool, ToolContext
|
|
7
|
+
import typing
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WebSearchTool(BaseTool):
|
|
11
|
+
name = "web_search"
|
|
12
|
+
description = (
|
|
13
|
+
"Mock web search tool for Responses compatibility. Returns empty results."
|
|
14
|
+
)
|
|
15
|
+
input_schema = {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"query": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Primary search query.",
|
|
21
|
+
},
|
|
22
|
+
"queries": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"description": "Optional batch of search queries.",
|
|
25
|
+
"items": {"type": "string"},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
"required": ["query"],
|
|
29
|
+
}
|
|
30
|
+
supports_parallel = False
|
|
31
|
+
|
|
32
|
+
async def run(self, context: 'ToolContext', args: 'JSONValue') -> 'JSONValue':
|
|
33
|
+
del context
|
|
34
|
+
query, queries = extract_queries(args)
|
|
35
|
+
output_payload: 'typing.Dict[str, object]' = {
|
|
36
|
+
"results": [],
|
|
37
|
+
"mock": True,
|
|
38
|
+
}
|
|
39
|
+
if query:
|
|
40
|
+
output_payload["query"] = query
|
|
41
|
+
if queries:
|
|
42
|
+
output_payload["queries"] = queries
|
|
43
|
+
return output_payload
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_tool_definition(tool: 'WebSearchTool') -> 'typing.Dict[str, object]':
|
|
47
|
+
return {
|
|
48
|
+
"type": "function",
|
|
49
|
+
"name": tool.name,
|
|
50
|
+
"function": {
|
|
51
|
+
"name": tool.name,
|
|
52
|
+
"description": tool.description,
|
|
53
|
+
"parameters": deepcopy(tool.input_schema),
|
|
54
|
+
"strict": False,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def partition_tool_calls(
|
|
60
|
+
tool: 'WebSearchTool',
|
|
61
|
+
tool_calls: 'typing.Dict[int, typing.Dict[str, object]]',
|
|
62
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
63
|
+
) -> 'typing.Tuple[typing.List[typing.Dict[str, object]], typing.Dict[int, typing.Dict[str, object]]]':
|
|
64
|
+
mock_tool_names = _collect_mock_tool_names(tool, outcomming_request)
|
|
65
|
+
mock_calls: 'typing.List[typing.Dict[str, object]]' = []
|
|
66
|
+
ordinary_tool_calls: 'typing.Dict[int, typing.Dict[str, object]]' = {}
|
|
67
|
+
for index in sorted(tool_calls):
|
|
68
|
+
tool_call = tool_calls[index]
|
|
69
|
+
function = tool_call.get("function") or {}
|
|
70
|
+
tool_name = ""
|
|
71
|
+
if isinstance(function, dict):
|
|
72
|
+
tool_name = str(function.get("name", "")).strip()
|
|
73
|
+
if tool_name in mock_tool_names:
|
|
74
|
+
mock_calls.append(tool_call)
|
|
75
|
+
continue
|
|
76
|
+
ordinary_tool_calls[index] = tool_call
|
|
77
|
+
return mock_calls, ordinary_tool_calls
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def hydrate_tool_call_names(
|
|
81
|
+
tool_calls: 'typing.Dict[int, typing.Dict[str, object]]',
|
|
82
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
83
|
+
) -> 'None':
|
|
84
|
+
raw_tools = outcomming_request.get("tools") or []
|
|
85
|
+
if not isinstance(raw_tools, list):
|
|
86
|
+
return
|
|
87
|
+
for index, tool_call in tool_calls.items():
|
|
88
|
+
function = tool_call.get("function") or {}
|
|
89
|
+
if not isinstance(function, dict):
|
|
90
|
+
continue
|
|
91
|
+
if str(function.get("name", "")).strip():
|
|
92
|
+
continue
|
|
93
|
+
if index >= len(raw_tools):
|
|
94
|
+
continue
|
|
95
|
+
raw_tool = raw_tools[index]
|
|
96
|
+
if not isinstance(raw_tool, dict) or raw_tool.get("type") != "function":
|
|
97
|
+
continue
|
|
98
|
+
raw_function = raw_tool.get("function") or {}
|
|
99
|
+
if not isinstance(raw_function, dict):
|
|
100
|
+
continue
|
|
101
|
+
name = str(raw_function.get("name", "")).strip()
|
|
102
|
+
if name:
|
|
103
|
+
function["name"] = name
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def build_output_items(
|
|
107
|
+
mock_search_calls: 'typing.List[typing.Dict[str, object]]',
|
|
108
|
+
) -> 'typing.List[typing.Dict[str, object]]':
|
|
109
|
+
items: 'typing.List[typing.Dict[str, object]]' = []
|
|
110
|
+
for tool_call in mock_search_calls:
|
|
111
|
+
function = tool_call.get("function") or {}
|
|
112
|
+
if not isinstance(function, dict):
|
|
113
|
+
continue
|
|
114
|
+
query, queries = extract_queries(function.get("arguments"))
|
|
115
|
+
action: 'typing.Dict[str, object]' = {"type": "search"}
|
|
116
|
+
if query:
|
|
117
|
+
action["query"] = query
|
|
118
|
+
if queries:
|
|
119
|
+
action["queries"] = queries
|
|
120
|
+
items.append(
|
|
121
|
+
{
|
|
122
|
+
"type": "web_search_call",
|
|
123
|
+
"id": str(tool_call.get("id", "")).strip() or "ws_mock",
|
|
124
|
+
"action": action,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
return items
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def build_followup_request(
|
|
131
|
+
tool: 'WebSearchTool',
|
|
132
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
133
|
+
mock_search_calls: 'typing.List[typing.Dict[str, object]]',
|
|
134
|
+
reasoning_text: 'typing.Union[str, None]' = None,
|
|
135
|
+
) -> 'typing.Dict[str, object]':
|
|
136
|
+
followup_request = deepcopy(outcomming_request)
|
|
137
|
+
messages = followup_request.get("messages") or []
|
|
138
|
+
if not isinstance(messages, list):
|
|
139
|
+
raise ValueError("outcomming request messages must be a list")
|
|
140
|
+
|
|
141
|
+
assistant_tool_calls: 'typing.List[typing.Dict[str, object]]' = []
|
|
142
|
+
for tool_call in mock_search_calls:
|
|
143
|
+
function = tool_call.get("function") or {}
|
|
144
|
+
if not isinstance(function, dict):
|
|
145
|
+
continue
|
|
146
|
+
assistant_tool_calls.append(
|
|
147
|
+
{
|
|
148
|
+
"id": str(tool_call.get("id", "")).strip() or "ws_mock",
|
|
149
|
+
"type": "function",
|
|
150
|
+
"function": {
|
|
151
|
+
"name": str(function.get("name", "")).strip() or tool.name,
|
|
152
|
+
"arguments": str(function.get("arguments", "") or "{}"),
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
if assistant_tool_calls:
|
|
157
|
+
assistant_message: 'typing.Dict[str, object]' = {
|
|
158
|
+
"role": "assistant",
|
|
159
|
+
"tool_calls": assistant_tool_calls,
|
|
160
|
+
}
|
|
161
|
+
if reasoning_text:
|
|
162
|
+
assistant_message["reasoning"] = reasoning_text
|
|
163
|
+
messages.append(assistant_message)
|
|
164
|
+
|
|
165
|
+
for tool_call in mock_search_calls:
|
|
166
|
+
tool_output = _build_mock_output((tool_call.get("function") or {}).get("arguments"))
|
|
167
|
+
messages.append(
|
|
168
|
+
{
|
|
169
|
+
"role": "tool",
|
|
170
|
+
"tool_call_id": str(tool_call.get("id", "")).strip() or "ws_mock",
|
|
171
|
+
"content": json.dumps(tool_output, ensure_ascii=False),
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
followup_request["messages"] = messages
|
|
176
|
+
raw_tools = followup_request.get("tools") or []
|
|
177
|
+
if isinstance(raw_tools, list):
|
|
178
|
+
filtered_tools = [
|
|
179
|
+
raw_tool for raw_tool in raw_tools if not is_mock_tool(tool, raw_tool)
|
|
180
|
+
]
|
|
181
|
+
if filtered_tools:
|
|
182
|
+
followup_request["tools"] = filtered_tools
|
|
183
|
+
else:
|
|
184
|
+
followup_request.pop("tools", None)
|
|
185
|
+
followup_request.pop("tool_choice", None)
|
|
186
|
+
followup_request.pop("parallel_tool_calls", None)
|
|
187
|
+
return followup_request
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def extract_queries(raw_arguments: 'JSONValue') -> 'typing.Tuple[str, typing.List[str]]':
|
|
191
|
+
if isinstance(raw_arguments, dict):
|
|
192
|
+
parsed = raw_arguments
|
|
193
|
+
else:
|
|
194
|
+
parsed = None
|
|
195
|
+
|
|
196
|
+
if isinstance(raw_arguments, str):
|
|
197
|
+
raw_text = raw_arguments
|
|
198
|
+
else:
|
|
199
|
+
raw_text = str(raw_arguments or "")
|
|
200
|
+
|
|
201
|
+
if parsed is None:
|
|
202
|
+
try:
|
|
203
|
+
parsed = json.loads(raw_text or "{}")
|
|
204
|
+
except json.JSONDecodeError:
|
|
205
|
+
query = raw_text.strip()
|
|
206
|
+
return query, [query] if query else []
|
|
207
|
+
|
|
208
|
+
if not isinstance(parsed, dict):
|
|
209
|
+
query = raw_text.strip()
|
|
210
|
+
return query, [query] if query else []
|
|
211
|
+
|
|
212
|
+
query = str(parsed.get("query", "")).strip()
|
|
213
|
+
queries_value = parsed.get("queries") or []
|
|
214
|
+
queries: 'typing.List[str]' = []
|
|
215
|
+
if isinstance(queries_value, list):
|
|
216
|
+
for value in queries_value:
|
|
217
|
+
normalized = str(value).strip()
|
|
218
|
+
if normalized:
|
|
219
|
+
queries.append(normalized)
|
|
220
|
+
if not query and queries:
|
|
221
|
+
query = queries[0]
|
|
222
|
+
if query and query not in queries:
|
|
223
|
+
queries.insert(0, query)
|
|
224
|
+
return query, queries
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def is_mock_tool(tool: 'WebSearchTool', raw_tool: 'object') -> 'bool':
|
|
228
|
+
if not isinstance(raw_tool, dict) or raw_tool.get("type") != "function":
|
|
229
|
+
return False
|
|
230
|
+
function = raw_tool.get("function") or {}
|
|
231
|
+
if not isinstance(function, dict):
|
|
232
|
+
return False
|
|
233
|
+
return (
|
|
234
|
+
str(function.get("name", "")).strip() == tool.name
|
|
235
|
+
and str(function.get("description", "")).strip() == tool.description
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _collect_mock_tool_names(
|
|
240
|
+
tool: 'WebSearchTool',
|
|
241
|
+
outcomming_request: 'typing.Dict[str, object]',
|
|
242
|
+
) -> 'typing.Set[str]':
|
|
243
|
+
names: 'typing.Set[str]' = set()
|
|
244
|
+
raw_tools = outcomming_request.get("tools") or []
|
|
245
|
+
if not isinstance(raw_tools, list):
|
|
246
|
+
return names
|
|
247
|
+
for raw_tool in raw_tools:
|
|
248
|
+
if is_mock_tool(tool, raw_tool):
|
|
249
|
+
names.add(tool.name)
|
|
250
|
+
return names
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _build_mock_output(raw_arguments: 'JSONValue') -> 'typing.Dict[str, object]':
|
|
254
|
+
query, queries = extract_queries(raw_arguments)
|
|
255
|
+
output_payload: 'typing.Dict[str, object]' = {
|
|
256
|
+
"results": [],
|
|
257
|
+
"mock": True,
|
|
258
|
+
}
|
|
259
|
+
if query:
|
|
260
|
+
output_payload["query"] = query
|
|
261
|
+
if queries:
|
|
262
|
+
output_payload["queries"] = queries
|
|
263
|
+
return output_payload
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
pycodex/__init__.py,sha256=T11JU1QHEk81TchhrTAOqVkvUUiQGlesk9PNaivjPrU,3052
|
|
2
|
-
pycodex/agent.py,sha256=ApIneWSqDxryf9hdmTRFL65AH4e-sn0MWuuR80951Ec,10069
|
|
3
|
-
pycodex/cli.py,sha256=JKHedrIgFaQgAc9h3P1MHvJTvUjL5dv7gxgHh-LCsDs,24520
|
|
4
|
-
pycodex/collaboration.py,sha256=XAM2enljzHMjzZVlLxbOQF0JhWgKW4qaaDfVcUdE47g,632
|
|
5
|
-
pycodex/context.py,sha256=8-Eg1TE4-GVbEfW0fNZjDWhjLypK3jBlKZY1haYYVPY,23143
|
|
6
|
-
pycodex/doctor.py,sha256=VN-qetM2qJCNRNTZXBMe44VSrEOu8kUXE01luLMF050,10357
|
|
7
|
-
pycodex/model.py,sha256=ZqXSucpzBm0kn2XfhBdKebdwvJQH1Jc9xMqBfPwOKGM,19672
|
|
8
|
-
pycodex/portable.py,sha256=Y2pY08pDiWITY0QYgH3F9YKpOe2EYtxE0qqSmrCkp_g,15260
|
|
9
|
-
pycodex/portable_server.py,sha256=xhEwySCJ41WnsowXM-Db6kkmCOVM02Lmd4pbN6hZzh0,7232
|
|
10
|
-
pycodex/protocol.py,sha256=8mQ7I-y9bxYueSr7d_yGj2Tw69t47OCgwvmxhwihdFw,10807
|
|
11
|
-
pycodex/runtime.py,sha256=tfEuyZmnTP625BQ0NMm-AGhjfQpXcv2EaZLtCJTnEmM,7757
|
|
12
|
-
pycodex/runtime_services.py,sha256=IIpv96YuxdWX2D1yu-HmtCx3Og-fYDPrA29vgAlyvJE,12331
|
|
13
|
-
pycodex/prompts/collaboration_default.md,sha256=MBTmPuMubeWfZgIeFVj49wwnwD4n_o3fVYAbgWKwu6Q,955
|
|
14
|
-
pycodex/prompts/collaboration_plan.md,sha256=IzjQAA5oHJz-3FmJdOjsJ4LHq6LW1tlEYMoy09n0HKk,8777
|
|
15
|
-
pycodex/prompts/default_base_instructions.md,sha256=D65mcj6bo4CDvVom-D9cbJRJVNquo0NghKt164_fRsg,20923
|
|
16
|
-
pycodex/prompts/exec_tools.json,sha256=2wYLsjL6VGzMnhFNCxE9IA_kxsxUspN68lr7JOlZq54,23369
|
|
17
|
-
pycodex/prompts/models.json,sha256=Xmuy5-FiiWdAe-Zz9w_-_kdEcRvIVssS1PugQSA64i8,251450
|
|
18
|
-
pycodex/prompts/subagent_tools.json,sha256=2ZOXyAiAaai2aazIlXdjjXb7cra5gZ2WYYbPltPaiYg,6199
|
|
19
|
-
pycodex/prompts/permissions/approval_policy/never.md,sha256=QceTG6wjkaJARjYr0HYV1aPnPcpGcrkRUW-smWRr6MQ,120
|
|
20
|
-
pycodex/prompts/permissions/approval_policy/on_failure.md,sha256=dfJjpXkpO6_ANdCKxbVJ8o4vyLxevrJWfKsGHTqtbkc,289
|
|
21
|
-
pycodex/prompts/permissions/approval_policy/on_request.md,sha256=hVQalzh0FAdkKzw5u-N4H7-LtC9ijVDlYsh3OKsZKzo,3661
|
|
22
|
-
pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md,sha256=mOinishp1k-wlPsaEuIOMn5GoVm_dAIsWIuEMmv2r7o,1725
|
|
23
|
-
pycodex/prompts/permissions/approval_policy/unless_trusted.md,sha256=XHpi1Lfx1iIXFbbQ_ho_kGstA3JN-RLho291HM30UNw,247
|
|
24
|
-
pycodex/prompts/permissions/sandbox_mode/danger_full_access.md,sha256=nZ7YHacBd3cAHKRZc9XClOOOnXJPXPh0WFBueh5C2D0,197
|
|
25
|
-
pycodex/prompts/permissions/sandbox_mode/read_only.md,sha256=2rAPEXsBYCcuttI5j3euS-3uv_v97catIsnhxlSQSIM,173
|
|
26
|
-
pycodex/prompts/permissions/sandbox_mode/workspace_write.md,sha256=lVN-LwrBbHqlv5yVjcd_mU8tzZW8jfKpTatJKIZu9HI,277
|
|
27
|
-
pycodex/tools/__init__.py,sha256=aSLXrr_31KGQgDfRow5zVIc-2-KdXlHaCE6qUnE4HWI,1772
|
|
28
|
-
pycodex/tools/agent_tool_schemas.py,sha256=adzoo0L6jCM7rLF-O1mNrq85HENQ_oKakjzwP6id1Yk,2069
|
|
29
|
-
pycodex/tools/apply_patch_tool.py,sha256=8hHxoWxWovAYdhr0XzDCZwljtMlApp7-TMZtyxe3adY,13691
|
|
30
|
-
pycodex/tools/base_tool.py,sha256=34NaExjTCyDn333MzlU4ShQ9WHocW3WREfg9ifdAqY4,5299
|
|
31
|
-
pycodex/tools/close_agent_tool.py,sha256=InKhe2gFWOcqE187J3XYrCckecsyAR48VeVmGdYhKWE,1623
|
|
32
|
-
pycodex/tools/code_mode_manager.py,sha256=pEczPyCq-3DpJlTtfUEpl4JAGolz8cOpI8mBc7gdrn0,18603
|
|
33
|
-
pycodex/tools/exec_command_tool.py,sha256=_fWfkQLGeINb2-cniY9CWskkAPjC9hE8pfjcBKkWXAg,3459
|
|
34
|
-
pycodex/tools/exec_runtime.js,sha256=ZczdhrzpSZ-qNnJDDJOe8Ap86HpzHb2FZ_vSpHszgLs,3625
|
|
35
|
-
pycodex/tools/exec_tool.py,sha256=b3_HSlyA0qB9rJulSckLBXOcKS3Lw5xvsQXU-wpNpCs,1414
|
|
36
|
-
pycodex/tools/grep_files_tool.py,sha256=twsx1KsvOWh8mi-lbycAtEyh6PeLxtNzl9LzdjwgAf4,4742
|
|
37
|
-
pycodex/tools/list_dir_tool.py,sha256=7S0RsE-NL04G47FmFZtzo-N-O3fPCYQFF0HrjEVuv3U,4749
|
|
38
|
-
pycodex/tools/read_file_tool.py,sha256=GVamhSNEZ1F1IU_og9GgSCzV12TL5t5b1fOUlzTOQBQ,8084
|
|
39
|
-
pycodex/tools/request_permissions_tool.py,sha256=GzYG-mc1ifTiIM8xK8WD-vPGUKpYgMX9zyqq9O6K-EE,3036
|
|
40
|
-
pycodex/tools/request_user_input_tool.py,sha256=hVqCYGPLA29uxBblIjUIoIrzXVg5KZ8PcxJ84obgNro,5715
|
|
41
|
-
pycodex/tools/resume_agent_tool.py,sha256=vF-vLtx90bOuSjLFvn9vgqrKiznIbwpmZ3HVJlCP1LE,1640
|
|
42
|
-
pycodex/tools/send_input_tool.py,sha256=z9PR5VoFd9SF4A-ol04Op8AXQF_3YLE74C6coiTXsZ0,3546
|
|
43
|
-
pycodex/tools/shell_command_tool.py,sha256=Bbah_5HirG1BJOIiqzuMa8kNHNYVPCUvxCFa09eRU6A,3500
|
|
44
|
-
pycodex/tools/shell_tool.py,sha256=BWSaEJZwfQg9Ta-ld2wqeXqavrZC7Y8qgF_vBEOxfYA,3678
|
|
45
|
-
pycodex/tools/spawn_agent_tool.py,sha256=LfJlGI0Ecp9HWNLlTubyybFq-xeRNChILq9ozT7piA8,3556
|
|
46
|
-
pycodex/tools/unified_exec_manager.py,sha256=dyuGfaljXTuV4_Bf5Y6OwFGj5_Kb1LW-sh9ni5mMF1Y,12602
|
|
47
|
-
pycodex/tools/update_plan_tool.py,sha256=l_EG39bEw5K9BIUKoSUsXYDb0W7aLn8SviKSb-bs7Os,2887
|
|
48
|
-
pycodex/tools/view_image_tool.py,sha256=yB915Jd3he4RjPANdm-dYdvio24OXKhBkAsp-9WVPBg,3924
|
|
49
|
-
pycodex/tools/wait_agent_tool.py,sha256=1tJ5spBtpZ_MjoMv5xmZz5WWKl7UwMqHIJ3SYKXEPZw,2596
|
|
50
|
-
pycodex/tools/wait_tool.py,sha256=G4YhaYeWAvUedzxTDAxbczVmXXUp7H2OKWO3qZlEyyk,2324
|
|
51
|
-
pycodex/tools/web_search_tool.py,sha256=hq78XF6MRvmNyPFSIp5eI0eYn9ryKdKvvoIOFNU3tuY,987
|
|
52
|
-
pycodex/tools/write_stdin_tool.py,sha256=DghlwPJnAqDoRBYyh1zeXRsfTXoQUdLJ8JQfrdE4RLs,2542
|
|
53
|
-
pycodex/utils/__init__.py,sha256=Hj_0a7RhkAblWkaHyFhpi0cs2nSjJ1NdavbkBgEHieY,1024
|
|
54
|
-
pycodex/utils/dotenv.py,sha256=sOpu6PA1VrsPZK13ynh3nZg3-u9pdiCXkW648v3pwZQ,1789
|
|
55
|
-
pycodex/utils/get_env.py,sha256=3l_KA8JCWW9mrKE9FiV2mTx10-e5MUbxaU8jbn3JaRs,6265
|
|
56
|
-
pycodex/utils/random_ids.py,sha256=vOEVgkwKeQXaHoEVU7IfsPPjKUABkGIeQ7lu9MZctU8,413
|
|
57
|
-
pycodex/utils/visualize.py,sha256=fK79pTfOwMmRrQujAosGt0nGyyJjpz0GfpWY8BkK91c,35369
|
|
58
|
-
python_codex-0.1.1.dist-info/METADATA,sha256=3w9Sv_prpkT9BQ7zYh4NUCDV3eVlcoaq2Pv6yAkacOc,13969
|
|
59
|
-
python_codex-0.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
60
|
-
python_codex-0.1.1.dist-info/entry_points.txt,sha256=sNUVakoVuTrzJH505ZgRTQxmtRRPUHV_EH0i6EbYTyM,45
|
|
61
|
-
python_codex-0.1.1.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
|
|
62
|
-
python_codex-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|