python-codex 0.1.3__py3-none-any.whl → 0.1.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.
- pycodex/agent.py +51 -11
- pycodex/cli.py +109 -3
- pycodex/context.py +23 -0
- pycodex/model.py +362 -23
- pycodex/prompts/models.json +30 -0
- pycodex/tools/apply_patch_tool.py +2 -2
- pycodex/utils/__init__.py +4 -0
- pycodex/utils/compactor.py +189 -0
- pycodex/utils/session_persist.py +483 -0
- pycodex/utils/visualize.py +120 -6
- {python_codex-0.1.3.dist-info → python_codex-0.1.5.dist-info}/METADATA +18 -3
- {python_codex-0.1.3.dist-info → python_codex-0.1.5.dist-info}/RECORD +18 -16
- responses_server/app.py +4 -1
- responses_server/payload_processors.py +10 -1
- responses_server/stream_router.py +25 -6
- {python_codex-0.1.3.dist-info → python_codex-0.1.5.dist-info}/WHEEL +0 -0
- {python_codex-0.1.3.dist-info → python_codex-0.1.5.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.3.dist-info → python_codex-0.1.5.dist-info}/licenses/LICENSE +0 -0
pycodex/utils/visualize.py
CHANGED
|
@@ -23,6 +23,8 @@ ANSI_YELLOW = "\x1b[33m"
|
|
|
23
23
|
ANSI_MAGENTA = "\x1b[35m"
|
|
24
24
|
ANSI_RED = "\x1b[31m"
|
|
25
25
|
SPINNER_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
26
|
+
PROMPT_CONTEXT_BASELINE_TOKENS = 12_000
|
|
27
|
+
DEFAULT_MAIN_PROMPT = "pycodex> "
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
def shorten_title(text: 'str', limit: 'int' = 48) -> 'str':
|
|
@@ -81,7 +83,24 @@ def format_cli_plan_messages(
|
|
|
81
83
|
|
|
82
84
|
def build_cli_spinner_frame(index: 'int', label: 'str') -> 'str':
|
|
83
85
|
suffix = f" {label}" if label else ""
|
|
84
|
-
return f"
|
|
86
|
+
return f"{SPINNER_FRAMES[index % len(SPINNER_FRAMES)]}{suffix}"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def percent_of_context_window_remaining(
|
|
90
|
+
total_tokens: 'int',
|
|
91
|
+
context_window_tokens: 'int',
|
|
92
|
+
) -> 'int':
|
|
93
|
+
if context_window_tokens <= PROMPT_CONTEXT_BASELINE_TOKENS:
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
effective_window = context_window_tokens - PROMPT_CONTEXT_BASELINE_TOKENS
|
|
97
|
+
used = max(total_tokens - PROMPT_CONTEXT_BASELINE_TOKENS, 0)
|
|
98
|
+
remaining = max(effective_window - used, 0)
|
|
99
|
+
return int(
|
|
100
|
+
round(
|
|
101
|
+
max(0.0, min(100.0, (remaining / effective_window) * 100.0))
|
|
102
|
+
)
|
|
103
|
+
)
|
|
85
104
|
|
|
86
105
|
|
|
87
106
|
class Spinner:
|
|
@@ -541,7 +560,7 @@ class CliSessionView:
|
|
|
541
560
|
source has closed and the caller should end the session loop.
|
|
542
561
|
- `write_line(text)`, `finish_stream()`, `show_error(text)`: imperative output
|
|
543
562
|
helpers for CLI-side messages that do not come from `AgentEvent`.
|
|
544
|
-
- `show_history()`, `show_title()`, `show_steer_queued(...)`,
|
|
563
|
+
- `show_history()`, `show_title()`, `load_session_history(...)`, `show_steer_queued(...)`,
|
|
545
564
|
`schedule_steer_inserted(...)`: small session UI helpers used by the
|
|
546
565
|
interactive command loop.
|
|
547
566
|
- `close()`: release prompt/spinner resources at shutdown.
|
|
@@ -562,7 +581,10 @@ class CliSessionView:
|
|
|
562
581
|
normal terminal stream so the reply is not lost.
|
|
563
582
|
"""
|
|
564
583
|
|
|
565
|
-
def __init__(
|
|
584
|
+
def __init__(
|
|
585
|
+
self,
|
|
586
|
+
context_window_tokens: 'typing.Union[int, None]' = None,
|
|
587
|
+
) -> 'None':
|
|
566
588
|
import sys
|
|
567
589
|
|
|
568
590
|
self._line_output = print
|
|
@@ -578,6 +600,10 @@ class CliSessionView:
|
|
|
578
600
|
self._prompt_stream_buffer = ""
|
|
579
601
|
self._streaming_in_prompt = False
|
|
580
602
|
self._input_active = False
|
|
603
|
+
self._context_window_tokens = context_window_tokens
|
|
604
|
+
self._context_remaining_percent: 'typing.Union[int, None]' = (
|
|
605
|
+
100 if context_window_tokens is not None else None
|
|
606
|
+
)
|
|
581
607
|
self._color_enabled = cli_color_enabled() and sys.stdout.isatty()
|
|
582
608
|
self._agent_names: 'typing.Dict[str, str]' = {}
|
|
583
609
|
self._prompt_session: 'typing.Union[PromptSession, None]' = None
|
|
@@ -627,6 +653,8 @@ class CliSessionView:
|
|
|
627
653
|
self._color_enabled,
|
|
628
654
|
)
|
|
629
655
|
)
|
|
656
|
+
if user_text:
|
|
657
|
+
self._print_user_turn(user_text)
|
|
630
658
|
self._spinner.start_turn("thinking")
|
|
631
659
|
if self._input_active:
|
|
632
660
|
self._spinner.pause()
|
|
@@ -640,6 +668,27 @@ class CliSessionView:
|
|
|
640
668
|
self._spinner.set_label("waiting model")
|
|
641
669
|
return
|
|
642
670
|
|
|
671
|
+
if event.kind == "token_count":
|
|
672
|
+
self._update_context_window(event.payload.get("usage"))
|
|
673
|
+
return
|
|
674
|
+
|
|
675
|
+
if event.kind == "stream_error":
|
|
676
|
+
self._finish_stream()
|
|
677
|
+
message = str(event.payload.get("message", "")).strip() or "Reconnecting..."
|
|
678
|
+
self._print_line(
|
|
679
|
+
colorize_cli_message(
|
|
680
|
+
f"[status] {message}",
|
|
681
|
+
"status",
|
|
682
|
+
self._color_enabled,
|
|
683
|
+
)
|
|
684
|
+
)
|
|
685
|
+
if self._input_active:
|
|
686
|
+
self._spinner.pause()
|
|
687
|
+
else:
|
|
688
|
+
self._spinner.resume()
|
|
689
|
+
self._spinner.set_label("reconnecting")
|
|
690
|
+
return
|
|
691
|
+
|
|
643
692
|
if event.kind == "assistant_delta":
|
|
644
693
|
delta = str(event.payload.get("delta", ""))
|
|
645
694
|
if not delta:
|
|
@@ -676,16 +725,26 @@ class CliSessionView:
|
|
|
676
725
|
self._spinner.pause()
|
|
677
726
|
else:
|
|
678
727
|
self._spinner.resume()
|
|
679
|
-
self._spinner.set_label("running tools")
|
|
728
|
+
self._spinner.set_label("running provider tools")
|
|
680
729
|
return
|
|
681
730
|
|
|
682
731
|
if event.kind == "tool_started":
|
|
683
732
|
self._finish_stream()
|
|
733
|
+
tool_name = str(event.payload.get("tool_name", "")).strip()
|
|
734
|
+
call = event.payload.get("call")
|
|
735
|
+
args = None
|
|
736
|
+
if isinstance(call, ToolCall):
|
|
737
|
+
args = call.arguments
|
|
684
738
|
if self._input_active:
|
|
685
739
|
self._spinner.pause()
|
|
686
740
|
else:
|
|
687
741
|
self._spinner.resume()
|
|
688
|
-
|
|
742
|
+
if tool_name and args is not None:
|
|
743
|
+
self._spinner.set_label(shorten_title(f"running {tool_name}({args})", limit=72))
|
|
744
|
+
elif tool_name:
|
|
745
|
+
self._spinner.set_label(f"running {tool_name}")
|
|
746
|
+
else:
|
|
747
|
+
self._spinner.set_label("running provider tools")
|
|
689
748
|
return
|
|
690
749
|
|
|
691
750
|
if event.kind == "tool_completed":
|
|
@@ -753,6 +812,19 @@ class CliSessionView:
|
|
|
753
812
|
self._finish_stream()
|
|
754
813
|
self._print_line(f"Session: {self._title or 'untitled'}")
|
|
755
814
|
|
|
815
|
+
def load_session_history(
|
|
816
|
+
self,
|
|
817
|
+
title: 'typing.Union[str, None]',
|
|
818
|
+
history: 'typing.Tuple[typing.Tuple[str, str], ...]',
|
|
819
|
+
) -> 'None':
|
|
820
|
+
self._spinner.finish_turn()
|
|
821
|
+
self._finish_stream()
|
|
822
|
+
self._title = title or None
|
|
823
|
+
self._history = list(history)
|
|
824
|
+
self._pending_user_prompts.clear()
|
|
825
|
+
self._queued_steer_prompts.clear()
|
|
826
|
+
self._inserted_steer_prompts.clear()
|
|
827
|
+
|
|
756
828
|
def pause_spinner(self) -> 'None':
|
|
757
829
|
self._spinner.pause()
|
|
758
830
|
|
|
@@ -808,6 +880,7 @@ class CliSessionView:
|
|
|
808
880
|
self.set_input_active(False, resume_spinner=False)
|
|
809
881
|
|
|
810
882
|
def build_input_prompt(self, prompt: 'str') -> 'str':
|
|
883
|
+
prompt = self._format_main_prompt(prompt)
|
|
811
884
|
if not self._input_active:
|
|
812
885
|
return prompt
|
|
813
886
|
if self._streaming and self._streaming_in_prompt:
|
|
@@ -819,6 +892,29 @@ class CliSessionView:
|
|
|
819
892
|
return prompt
|
|
820
893
|
return f"{prompt_line}\n{prompt}"
|
|
821
894
|
|
|
895
|
+
def _update_context_window(self, usage: 'object') -> 'None':
|
|
896
|
+
if self._context_window_tokens is None:
|
|
897
|
+
return
|
|
898
|
+
if not isinstance(usage, dict):
|
|
899
|
+
self._context_remaining_percent = None
|
|
900
|
+
return
|
|
901
|
+
try:
|
|
902
|
+
total_tokens = int(usage["total_tokens"])
|
|
903
|
+
except (KeyError, TypeError, ValueError):
|
|
904
|
+
self._context_remaining_percent = None
|
|
905
|
+
return
|
|
906
|
+
self._context_remaining_percent = percent_of_context_window_remaining(
|
|
907
|
+
total_tokens,
|
|
908
|
+
self._context_window_tokens,
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
def _format_main_prompt(self, prompt: 'str') -> 'str':
|
|
912
|
+
if prompt != DEFAULT_MAIN_PROMPT:
|
|
913
|
+
return prompt
|
|
914
|
+
if self._context_remaining_percent is None:
|
|
915
|
+
return prompt
|
|
916
|
+
return f"pyco({self._context_remaining_percent}%)> "
|
|
917
|
+
|
|
822
918
|
def show_steer_queued(self, turn_id: 'str', prompt: 'str') -> 'None':
|
|
823
919
|
preview = shorten_title(prompt, limit=72)
|
|
824
920
|
self._queued_steer_prompts.setdefault(turn_id, []).append(preview)
|
|
@@ -843,6 +939,15 @@ class CliSessionView:
|
|
|
843
939
|
if self._stdout_proxy is not None:
|
|
844
940
|
self._stdout_proxy.close()
|
|
845
941
|
|
|
942
|
+
def set_context_window_tokens(
|
|
943
|
+
self,
|
|
944
|
+
context_window_tokens: 'typing.Union[int, None]',
|
|
945
|
+
) -> 'None':
|
|
946
|
+
self._context_window_tokens = context_window_tokens
|
|
947
|
+
self._context_remaining_percent = (
|
|
948
|
+
100 if context_window_tokens is not None else None
|
|
949
|
+
)
|
|
950
|
+
|
|
846
951
|
def finish_stream(self) -> 'None':
|
|
847
952
|
self._finish_stream()
|
|
848
953
|
|
|
@@ -852,9 +957,15 @@ class CliSessionView:
|
|
|
852
957
|
def show_error(self, text: 'str') -> 'None':
|
|
853
958
|
self._spinner.finish_turn()
|
|
854
959
|
self._finish_stream()
|
|
960
|
+
lines = str(text).splitlines() or [""]
|
|
961
|
+
formatted = [f"Error: {lines[0]}"]
|
|
962
|
+
formatted.extend(
|
|
963
|
+
f" {line}" if line else ""
|
|
964
|
+
for line in lines[1:]
|
|
965
|
+
)
|
|
855
966
|
self._print_line(
|
|
856
967
|
colorize_cli_message(
|
|
857
|
-
|
|
968
|
+
"\n".join(formatted),
|
|
858
969
|
"error",
|
|
859
970
|
self._color_enabled,
|
|
860
971
|
)
|
|
@@ -921,6 +1032,9 @@ class CliSessionView:
|
|
|
921
1032
|
self._spinner.clear()
|
|
922
1033
|
self._line_output(text)
|
|
923
1034
|
|
|
1035
|
+
def _print_user_turn(self, text: 'str') -> 'None':
|
|
1036
|
+
self._print_line(f"user> {text}")
|
|
1037
|
+
|
|
924
1038
|
def _remember_agent_name(self, tool_name: 'str', summary: 'str') -> 'None':
|
|
925
1039
|
if tool_name != "spawn_agent":
|
|
926
1040
|
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-codex
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: A minimal Python extraction of Codex's main agent loop
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.6.2
|
|
@@ -72,7 +72,7 @@ Intentionally not included yet:
|
|
|
72
72
|
|
|
73
73
|
- TUI / streaming incremental rendering
|
|
74
74
|
- MCP / connectors / sandbox / approvals
|
|
75
|
-
- memory / compact /
|
|
75
|
+
- memory / compact / review mode
|
|
76
76
|
- a full production OpenAI adapter surface
|
|
77
77
|
|
|
78
78
|
All of those can be layered on later. For now, the project is focused on
|
|
@@ -174,9 +174,24 @@ Current behavior:
|
|
|
174
174
|
- interactive mode shows a compact event stream for user-visible phases such as
|
|
175
175
|
tool execution and model follow-up after tool results
|
|
176
176
|
- assistant text is printed from streaming deltas directly
|
|
177
|
-
- interactive mode supports `/history`, `/title`, and `/
|
|
177
|
+
- interactive mode supports `/history`, `/title`, `/model`, `/resume`, and `/compact`
|
|
178
178
|
- `/model <name>` switches the model used by later turns in the current
|
|
179
179
|
interactive session; `/model` shows the current model and available choices
|
|
180
|
+
- `/resume` with no argument lists the currently resumable sessions by their
|
|
181
|
+
first user-message preview; `/resume 1` resumes the first listed session
|
|
182
|
+
- `/resume <number>` replaces the in-memory history with the selected recorded
|
|
183
|
+
Codex rollout from `CODEX_HOME/sessions`
|
|
184
|
+
- `/compact` synthesizes a local handoff summary, replaces the in-memory
|
|
185
|
+
conversation history with the compacted view, and appends a compacted-history
|
|
186
|
+
entry to the rollout so later `/resume` sees the same state
|
|
187
|
+
- new sessions are now recorded under `CODEX_HOME/sessions/.../rollout-*.jsonl`
|
|
188
|
+
with a stable session/thread id and per-item append+flush semantics so
|
|
189
|
+
`/resume` reads back the same rollout format
|
|
190
|
+
- if `TURN_HOOK.md` exists in the workspace root and is non-empty, each
|
|
191
|
+
completed turn also forks the just-finished history into a temporary,
|
|
192
|
+
non-persisted follow-up session and submits the file contents as the next
|
|
193
|
+
user instruction; this is intended for side-effect follow-ups such as
|
|
194
|
+
Feishu notifications
|
|
180
195
|
- steer is enabled by default in interactive mode: normal input goes into the
|
|
181
196
|
runtime steer path, the current request stops at the next safe boundary, and
|
|
182
197
|
later steer text is appended to the next model request's `input` in order;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
pycodex/__init__.py,sha256=jCnC_Bgotlxa4GwO3Re2sChKGY49TRM-uVZEQ9uBpfw,3106
|
|
2
|
-
pycodex/agent.py,sha256=
|
|
3
|
-
pycodex/cli.py,sha256=
|
|
2
|
+
pycodex/agent.py,sha256=s0FrF_XG2pHKryooS461Jr_acmQ_TKTp2JLGQNiny6w,11888
|
|
3
|
+
pycodex/cli.py,sha256=ntgC0LWlSOhuYAUOBgSEeVIjBTKS91klyvkTO9QtFoE,29559
|
|
4
4
|
pycodex/collaboration.py,sha256=yQ6pBD-R3ZWR4_FAYQFoS7KF0m4LLD42otXIbPqw2ys,641
|
|
5
5
|
pycodex/compat.py,sha256=IO0X7AgcYhlHnYnpvBZ6leCh_UjoQzg5HLT5wYBNNIw,3155
|
|
6
|
-
pycodex/context.py,sha256=
|
|
6
|
+
pycodex/context.py,sha256=R5tuMcNrX1F-Lh9ymsSbnfRbKLJ19TWrtQoZ3tWlHvM,24982
|
|
7
7
|
pycodex/doctor.py,sha256=De3M4hRBJq8ZeqsUJgHz0vitqrH18YugrEnz7oHhTdQ,10572
|
|
8
|
-
pycodex/model.py,sha256=
|
|
8
|
+
pycodex/model.py,sha256=Mk9LZKmFcXG71I18-gs4dUWNn0GIM1rbMhFfKDut_3w,32790
|
|
9
9
|
pycodex/portable.py,sha256=kZ5XVOMZq0l6xXsx3FY9C3DfB4Jra5Hw38qTMH0TEwg,15597
|
|
10
10
|
pycodex/portable_server.py,sha256=6I3pQkWj3e_SFlDXY2mGdCPns1w_3PSxByBV9wv5epI,7331
|
|
11
11
|
pycodex/protocol.py,sha256=LYDzJefu1tugqQzee4NuZzxhGAv3hXrNcnlw04CudAY,11106
|
|
@@ -15,7 +15,7 @@ pycodex/prompts/collaboration_default.md,sha256=MBTmPuMubeWfZgIeFVj49wwnwD4n_o3f
|
|
|
15
15
|
pycodex/prompts/collaboration_plan.md,sha256=IzjQAA5oHJz-3FmJdOjsJ4LHq6LW1tlEYMoy09n0HKk,8777
|
|
16
16
|
pycodex/prompts/default_base_instructions.md,sha256=D65mcj6bo4CDvVom-D9cbJRJVNquo0NghKt164_fRsg,20923
|
|
17
17
|
pycodex/prompts/exec_tools.json,sha256=2wYLsjL6VGzMnhFNCxE9IA_kxsxUspN68lr7JOlZq54,23369
|
|
18
|
-
pycodex/prompts/models.json,sha256=
|
|
18
|
+
pycodex/prompts/models.json,sha256=u4u2bylNZnMw_qKtvn_iZMUwS4wEq1JBMwHcetC3Spo,285814
|
|
19
19
|
pycodex/prompts/subagent_tools.json,sha256=2ZOXyAiAaai2aazIlXdjjXb7cra5gZ2WYYbPltPaiYg,6199
|
|
20
20
|
pycodex/prompts/permissions/approval_policy/never.md,sha256=QceTG6wjkaJARjYr0HYV1aPnPcpGcrkRUW-smWRr6MQ,120
|
|
21
21
|
pycodex/prompts/permissions/approval_policy/on_failure.md,sha256=dfJjpXkpO6_ANdCKxbVJ8o4vyLxevrJWfKsGHTqtbkc,289
|
|
@@ -27,7 +27,7 @@ pycodex/prompts/permissions/sandbox_mode/read_only.md,sha256=2rAPEXsBYCcuttI5j3e
|
|
|
27
27
|
pycodex/prompts/permissions/sandbox_mode/workspace_write.md,sha256=lVN-LwrBbHqlv5yVjcd_mU8tzZW8jfKpTatJKIZu9HI,277
|
|
28
28
|
pycodex/tools/__init__.py,sha256=aSLXrr_31KGQgDfRow5zVIc-2-KdXlHaCE6qUnE4HWI,1772
|
|
29
29
|
pycodex/tools/agent_tool_schemas.py,sha256=r7pBICcx8fb0Rg6IzIg8-u3um2z11TogQ4yCzuiO-4o,2033
|
|
30
|
-
pycodex/tools/apply_patch_tool.py,sha256=
|
|
30
|
+
pycodex/tools/apply_patch_tool.py,sha256=aFob-gzaCXlzPdCIvRXVKm1NrQqqhqe8CVkFVAhqiTc,13955
|
|
31
31
|
pycodex/tools/base_tool.py,sha256=FLtbb6KPUKyhHRMrR6_anYi_GmpJFCaX1ch5aRnjQjo,5527
|
|
32
32
|
pycodex/tools/close_agent_tool.py,sha256=nY3l_UOX6NyTgUqdXag3yRpdyQScV0g0Vv4HE3ElLwg,1597
|
|
33
33
|
pycodex/tools/code_mode_manager.py,sha256=Wow42H_9IomUKUjjjU8rrAFAklhE-UlgxgrbgHRU_4M,19031
|
|
@@ -51,24 +51,26 @@ pycodex/tools/wait_agent_tool.py,sha256=0xjr5M2S0SNZaSr1o4U0RXI6dTJfMVpBB8Uclm_4
|
|
|
51
51
|
pycodex/tools/wait_tool.py,sha256=EJcW2Ev9jUD9eZ7cFDNOLDzlywS2BD3ll6pArXyxfrI,2331
|
|
52
52
|
pycodex/tools/web_search_tool.py,sha256=_7r2ltWhnBM0ZCgweA5a0GbEi0qSFAHOyi1RHrl6tfQ,957
|
|
53
53
|
pycodex/tools/write_stdin_tool.py,sha256=nCuProkbeewfQ_yS8CgBajo--K3EmkXzJYh1D2QtAM4,2549
|
|
54
|
-
pycodex/utils/__init__.py,sha256=
|
|
54
|
+
pycodex/utils/__init__.py,sha256=XawMC7CRm9bt3wPWyithj5x7YQvYrggn2_DcGGSTnCY,1162
|
|
55
|
+
pycodex/utils/compactor.py,sha256=ZCzGc02xHmXq1rIjnG2gATKcFtt6r-OGsCIK0ypjnyI,6467
|
|
55
56
|
pycodex/utils/dotenv.py,sha256=EDBXdn93ewmq9zhJki5_LsJJXe0wMIQJ6VfCE1r7voQ,1818
|
|
56
57
|
pycodex/utils/get_env.py,sha256=jR8G0Xco57jX-71E1oHIcl3-Kz9Ltc0kzxj04DKzt80,7316
|
|
57
58
|
pycodex/utils/random_ids.py,sha256=zBphjVGc7OXk9ZNExAbxRi_bk7ipyLG491qTv7hi8jM,380
|
|
58
|
-
pycodex/utils/
|
|
59
|
+
pycodex/utils/session_persist.py,sha256=dUvo3Z1QBB4HJT1tLerDlLD3ZB25umB6FP6JORg9V40,16414
|
|
60
|
+
pycodex/utils/visualize.py,sha256=wjuW7l8uE7sckRtUJNAQ_irHKIfDWdfnFINHYtvQ5bw,40043
|
|
59
61
|
responses_server/__init__.py,sha256=3yPv_zeGT7P11tTnmj5kXktISLNsNW-02MUnnbiZcb0,394
|
|
60
62
|
responses_server/__main__.py,sha256=9SRp-Yw7ShGxc6DhSIXcDLKgGEdAVm3oBZ59rBOPjT0,62
|
|
61
|
-
responses_server/app.py,sha256=
|
|
63
|
+
responses_server/app.py,sha256=9gKgzD8LsOdkg2Te2KOwodH-Z0BD_RuNEVh9MBclx7Q,7429
|
|
62
64
|
responses_server/config.py,sha256=wEcZbXZclTYz4fI_oy_sSMglWPeEITWlFeglQrrr6HE,2236
|
|
63
|
-
responses_server/payload_processors.py,sha256=
|
|
65
|
+
responses_server/payload_processors.py,sha256=AcOipqVQyo4wKw_pb3ABlarwIK1VjcnQTlgPehRVGO8,3412
|
|
64
66
|
responses_server/server.py,sha256=isyzN-p-Ir8LLycN_dQfcanvie2ZqqSu52mOPz_wYD4,2095
|
|
65
67
|
responses_server/session_store.py,sha256=ZD3cH2aEOkWaQsu5qTzcal2mThTSFQPAhAhPUN9srgI,1115
|
|
66
|
-
responses_server/stream_router.py,sha256=
|
|
68
|
+
responses_server/stream_router.py,sha256=zWC4yyZ3I8E-Zgco844tIhRMWOwIkjOV0s-G-a9-B8k,30861
|
|
67
69
|
responses_server/tools/__init__.py,sha256=ivsBSEy0SBUhY-Uea5v1XMLXShkwHdCVl0id-1FwdZg,150
|
|
68
70
|
responses_server/tools/custom_adapter.py,sha256=LxO7ldydvR-GWachDz8GKC0Q8KGGFoFPbZxM0QvxuZ0,8350
|
|
69
71
|
responses_server/tools/web_search.py,sha256=pm4ZUiHUfxc0bGY1kEvt-BCzDrZIyP24xzPUcga2ul0,8908
|
|
70
|
-
python_codex-0.1.
|
|
71
|
-
python_codex-0.1.
|
|
72
|
-
python_codex-0.1.
|
|
73
|
-
python_codex-0.1.
|
|
74
|
-
python_codex-0.1.
|
|
72
|
+
python_codex-0.1.5.dist-info/METADATA,sha256=rTfHXfLqg86xKhx6VNFcrHs_qBdBJuVSn5Mfe_wXODQ,15451
|
|
73
|
+
python_codex-0.1.5.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
|
|
74
|
+
python_codex-0.1.5.dist-info/entry_points.txt,sha256=sNUVakoVuTrzJH505ZgRTQxmtRRPUHV_EH0i6EbYTyM,45
|
|
75
|
+
python_codex-0.1.5.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
|
|
76
|
+
python_codex-0.1.5.dist-info/RECORD,,
|
responses_server/app.py
CHANGED
|
@@ -34,13 +34,16 @@ def _stream_events(response_server: 'ResponseServer', request_body: 'typing.Dict
|
|
|
34
34
|
for event_name, payload in event_iter:
|
|
35
35
|
yield _format_sse_event(event_name, payload)
|
|
36
36
|
except OutcommingChatError as exc:
|
|
37
|
+
|
|
38
|
+
import traceback
|
|
39
|
+
exc_info = traceback.format_exception(type(exc), exc, exc.__traceback__)
|
|
37
40
|
yield _format_sse_event(
|
|
38
41
|
"response.failed",
|
|
39
42
|
{
|
|
40
43
|
"type": "response.failed",
|
|
41
44
|
"response": {
|
|
42
45
|
"error": {
|
|
43
|
-
"message":
|
|
46
|
+
"message": '\n'.join(exc_info),
|
|
44
47
|
}
|
|
45
48
|
},
|
|
46
49
|
},
|
|
@@ -52,9 +52,18 @@ def _drop_developer_messages(outcomming_request: 'OutgoingRequest') -> 'Outgoing
|
|
|
52
52
|
]
|
|
53
53
|
return outcomming_request
|
|
54
54
|
|
|
55
|
+
def _replace_developer_messages(outcomming_request: 'OutgoingRequest') -> 'OutgoingRequest':
|
|
56
|
+
"""Replace all developer-role messages to system-role messages"""
|
|
57
|
+
|
|
58
|
+
for message in outcomming_request['messages']:
|
|
59
|
+
if message.get("role") == "developer":
|
|
60
|
+
message['role'] = "system"
|
|
61
|
+
|
|
62
|
+
return outcomming_request
|
|
63
|
+
|
|
55
64
|
|
|
56
65
|
PAYLOAD_POST_PROCESSORS: 'typing.Dict[str, PayloadPostProcessor]' = {
|
|
57
|
-
"stepfun":
|
|
66
|
+
"stepfun": _replace_developer_messages,
|
|
58
67
|
"vllm": _identity,
|
|
59
68
|
}
|
|
60
69
|
"""Mapping from normalized `model_provider` name to payload rewrite hook."""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import json
|
|
3
|
+
import http.client
|
|
3
4
|
import ssl
|
|
4
5
|
import urllib.error
|
|
5
6
|
import urllib.request
|
|
@@ -161,12 +162,30 @@ class StreamRouter:
|
|
|
161
162
|
context=ssl.create_default_context(),
|
|
162
163
|
timeout=self._config.timeout_seconds,
|
|
163
164
|
) as response:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
try:
|
|
166
|
+
saw_done = False
|
|
167
|
+
for _event_name, data in self._iter_sse_events(response):
|
|
168
|
+
if not data:
|
|
169
|
+
continue
|
|
170
|
+
if data == "[DONE]":
|
|
171
|
+
saw_done = True
|
|
172
|
+
break
|
|
173
|
+
yield json.loads(data)
|
|
174
|
+
if not saw_done:
|
|
175
|
+
raise OutcommingChatError(
|
|
176
|
+
"outcomming chat stream ended before [DONE]"
|
|
177
|
+
)
|
|
178
|
+
except (
|
|
179
|
+
ConnectionError,
|
|
180
|
+
EOFError,
|
|
181
|
+
OSError,
|
|
182
|
+
http.client.HTTPException,
|
|
183
|
+
json.JSONDecodeError,
|
|
184
|
+
) as exc:
|
|
185
|
+
raise OutcommingChatError(
|
|
186
|
+
"outcomming chat stream failed while reading response body: "
|
|
187
|
+
f"{exc}"
|
|
188
|
+
) from exc
|
|
170
189
|
except urllib.error.HTTPError as exc:
|
|
171
190
|
body = exc.read().decode("utf-8", errors="replace")
|
|
172
191
|
raise OutcommingChatError(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|