python-codex 0.2.6__py3-none-any.whl → 0.3.0__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 +18 -14
- pycodex/agent.py +468 -462
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -436
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +329 -252
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +90 -52
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +235 -803
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -174
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +4 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +201 -0
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +13 -13
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +50 -66
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/utils/image_utils.py +76 -0
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +263 -161
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +25 -22
- responses_server/messages_api.py +96 -49
- responses_server/payload_processors.py +25 -19
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +196 -107
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +51 -13
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +470 -384
- workspace_server/workspace.html +859 -232
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +168 -100
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -553
- python_codex-0.2.6.dist-info/METADATA +0 -441
- python_codex-0.2.6.dist-info/RECORD +0 -91
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
responses_server/messages_api.py
CHANGED
|
@@ -9,8 +9,8 @@ class MessagesAPIAdapterError(ValueError):
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def build_messages_request(
|
|
12
|
-
outcomming_request:
|
|
13
|
-
) ->
|
|
12
|
+
outcomming_request: "typing.Dict[str, object]",
|
|
13
|
+
) -> "typing.Dict[str, object]":
|
|
14
14
|
model = str(outcomming_request.get("model", "")).strip()
|
|
15
15
|
if not model:
|
|
16
16
|
raise MessagesAPIAdapterError("outcomming request is missing `model`")
|
|
@@ -19,13 +19,11 @@ def build_messages_request(
|
|
|
19
19
|
if not isinstance(raw_messages, list):
|
|
20
20
|
raise MessagesAPIAdapterError("outcomming request `messages` must be a list")
|
|
21
21
|
|
|
22
|
-
system_blocks:
|
|
23
|
-
messages:
|
|
22
|
+
system_blocks: "typing.List[typing.Dict[str, object]]" = []
|
|
23
|
+
messages: "typing.List[typing.Dict[str, object]]" = []
|
|
24
24
|
for raw_message in raw_messages:
|
|
25
25
|
if not isinstance(raw_message, dict):
|
|
26
|
-
raise MessagesAPIAdapterError(
|
|
27
|
-
"outcomming request messages must be objects"
|
|
28
|
-
)
|
|
26
|
+
raise MessagesAPIAdapterError("outcomming request messages must be objects")
|
|
29
27
|
role = str(raw_message.get("role", "")).strip()
|
|
30
28
|
if role in {"developer", "system"}:
|
|
31
29
|
text = str(raw_message.get("content", "") or "")
|
|
@@ -60,7 +58,7 @@ def build_messages_request(
|
|
|
60
58
|
f"unsupported outcomming message role for messages API: {role!r}"
|
|
61
59
|
)
|
|
62
60
|
|
|
63
|
-
payload:
|
|
61
|
+
payload: "typing.Dict[str, object]" = {
|
|
64
62
|
"model": model,
|
|
65
63
|
"messages": messages,
|
|
66
64
|
"max_tokens": _resolve_max_tokens(outcomming_request),
|
|
@@ -84,12 +82,12 @@ def build_messages_request(
|
|
|
84
82
|
|
|
85
83
|
|
|
86
84
|
def iter_chat_chunks(
|
|
87
|
-
event_name:
|
|
88
|
-
payload:
|
|
89
|
-
state:
|
|
90
|
-
) ->
|
|
85
|
+
event_name: "typing.Union[str, None]",
|
|
86
|
+
payload: "typing.Dict[str, object]",
|
|
87
|
+
state: "typing.Dict[str, object]",
|
|
88
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
91
89
|
event_type = str(payload.get("type") or event_name or "").strip()
|
|
92
|
-
chunks:
|
|
90
|
+
chunks: "typing.List[typing.Dict[str, object]]" = []
|
|
93
91
|
|
|
94
92
|
if event_type == "message_start":
|
|
95
93
|
message = payload.get("message") or {}
|
|
@@ -188,21 +186,72 @@ def iter_chat_chunks(
|
|
|
188
186
|
return chunks
|
|
189
187
|
|
|
190
188
|
|
|
191
|
-
def saw_message_stop(state:
|
|
189
|
+
def saw_message_stop(state: "typing.Dict[str, object]") -> "bool":
|
|
192
190
|
return bool(state.get("saw_message_stop"))
|
|
193
191
|
|
|
194
192
|
|
|
195
|
-
def _build_text_blocks(
|
|
193
|
+
def _build_text_blocks(
|
|
194
|
+
raw_content: "object",
|
|
195
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
196
|
+
if isinstance(raw_content, list):
|
|
197
|
+
blocks: "typing.List[typing.Dict[str, object]]" = []
|
|
198
|
+
for raw_part in raw_content:
|
|
199
|
+
if not isinstance(raw_part, dict):
|
|
200
|
+
raise MessagesAPIAdapterError("message content parts must be objects")
|
|
201
|
+
part_type = str(raw_part.get("type", "")).strip()
|
|
202
|
+
if part_type == "text":
|
|
203
|
+
text = str(raw_part.get("text", "") or "")
|
|
204
|
+
if text:
|
|
205
|
+
blocks.append({"type": "text", "text": text})
|
|
206
|
+
continue
|
|
207
|
+
if part_type == "image_url":
|
|
208
|
+
blocks.append(_build_image_block(raw_part))
|
|
209
|
+
continue
|
|
210
|
+
raise MessagesAPIAdapterError(
|
|
211
|
+
f"unsupported outcomming content part type for messages API: {part_type!r}"
|
|
212
|
+
)
|
|
213
|
+
return blocks
|
|
214
|
+
|
|
196
215
|
text = str(raw_content or "")
|
|
197
216
|
if not text:
|
|
198
217
|
return []
|
|
199
218
|
return [{"type": "text", "text": text}]
|
|
200
219
|
|
|
201
220
|
|
|
221
|
+
def _build_image_block(
|
|
222
|
+
raw_part: "typing.Dict[str, object]",
|
|
223
|
+
) -> "typing.Dict[str, object]":
|
|
224
|
+
image_url = raw_part.get("image_url") or {}
|
|
225
|
+
if not isinstance(image_url, dict):
|
|
226
|
+
raise MessagesAPIAdapterError("`image_url` content parts must be objects")
|
|
227
|
+
url = str(image_url.get("url", "") or "").strip()
|
|
228
|
+
if not url:
|
|
229
|
+
raise MessagesAPIAdapterError(
|
|
230
|
+
"`image_url` content parts must carry a non-empty `url`"
|
|
231
|
+
)
|
|
232
|
+
if not url.startswith("data:"):
|
|
233
|
+
return {"type": "image", "source": {"type": "url", "url": url}}
|
|
234
|
+
|
|
235
|
+
header, _, data = url.partition(",")
|
|
236
|
+
media_type = header[len("data:") :].split(";")[0].strip()
|
|
237
|
+
if not media_type or not header.endswith(";base64"):
|
|
238
|
+
raise MessagesAPIAdapterError(
|
|
239
|
+
"`image_url` data URLs must be base64 encoded with a media type"
|
|
240
|
+
)
|
|
241
|
+
return {
|
|
242
|
+
"type": "image",
|
|
243
|
+
"source": {
|
|
244
|
+
"type": "base64",
|
|
245
|
+
"media_type": media_type,
|
|
246
|
+
"data": data,
|
|
247
|
+
},
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
202
251
|
def _build_assistant_blocks(
|
|
203
|
-
raw_message:
|
|
204
|
-
) ->
|
|
205
|
-
blocks:
|
|
252
|
+
raw_message: "typing.Dict[str, object]",
|
|
253
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
254
|
+
blocks: "typing.List[typing.Dict[str, object]]" = []
|
|
206
255
|
reasoning = str(raw_message.get("reasoning", "") or "")
|
|
207
256
|
if reasoning:
|
|
208
257
|
blocks.append({"type": "thinking", "thinking": reasoning})
|
|
@@ -235,8 +284,8 @@ def _build_assistant_blocks(
|
|
|
235
284
|
|
|
236
285
|
|
|
237
286
|
def _build_tool_result_block(
|
|
238
|
-
raw_message:
|
|
239
|
-
) ->
|
|
287
|
+
raw_message: "typing.Dict[str, object]",
|
|
288
|
+
) -> "typing.Dict[str, object]":
|
|
240
289
|
return {
|
|
241
290
|
"type": "tool_result",
|
|
242
291
|
"tool_use_id": str(raw_message.get("tool_call_id", "")).strip(),
|
|
@@ -245,9 +294,9 @@ def _build_tool_result_block(
|
|
|
245
294
|
|
|
246
295
|
|
|
247
296
|
def _translate_tools(
|
|
248
|
-
raw_tools:
|
|
249
|
-
) ->
|
|
250
|
-
translated:
|
|
297
|
+
raw_tools: "object",
|
|
298
|
+
) -> "typing.List[typing.Dict[str, object]]":
|
|
299
|
+
translated: "typing.List[typing.Dict[str, object]]" = []
|
|
251
300
|
if not isinstance(raw_tools, list):
|
|
252
301
|
return translated
|
|
253
302
|
for raw_tool in raw_tools:
|
|
@@ -272,9 +321,9 @@ def _translate_tools(
|
|
|
272
321
|
|
|
273
322
|
|
|
274
323
|
def _translate_tool_choice(
|
|
275
|
-
raw_tool_choice:
|
|
276
|
-
parallel_tool_calls:
|
|
277
|
-
) ->
|
|
324
|
+
raw_tool_choice: "object",
|
|
325
|
+
parallel_tool_calls: "object",
|
|
326
|
+
) -> "typing.Union[typing.Dict[str, object], None]":
|
|
278
327
|
if raw_tool_choice is None:
|
|
279
328
|
if parallel_tool_calls is False:
|
|
280
329
|
return {
|
|
@@ -283,7 +332,7 @@ def _translate_tool_choice(
|
|
|
283
332
|
}
|
|
284
333
|
return None
|
|
285
334
|
|
|
286
|
-
translated:
|
|
335
|
+
translated: "typing.Dict[str, object]"
|
|
287
336
|
if isinstance(raw_tool_choice, str):
|
|
288
337
|
choice = raw_tool_choice.strip()
|
|
289
338
|
if choice == "auto":
|
|
@@ -306,9 +355,7 @@ def _translate_tool_choice(
|
|
|
306
355
|
if not name:
|
|
307
356
|
name = str(raw_tool_choice.get("name", "")).strip()
|
|
308
357
|
if not name:
|
|
309
|
-
raise MessagesAPIAdapterError(
|
|
310
|
-
"function tool_choice is missing `name`"
|
|
311
|
-
)
|
|
358
|
+
raise MessagesAPIAdapterError("function tool_choice is missing `name`")
|
|
312
359
|
translated = {
|
|
313
360
|
"type": "tool",
|
|
314
361
|
"name": name,
|
|
@@ -327,7 +374,7 @@ def _translate_tool_choice(
|
|
|
327
374
|
return translated
|
|
328
375
|
|
|
329
376
|
|
|
330
|
-
def _parse_json_object(raw_value:
|
|
377
|
+
def _parse_json_object(raw_value: "object") -> "typing.Dict[str, object]":
|
|
331
378
|
if isinstance(raw_value, dict):
|
|
332
379
|
return dict(raw_value)
|
|
333
380
|
if isinstance(raw_value, str):
|
|
@@ -350,7 +397,7 @@ def _parse_json_object(raw_value: 'object') -> 'typing.Dict[str, object]':
|
|
|
350
397
|
)
|
|
351
398
|
|
|
352
399
|
|
|
353
|
-
def _resolve_max_tokens(outcomming_request:
|
|
400
|
+
def _resolve_max_tokens(outcomming_request: "typing.Dict[str, object]") -> "int":
|
|
354
401
|
raw_value = outcomming_request.get("max_tokens")
|
|
355
402
|
if isinstance(raw_value, bool):
|
|
356
403
|
return DEFAULT_MESSAGES_MAX_TOKENS
|
|
@@ -359,7 +406,7 @@ def _resolve_max_tokens(outcomming_request: 'typing.Dict[str, object]') -> 'int'
|
|
|
359
406
|
return DEFAULT_MESSAGES_MAX_TOKENS
|
|
360
407
|
|
|
361
408
|
|
|
362
|
-
def _usage_chunk(raw_usage:
|
|
409
|
+
def _usage_chunk(raw_usage: "object") -> "typing.Union[typing.Dict[str, object], None]":
|
|
363
410
|
usage = _translate_usage(raw_usage)
|
|
364
411
|
if not usage:
|
|
365
412
|
return None
|
|
@@ -369,10 +416,10 @@ def _usage_chunk(raw_usage: 'object') -> 'typing.Union[typing.Dict[str, object],
|
|
|
369
416
|
}
|
|
370
417
|
|
|
371
418
|
|
|
372
|
-
def _translate_usage(raw_usage:
|
|
419
|
+
def _translate_usage(raw_usage: "object") -> "typing.Dict[str, object]":
|
|
373
420
|
if not isinstance(raw_usage, dict):
|
|
374
421
|
return {}
|
|
375
|
-
usage:
|
|
422
|
+
usage: "typing.Dict[str, object]" = {}
|
|
376
423
|
input_tokens = raw_usage.get("input_tokens")
|
|
377
424
|
output_tokens = raw_usage.get("output_tokens")
|
|
378
425
|
if isinstance(input_tokens, int):
|
|
@@ -385,7 +432,7 @@ def _translate_usage(raw_usage: 'object') -> 'typing.Dict[str, object]':
|
|
|
385
432
|
elif isinstance(input_tokens, int) and isinstance(output_tokens, int):
|
|
386
433
|
usage["total_tokens"] = input_tokens + output_tokens
|
|
387
434
|
|
|
388
|
-
input_details:
|
|
435
|
+
input_details: "typing.Dict[str, int]" = {}
|
|
389
436
|
cache_creation = raw_usage.get("cache_creation_input_tokens")
|
|
390
437
|
if isinstance(cache_creation, int):
|
|
391
438
|
input_details["cache_creation_input_tokens"] = cache_creation
|
|
@@ -397,7 +444,7 @@ def _translate_usage(raw_usage: 'object') -> 'typing.Dict[str, object]':
|
|
|
397
444
|
return usage
|
|
398
445
|
|
|
399
446
|
|
|
400
|
-
def _normalize_index(raw_index:
|
|
447
|
+
def _normalize_index(raw_index: "object") -> "int":
|
|
401
448
|
if isinstance(raw_index, int):
|
|
402
449
|
return raw_index
|
|
403
450
|
try:
|
|
@@ -406,7 +453,7 @@ def _normalize_index(raw_index: 'object') -> 'int':
|
|
|
406
453
|
return 0
|
|
407
454
|
|
|
408
455
|
|
|
409
|
-
def _translate_stop_reason(raw_stop_reason:
|
|
456
|
+
def _translate_stop_reason(raw_stop_reason: "object") -> "typing.Union[str, None]":
|
|
410
457
|
if not isinstance(raw_stop_reason, str):
|
|
411
458
|
return None
|
|
412
459
|
stop_reason = raw_stop_reason.strip()
|
|
@@ -421,21 +468,21 @@ def _translate_stop_reason(raw_stop_reason: 'object') -> 'typing.Union[str, None
|
|
|
421
468
|
return stop_reason
|
|
422
469
|
|
|
423
470
|
|
|
424
|
-
def _chat_text_chunk(text:
|
|
471
|
+
def _chat_text_chunk(text: "str") -> "typing.Dict[str, object]":
|
|
425
472
|
return _chat_delta_chunk({"content": text})
|
|
426
473
|
|
|
427
474
|
|
|
428
|
-
def _chat_reasoning_chunk(reasoning:
|
|
475
|
+
def _chat_reasoning_chunk(reasoning: "str") -> "typing.Dict[str, object]":
|
|
429
476
|
return _chat_delta_chunk({"reasoning_content": reasoning})
|
|
430
477
|
|
|
431
478
|
|
|
432
479
|
def _chat_tool_chunk(
|
|
433
|
-
index:
|
|
434
|
-
call_id:
|
|
435
|
-
name:
|
|
436
|
-
arguments:
|
|
437
|
-
) ->
|
|
438
|
-
tool_call:
|
|
480
|
+
index: "int",
|
|
481
|
+
call_id: "str" = "",
|
|
482
|
+
name: "str" = "",
|
|
483
|
+
arguments: "str" = "",
|
|
484
|
+
) -> "typing.Dict[str, object]":
|
|
485
|
+
tool_call: "typing.Dict[str, object]" = {
|
|
439
486
|
"index": index,
|
|
440
487
|
"function": {},
|
|
441
488
|
}
|
|
@@ -453,7 +500,7 @@ def _chat_tool_chunk(
|
|
|
453
500
|
return _chat_delta_chunk({"tool_calls": [tool_call]})
|
|
454
501
|
|
|
455
502
|
|
|
456
|
-
def _chat_delta_chunk(delta:
|
|
503
|
+
def _chat_delta_chunk(delta: "typing.Dict[str, object]") -> "typing.Dict[str, object]":
|
|
457
504
|
return {
|
|
458
505
|
"choices": [
|
|
459
506
|
{
|
|
@@ -465,7 +512,7 @@ def _chat_delta_chunk(delta: 'typing.Dict[str, object]') -> 'typing.Dict[str, ob
|
|
|
465
512
|
}
|
|
466
513
|
|
|
467
514
|
|
|
468
|
-
def _chat_finish_chunk(finish_reason:
|
|
515
|
+
def _chat_finish_chunk(finish_reason: "str") -> "typing.Dict[str, object]":
|
|
469
516
|
return {
|
|
470
517
|
"choices": [
|
|
471
518
|
{
|
|
@@ -477,5 +524,5 @@ def _chat_finish_chunk(finish_reason: 'str') -> 'typing.Dict[str, object]':
|
|
|
477
524
|
}
|
|
478
525
|
|
|
479
526
|
|
|
480
|
-
def _dump_json(raw_value:
|
|
527
|
+
def _dump_json(raw_value: "object") -> "str":
|
|
481
528
|
return json.dumps(raw_value, ensure_ascii=False, separators=(",", ":"))
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
"""Provider-specific post-process hooks for canonical outgoing chat requests.
|
|
3
2
|
|
|
4
3
|
Each downstream chat-completions provider may have its own payload quirks:
|
|
@@ -8,9 +7,10 @@ building one canonical `outcomming_request`, while `server.py` selects the
|
|
|
8
7
|
appropriate hook from `CompatServerConfig.model_provider`.
|
|
9
8
|
"""
|
|
10
9
|
|
|
10
|
+
import typing
|
|
11
11
|
from copy import deepcopy
|
|
12
12
|
from typing import Callable, Optional
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
from typing_extensions import TypedDict
|
|
15
15
|
|
|
16
16
|
ChatMessage = typing.Dict[str, object]
|
|
@@ -25,26 +25,29 @@ class OutgoingRequest(TypedDict):
|
|
|
25
25
|
not rely on TypedDict inheritance.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
model:
|
|
29
|
-
messages:
|
|
30
|
-
stream:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
model: "str"
|
|
29
|
+
messages: "typing.List[ChatMessage]"
|
|
30
|
+
stream: "bool"
|
|
31
|
+
chat_template_kwargs: "Optional[typing.Dict[str, object]]"
|
|
32
|
+
max_tokens: "Optional[int]"
|
|
33
|
+
tools: "Optional[typing.List[typing.Dict[str, object]]]"
|
|
34
|
+
tool_choice: "Optional[object]"
|
|
35
|
+
parallel_tool_calls: "Optional[bool]"
|
|
36
|
+
return_token_ids: "Optional[bool]"
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
PayloadPostProcessor = Callable[[OutgoingRequest], OutgoingRequest]
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
def _identity(outcomming_request:
|
|
42
|
+
def _identity(outcomming_request: "OutgoingRequest") -> "OutgoingRequest":
|
|
42
43
|
"""Keep the canonical request unchanged."""
|
|
43
44
|
|
|
44
45
|
return outcomming_request
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
def _drop_developer_messages(
|
|
48
|
+
def _drop_developer_messages(
|
|
49
|
+
outcomming_request: "OutgoingRequest",
|
|
50
|
+
) -> "OutgoingRequest":
|
|
48
51
|
"""Remove all developer-role messages for providers that reject them."""
|
|
49
52
|
|
|
50
53
|
outcomming_request["messages"] = [
|
|
@@ -54,17 +57,20 @@ def _drop_developer_messages(outcomming_request: 'OutgoingRequest') -> 'Outgoing
|
|
|
54
57
|
]
|
|
55
58
|
return outcomming_request
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
|
|
61
|
+
def _replace_developer_messages(
|
|
62
|
+
outcomming_request: "OutgoingRequest",
|
|
63
|
+
) -> "OutgoingRequest":
|
|
58
64
|
"""Replace all developer-role messages to system-role messages"""
|
|
59
65
|
|
|
60
|
-
for message in outcomming_request[
|
|
66
|
+
for message in outcomming_request["messages"]:
|
|
61
67
|
if message.get("role") == "developer":
|
|
62
|
-
message[
|
|
68
|
+
message["role"] = "system"
|
|
63
69
|
|
|
64
70
|
return outcomming_request
|
|
65
71
|
|
|
66
72
|
|
|
67
|
-
PAYLOAD_POST_PROCESSORS:
|
|
73
|
+
PAYLOAD_POST_PROCESSORS: "typing.Dict[str, PayloadPostProcessor]" = {
|
|
68
74
|
"stepfun": _replace_developer_messages,
|
|
69
75
|
"vllm": _identity,
|
|
70
76
|
}
|
|
@@ -72,9 +78,9 @@ PAYLOAD_POST_PROCESSORS: 'typing.Dict[str, PayloadPostProcessor]' = {
|
|
|
72
78
|
|
|
73
79
|
|
|
74
80
|
def post_process_outcomming_request(
|
|
75
|
-
outcomming_request:
|
|
76
|
-
model_provider:
|
|
77
|
-
) ->
|
|
81
|
+
outcomming_request: "OutgoingRequest",
|
|
82
|
+
model_provider: "typing.Union[str, None]",
|
|
83
|
+
) -> "OutgoingRequest":
|
|
78
84
|
"""Apply the provider-specific payload hook to one outgoing request.
|
|
79
85
|
|
|
80
86
|
This is the single wrapper around `PAYLOAD_POST_PROCESSORS`: it normalizes
|
responses_server/server.py
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
+
import typing
|
|
1
2
|
|
|
2
3
|
from .config import CompatServerConfig
|
|
3
4
|
from .payload_processors import post_process_outcomming_request
|
|
4
5
|
from .session_store import SessionStore
|
|
5
6
|
from .stream_router import StreamRouter
|
|
6
7
|
from .trajectory_dump import TrajectoryDumpWriter
|
|
7
|
-
import typing
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class ResponseServer:
|
|
11
11
|
def __init__(
|
|
12
12
|
self,
|
|
13
|
-
config:
|
|
14
|
-
session_store:
|
|
15
|
-
stream_router:
|
|
16
|
-
) ->
|
|
13
|
+
config: "CompatServerConfig",
|
|
14
|
+
session_store: "typing.Union[SessionStore, None]" = None,
|
|
15
|
+
stream_router: "typing.Union[StreamRouter, None]" = None,
|
|
16
|
+
) -> "None":
|
|
17
17
|
self._config = config
|
|
18
18
|
self._session_store = session_store or SessionStore()
|
|
19
19
|
self._stream_router = stream_router or StreamRouter(config)
|
|
20
20
|
self._trajectory_dump = TrajectoryDumpWriter.from_env()
|
|
21
21
|
|
|
22
22
|
@property
|
|
23
|
-
def config(self) ->
|
|
23
|
+
def config(self) -> "CompatServerConfig":
|
|
24
24
|
return self._config
|
|
25
25
|
|
|
26
26
|
@property
|
|
27
|
-
def session_store(self) ->
|
|
27
|
+
def session_store(self) -> "SessionStore":
|
|
28
28
|
return self._session_store
|
|
29
29
|
|
|
30
30
|
@property
|
|
31
|
-
def stream_router(self) ->
|
|
31
|
+
def stream_router(self) -> "StreamRouter":
|
|
32
32
|
return self._stream_router
|
|
33
33
|
|
|
34
|
-
def list_models(self) ->
|
|
34
|
+
def list_models(self) -> "typing.Dict[str, object]":
|
|
35
35
|
return self._stream_router.list_models()
|
|
36
36
|
|
|
37
37
|
def start_response_stream(
|
|
38
38
|
self,
|
|
39
|
-
request_body:
|
|
40
|
-
request_headers:
|
|
39
|
+
request_body: "typing.Dict[str, object]",
|
|
40
|
+
request_headers: "typing.Dict[str, str]",
|
|
41
41
|
):
|
|
42
42
|
outcomming_request = self._stream_router.build_outcomming_request(request_body)
|
|
43
43
|
if self._trajectory_dump is not None:
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
from dataclasses import dataclass
|
|
3
1
|
import threading
|
|
4
2
|
import time
|
|
5
3
|
import typing
|
|
4
|
+
from dataclasses import dataclass
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
@dataclass(
|
|
7
|
+
@dataclass(
|
|
8
|
+
frozen=True,
|
|
9
|
+
)
|
|
9
10
|
class StoredResponse:
|
|
10
|
-
response_id:
|
|
11
|
-
session_id:
|
|
12
|
-
model:
|
|
13
|
-
created_at:
|
|
11
|
+
response_id: "str"
|
|
12
|
+
session_id: "typing.Union[str, None]"
|
|
13
|
+
model: "str"
|
|
14
|
+
created_at: "float"
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class SessionStore:
|
|
17
|
-
def __init__(self) ->
|
|
18
|
+
def __init__(self) -> "None":
|
|
18
19
|
self._lock = threading.Lock()
|
|
19
20
|
self._next_response_number = 1
|
|
20
|
-
self._responses:
|
|
21
|
+
self._responses: "typing.Dict[str, StoredResponse]" = {}
|
|
21
22
|
|
|
22
|
-
def create_response(
|
|
23
|
+
def create_response(
|
|
24
|
+
self, session_id: "typing.Union[str, None]", model: "str"
|
|
25
|
+
) -> "StoredResponse":
|
|
23
26
|
with self._lock:
|
|
24
27
|
response_id = f"resp_{self._next_response_number:08d}"
|
|
25
28
|
self._next_response_number += 1
|
|
@@ -32,6 +35,6 @@ class SessionStore:
|
|
|
32
35
|
self._responses[response_id] = stored
|
|
33
36
|
return stored
|
|
34
37
|
|
|
35
|
-
def get_response(self, response_id:
|
|
38
|
+
def get_response(self, response_id: "str") -> "typing.Union[StoredResponse, None]":
|
|
36
39
|
with self._lock:
|
|
37
40
|
return self._responses.get(response_id)
|