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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
pycodex/__init__.py,sha256=4BBGUXImsNBdSQjp2KtshkjQK9BMz5gK93DzpWokkWY,3235
|
|
2
|
+
pycodex/agent.py,sha256=zCIG2DV0oQntLXHFj8FVRkgBp5TCC0YJ7sz0lni9J1s,21177
|
|
3
|
+
pycodex/bootstrap.py,sha256=DSVx8u94tHRYbUa_e9zdKPqozOv_CrfctbTKZ4JG064,13765
|
|
4
|
+
pycodex/cli.py,sha256=m5IJhnodNqsfMTIzdwbuaHBl2r-N8x1MxYSpe_hlHiM,12997
|
|
5
|
+
pycodex/compat.py,sha256=yoQQWMiAQ6ytZv3DAMIbp66ImA0kyY4G5eKHrtwmj6c,3490
|
|
6
|
+
pycodex/context.py,sha256=nkq2Y0coZNpWs5IJj7uVVT65fDqh58Z9c07y_Wy0f4Q,26604
|
|
7
|
+
pycodex/doctor.py,sha256=9p624b1zloXbhyMRi1-Of1YtnYeoF4zHxfGFhfujDS4,10620
|
|
8
|
+
pycodex/events.py,sha256=MZljtqra6-Hnt9Emjbn_wgp5cGaODx1w3CbmIKd5htI,29692
|
|
9
|
+
pycodex/feishu_card.py,sha256=Gclo4TI3KTfdT5NDLiv58CRg6hH3sUCRXuSrHgXgYjI,27767
|
|
10
|
+
pycodex/feishu_link.py,sha256=yUbEvO-8g-Bxo8_cF9s6vLZguz3NA6q3qt2A9Q09fsE,14523
|
|
11
|
+
pycodex/model.py,sha256=cKG2STZ_Kc3ThelY6KTTRkBTCrIwcmF74Rhfhj71N08,44175
|
|
12
|
+
pycodex/model_metadata.py,sha256=RMYhvgkm2v1nhCslSiDXPAwrRAx8FPz9QJqv8QTbWlc,1332
|
|
13
|
+
pycodex/portable.py,sha256=rfOpVnBZ01RS0Kyjo_w2FNKHY2t-lUcHVtWhPsbp1-k,16158
|
|
14
|
+
pycodex/portable_server.py,sha256=A4IY3xHtBQLRxdHQ4O5zdaVGjqENaLvypsSkLGZvCZ0,7498
|
|
15
|
+
pycodex/protocol.py,sha256=dRcTVKTaE7Eaq_jiSYdCzBMebIo3gnL7wB1x-NJqY5E,12027
|
|
16
|
+
pycodex/runtime.py,sha256=bRlqrmXqK6kPndAG96QoGALKicbWmfuXgo2jDvbQzdE,23806
|
|
17
|
+
pycodex/runtime_services.py,sha256=4jDCMlXHwgbJZPJkWzVEPCRHPxrD0mpCBbDInVToGG8,15033
|
|
18
|
+
pycodex/prompts/default_base_instructions.md,sha256=D65mcj6bo4CDvVom-D9cbJRJVNquo0NghKt164_fRsg,20923
|
|
19
|
+
pycodex/prompts/models.json,sha256=9UUBlKwpQSrt53WS-D--I3pQVBhRUe9XhDpEHUS6be8,330811
|
|
20
|
+
pycodex/prompts/permissions/approval_policy/never.md,sha256=QceTG6wjkaJARjYr0HYV1aPnPcpGcrkRUW-smWRr6MQ,120
|
|
21
|
+
pycodex/prompts/permissions/approval_policy/on_failure.md,sha256=dfJjpXkpO6_ANdCKxbVJ8o4vyLxevrJWfKsGHTqtbkc,289
|
|
22
|
+
pycodex/prompts/permissions/approval_policy/on_request.md,sha256=hVQalzh0FAdkKzw5u-N4H7-LtC9ijVDlYsh3OKsZKzo,3661
|
|
23
|
+
pycodex/prompts/permissions/approval_policy/on_request_rule_request_permission.md,sha256=mOinishp1k-wlPsaEuIOMn5GoVm_dAIsWIuEMmv2r7o,1725
|
|
24
|
+
pycodex/prompts/permissions/approval_policy/unless_trusted.md,sha256=XHpi1Lfx1iIXFbbQ_ho_kGstA3JN-RLho291HM30UNw,247
|
|
25
|
+
pycodex/prompts/permissions/sandbox_mode/danger_full_access.md,sha256=nZ7YHacBd3cAHKRZc9XClOOOnXJPXPh0WFBueh5C2D0,197
|
|
26
|
+
pycodex/prompts/permissions/sandbox_mode/read_only.md,sha256=2rAPEXsBYCcuttI5j3euS-3uv_v97catIsnhxlSQSIM,173
|
|
27
|
+
pycodex/prompts/permissions/sandbox_mode/workspace_write.md,sha256=lVN-LwrBbHqlv5yVjcd_mU8tzZW8jfKpTatJKIZu9HI,277
|
|
28
|
+
pycodex/tools/__init__.py,sha256=UYwmsX_Ckw_YaYQ6TYhUU6w5pxi2qzZoTrq6BAgNErM,1857
|
|
29
|
+
pycodex/tools/agent_tool_schemas.py,sha256=8cL9ml4So0H6IIeK0ZzjAo9bk3VeprDOSYPpIamOqEU,2048
|
|
30
|
+
pycodex/tools/apply_patch_tool.py,sha256=t8XFCGZ7q-s9C1Ty6qkReBRk-QD3ONvcKXLAz9djKRY,13956
|
|
31
|
+
pycodex/tools/base_tool.py,sha256=kNNzPWijtdl4C_GunyR5G1qvsr7KQtnFJ2RiRHHnAYo,6478
|
|
32
|
+
pycodex/tools/clock_tool.py,sha256=2_RYbBEw4V9_f1L-qZl3hhJ6XNPM_gBDmOlCh1cCAjo,6575
|
|
33
|
+
pycodex/tools/close_agent_tool.py,sha256=zQtvd5iGW6YCPFiec8zc2OdGtgNIxoA4p2ZTuCoJOaI,2031
|
|
34
|
+
pycodex/tools/code_mode_manager.py,sha256=VFVn7S_qUHCOHW952CMeGxTIE-7CPOx2uuXJZgBml8o,19160
|
|
35
|
+
pycodex/tools/exec_command_tool.py,sha256=iqRiKaaX-AjG6LjATAMOakHIP0gpCWCj2o8af_8eAiQ,4464
|
|
36
|
+
pycodex/tools/exec_runtime.js,sha256=DR1uocKailTqNWAcJNFJuQgFFMSUzTpT_uQsRaneg2s,3643
|
|
37
|
+
pycodex/tools/exec_tool.py,sha256=tuC21rlhP8CPSYml4XnSDSRV0UPGkQ6aHj0DLIoOOvM,2274
|
|
38
|
+
pycodex/tools/grep_files_tool.py,sha256=Ysw68VKI9OEvUH5haYx8FWuthkHykc1wskbuzjj8gTk,5293
|
|
39
|
+
pycodex/tools/ipython_tool.py,sha256=SKVYpJEwlYjOQZQWbpqvrA-3K9aJTlIG9KM50p5JYUM,4496
|
|
40
|
+
pycodex/tools/list_dir_tool.py,sha256=_U6ouRcVbGHT5mChVyUsiMgDGrFK7EFMFaspPLAHtH8,5337
|
|
41
|
+
pycodex/tools/read_file_tool.py,sha256=YK__uhIVldBjAQega5S_ZuZU5jgP_kLOwr-YhjAmRo4,9735
|
|
42
|
+
pycodex/tools/request_permissions_tool.py,sha256=n8eg29SgRbx1DUxxLbAztnc5i-0j2N2WnDupspjwWbE,3979
|
|
43
|
+
pycodex/tools/request_user_input_tool.py,sha256=_7siHF2p3KiNDFwsTqKKIUw2kPCXMzLaR12PkZ1wdIQ,4050
|
|
44
|
+
pycodex/tools/resume_agent_tool.py,sha256=D_OcWQbIbsWf-2NrRgDab4hB6BUbay0XidfPInhg88Y,1614
|
|
45
|
+
pycodex/tools/send_input_tool.py,sha256=J91LLz4RpvHmBWti0XZgI9xWipWwD8-1hQIdC8-Jd5E,3762
|
|
46
|
+
pycodex/tools/shell_command_tool.py,sha256=US63I5xiV5zfO1Oox2yHUg7F6XoKzmyxtohfaQIsmrw,4216
|
|
47
|
+
pycodex/tools/shell_tool.py,sha256=lqFH2XosfBg7yQKhpB7i4vLGz9sNQtEZihD0rjsmBCQ,4068
|
|
48
|
+
pycodex/tools/spawn_agent_tool.py,sha256=n_WZi1IX4Waj-GQ-hR1BVlk3zzw_ssvem7zcj3tYI38,6209
|
|
49
|
+
pycodex/tools/unified_exec_manager.py,sha256=sO1H3uoXyZ6DiyF98-eidQ77mS-P5gSWyPqM3L50yTQ,14538
|
|
50
|
+
pycodex/tools/update_plan_tool.py,sha256=8zyANrQgj8u2LBJ2yz_0Cg5jP3S6ibp6eASxUupX9C8,3199
|
|
51
|
+
pycodex/tools/view_image_tool.py,sha256=UZRkBEwuCkqxOw7XBwacdgPZAtbnD6uSHTdm-6amUo4,3902
|
|
52
|
+
pycodex/tools/wait_agent_tool.py,sha256=2OWfb9FCGu5SjbRp33dVEJWG3apMtQgczvQR1V_rm6I,3829
|
|
53
|
+
pycodex/tools/wait_tool.py,sha256=0_OO_DvMsBhG3oDKZePTOjZTBq1lmWntXAzqIqvebdA,3206
|
|
54
|
+
pycodex/tools/web_search_tool.py,sha256=3ao1IvJw7MwcP35UKLbqrM-nlm5Z-UUFVdEzkJYOV1o,988
|
|
55
|
+
pycodex/tools/write_stdin_tool.py,sha256=jvBDchLLmoDITd7g-ZVeEVXHRhWKkmnjM5laL-53FOM,3609
|
|
56
|
+
pycodex/utils/__init__.py,sha256=lbf-jbo6JS7t1wfFkQqLHlG4zZVqXYGkOIj2cvtrjx8,591
|
|
57
|
+
pycodex/utils/async_bridge.py,sha256=d21Pjim-nsQbSG5pJddd0WaQ03CzA3w3TINWDmmjWbg,1815
|
|
58
|
+
pycodex/utils/compactor.py,sha256=0tlszw1n54aMEGH6sL1U1dEgFSU5ioYL2NI5uSvHSiM,6119
|
|
59
|
+
pycodex/utils/debug.py,sha256=BdXmuAQVWVB1BONKRDlkMpgXpkt4M1jhvEAq5n2AumI,296
|
|
60
|
+
pycodex/utils/dotenv.py,sha256=h7ZgvqpEswetqjhtwfiaHyokAHwNKPakizTAHPHz5Ds,1865
|
|
61
|
+
pycodex/utils/event_helpers.py,sha256=pnxL0-nBC7cnOheHPCIfkHqJjMbkJg3C348_EzAVhFA,6342
|
|
62
|
+
pycodex/utils/get_env.py,sha256=L4S8h84tkgxZKvrxY7ezTgePbXepG-yIigUTKeY6uuE,6094
|
|
63
|
+
pycodex/utils/image_utils.py,sha256=1kXhtTqL4xtazpEABHhtvJBUJlceo8hSzg6ukmf_4FA,2407
|
|
64
|
+
pycodex/utils/random_ids.py,sha256=jLTxZZ0MBbRdQOFDFWGVRjWzIo6FKSQ5ADXbQSLPp1s,379
|
|
65
|
+
pycodex/utils/session_persist.py,sha256=v9KhmjUB2yCg55c85i8ZU1BWfc-WEMDPmpT0Rv4K8a8,22489
|
|
66
|
+
pycodex/utils/truncation.py,sha256=5D6vAorT8EgXgjd7ASwtraAyIZdlCR-w5jKZqJxDzho,5446
|
|
67
|
+
responses_server/__init__.py,sha256=sx9C88YWqmehASZy24FUMqT14A36doM_rj0MIZtQ9iQ,377
|
|
68
|
+
responses_server/__main__.py,sha256=876xZTqhPeDBD2GKKr8pKX33Af8PhFTosAI8bYQsFgQ,61
|
|
69
|
+
responses_server/app.py,sha256=DtA1Sck-6FDyCww3MLkLIMs-fDacY1d-lqZLWoUn-KM,7874
|
|
70
|
+
responses_server/config.py,sha256=sCJYGisPHW9Uup26-R9Zv0o9W_kRVt-ETc2MFCVjG90,2795
|
|
71
|
+
responses_server/messages_api.py,sha256=ialAYTMmN0y-uxQB3Y51ukq-kvkSVO84WVLXaqLsKw0,18775
|
|
72
|
+
responses_server/payload_processors.py,sha256=dqPwqHD-FhyiIwjzFEMf2wCtQDxkRyerZzbdqnH1aDE,3561
|
|
73
|
+
responses_server/server.py,sha256=k4DbsOYkaHHcy38uF9u_QXahJC1cplKsTu-MdC3DCIs,2286
|
|
74
|
+
responses_server/session_store.py,sha256=nwGxQL6nUVKnDVeDHz8IzqNQ7dWk8Ybf0URekOHdxTo,1133
|
|
75
|
+
responses_server/stream_router.py,sha256=xoHwegjziLOgrzvkdAtJbKGoSBn6sBF-unUSo6QxStc,41402
|
|
76
|
+
responses_server/trajectory_dump.py,sha256=5_Wy6tslszrqx3j1JeP_boyTbzIicKy8No84PzMnmHk,5111
|
|
77
|
+
responses_server/tools/__init__.py,sha256=ivsBSEy0SBUhY-Uea5v1XMLXShkwHdCVl0id-1FwdZg,150
|
|
78
|
+
responses_server/tools/custom_adapter.py,sha256=SnIiNUdomff3APtTriURAC9YCeEdhCDjzGMEYusfUdk,8340
|
|
79
|
+
responses_server/tools/web_search.py,sha256=pnKH9JyTScRehWpZS3HpXRvjWsQvC0QZqxjEOCT4ndg,8936
|
|
80
|
+
workspace_server/__init__.py,sha256=PRM4ONODb6hxjBUHkBD4TprE_pkTCIlT9sfftK5ic6M,866
|
|
81
|
+
workspace_server/__main__.py,sha256=876xZTqhPeDBD2GKKr8pKX33Af8PhFTosAI8bYQsFgQ,61
|
|
82
|
+
workspace_server/app.py,sha256=iYUMziweCnsLX0EyT3jnswyVM4x2FhacrlinhBhymHo,59130
|
|
83
|
+
workspace_server/workspace.html,sha256=6UmGemYZfxFFX1qZNhGsVmD_BDWu6qhoPy3zyfd5_po,71483
|
|
84
|
+
workspace_server/workspaces.html,sha256=TpmfzpuCIXTVdFQzQrojxSCtBTHMSESn1zNGOmNz_W8,15787
|
|
85
|
+
workspace_server/workspaces.py,sha256=0oOk1xj7FxoJkLe2OyFDYVwWV4HHyNAwVaF-3Ln3NiY,22660
|
|
86
|
+
python_codex-0.3.0.dist-info/METADATA,sha256=Crsk3zCk6n-qeqnK7DoyH0GkSP4uUdf2f3TR5rTtUBs,35836
|
|
87
|
+
python_codex-0.3.0.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
|
|
88
|
+
python_codex-0.3.0.dist-info/entry_points.txt,sha256=vkV2UWCtEKvQNMJuPNjt8HyBKiwp83JyqBatrBNGDp8,80
|
|
89
|
+
python_codex-0.3.0.dist-info/licenses/LICENSE,sha256=0X8ifk312hYAORM4hlzg8wVSEXYKNmiPgWlB1YIy2Nw,10926
|
|
90
|
+
python_codex-0.3.0.dist-info/RECORD,,
|
responses_server/__init__.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
from .app import
|
|
2
|
-
ManagedResponseServer,
|
|
3
|
-
launch_chat_completion_compat_server,
|
|
4
|
-
run_server,
|
|
5
|
-
)
|
|
1
|
+
from .app import ManagedResponseServer, launch_chat_completion_compat_server, run_server
|
|
6
2
|
from .config import CompatServerConfig
|
|
7
3
|
from .server import ResponseServer
|
|
8
4
|
from .stream_router import StreamRouter
|
responses_server/__main__.py
CHANGED
responses_server/app.py
CHANGED
|
@@ -1,44 +1,52 @@
|
|
|
1
|
-
|
|
2
1
|
import argparse
|
|
3
2
|
import asyncio
|
|
4
|
-
from dataclasses import replace
|
|
5
3
|
import json
|
|
6
4
|
import socket
|
|
7
5
|
import threading
|
|
8
6
|
import time
|
|
7
|
+
import typing
|
|
8
|
+
from dataclasses import replace
|
|
9
9
|
from typing import Iterator
|
|
10
10
|
|
|
11
|
+
import uvicorn
|
|
11
12
|
from fastapi import FastAPI, Request
|
|
12
13
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
13
|
-
import uvicorn
|
|
14
14
|
|
|
15
15
|
from .config import CompatServerConfig
|
|
16
16
|
from .server import ResponseServer
|
|
17
17
|
from .stream_router import OutcommingChatError, UnsupportedIncommingFeature
|
|
18
|
-
import typing
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
def _run_uvicorn_server(server) ->
|
|
20
|
+
def _run_uvicorn_server(server) -> "None":
|
|
22
21
|
asyncio.set_event_loop(asyncio.new_event_loop())
|
|
23
22
|
server.run()
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
def _format_sse_event(
|
|
25
|
+
def _format_sse_event(
|
|
26
|
+
event_name: "str", payload: "typing.Dict[str, object]"
|
|
27
|
+
) -> "bytes":
|
|
27
28
|
data = json.dumps(payload, ensure_ascii=False)
|
|
28
29
|
return f"event: {event_name}\ndata: {data}\n\n".encode("utf-8")
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
def _stream_events(
|
|
32
|
+
def _stream_events(
|
|
33
|
+
response_server: "ResponseServer",
|
|
34
|
+
request_body: "typing.Dict[str, object]",
|
|
35
|
+
request_headers: "typing.Dict[str, str]",
|
|
36
|
+
) -> "Iterator[bytes]":
|
|
32
37
|
try:
|
|
33
|
-
event_iter = response_server.start_response_stream(
|
|
38
|
+
event_iter = response_server.start_response_stream(
|
|
39
|
+
request_body, request_headers
|
|
40
|
+
)
|
|
34
41
|
for event_name, payload in event_iter:
|
|
35
42
|
yield _format_sse_event(event_name, payload)
|
|
36
43
|
except OutcommingChatError as exc:
|
|
37
|
-
|
|
44
|
+
|
|
38
45
|
import traceback
|
|
46
|
+
|
|
39
47
|
exc_info = traceback.format_exception(type(exc), exc, exc.__traceback__)
|
|
40
48
|
error = {
|
|
41
|
-
"message":
|
|
49
|
+
"message": "\n".join(exc_info),
|
|
42
50
|
}
|
|
43
51
|
error_type = getattr(exc, "error_type", None)
|
|
44
52
|
if error_type:
|
|
@@ -54,7 +62,7 @@ def _stream_events(response_server: 'ResponseServer', request_body: 'typing.Dict
|
|
|
54
62
|
)
|
|
55
63
|
|
|
56
64
|
|
|
57
|
-
def build_parser() ->
|
|
65
|
+
def build_parser() -> "argparse.ArgumentParser":
|
|
58
66
|
parser = argparse.ArgumentParser(
|
|
59
67
|
prog="python -m responses_server",
|
|
60
68
|
description=(
|
|
@@ -77,7 +85,7 @@ def build_parser() -> 'argparse.ArgumentParser':
|
|
|
77
85
|
return parser
|
|
78
86
|
|
|
79
87
|
|
|
80
|
-
def run_server(config:
|
|
88
|
+
def run_server(config: "CompatServerConfig") -> "None":
|
|
81
89
|
uvicorn.run(
|
|
82
90
|
ManagedResponseServer.build_app(config),
|
|
83
91
|
host=config.host,
|
|
@@ -87,10 +95,10 @@ def run_server(config: 'CompatServerConfig') -> 'None':
|
|
|
87
95
|
|
|
88
96
|
|
|
89
97
|
def launch_chat_completion_compat_server(
|
|
90
|
-
base_url:
|
|
91
|
-
api_key_env:
|
|
92
|
-
model_provider:
|
|
93
|
-
outcomming_api:
|
|
98
|
+
base_url: "str",
|
|
99
|
+
api_key_env: "typing.Union[str, None]" = None,
|
|
100
|
+
model_provider: "typing.Union[str, None]" = None,
|
|
101
|
+
outcomming_api: "str" = "chat_completions",
|
|
94
102
|
):
|
|
95
103
|
config = CompatServerConfig.from_base_url(
|
|
96
104
|
base_url,
|
|
@@ -106,10 +114,10 @@ def launch_chat_completion_compat_server(
|
|
|
106
114
|
class ManagedResponseServer:
|
|
107
115
|
@staticmethod
|
|
108
116
|
def build_app(
|
|
109
|
-
config:
|
|
117
|
+
config: "CompatServerConfig",
|
|
110
118
|
session_store=None,
|
|
111
119
|
stream_router=None,
|
|
112
|
-
) ->
|
|
120
|
+
) -> "FastAPI":
|
|
113
121
|
response_server = ResponseServer(
|
|
114
122
|
config,
|
|
115
123
|
session_store=session_store,
|
|
@@ -120,7 +128,7 @@ class ManagedResponseServer:
|
|
|
120
128
|
|
|
121
129
|
@app.get("/health")
|
|
122
130
|
@app.get("/healthz")
|
|
123
|
-
async def health() ->
|
|
131
|
+
async def health() -> "typing.Dict[str, bool]":
|
|
124
132
|
return {"ok": True}
|
|
125
133
|
|
|
126
134
|
@app.get("/models")
|
|
@@ -136,7 +144,7 @@ class ManagedResponseServer:
|
|
|
136
144
|
|
|
137
145
|
@app.post("/responses")
|
|
138
146
|
@app.post("/v1/responses")
|
|
139
|
-
async def responses(request:
|
|
147
|
+
async def responses(request: "Request"):
|
|
140
148
|
try:
|
|
141
149
|
request_body = await request.json()
|
|
142
150
|
except Exception as exc:
|
|
@@ -151,8 +159,7 @@ class ManagedResponseServer:
|
|
|
151
159
|
)
|
|
152
160
|
|
|
153
161
|
request_headers = {
|
|
154
|
-
str(key).lower(): str(value)
|
|
155
|
-
for key, value in request.headers.items()
|
|
162
|
+
str(key).lower(): str(value) for key, value in request.headers.items()
|
|
156
163
|
}
|
|
157
164
|
try:
|
|
158
165
|
response_server.stream_router.validate_incomming_request(request_body)
|
|
@@ -173,7 +180,7 @@ class ManagedResponseServer:
|
|
|
173
180
|
|
|
174
181
|
return app
|
|
175
182
|
|
|
176
|
-
def __init__(self, config:
|
|
183
|
+
def __init__(self, config: "CompatServerConfig") -> "None":
|
|
177
184
|
port = config.port or _reserve_free_port()
|
|
178
185
|
self._config = replace(config, port=port)
|
|
179
186
|
self._app = self.build_app(self._config)
|
|
@@ -193,10 +200,10 @@ class ManagedResponseServer:
|
|
|
193
200
|
)
|
|
194
201
|
|
|
195
202
|
@property
|
|
196
|
-
def base_url(self) ->
|
|
203
|
+
def base_url(self) -> "str":
|
|
197
204
|
return f"http://{self._config.host}:{self._config.port}/v1"
|
|
198
205
|
|
|
199
|
-
def start(self, timeout_seconds:
|
|
206
|
+
def start(self, timeout_seconds: "float" = 10.0) -> "None":
|
|
200
207
|
self._thread.start()
|
|
201
208
|
deadline = time.time() + timeout_seconds
|
|
202
209
|
while not self._server.started:
|
|
@@ -206,16 +213,14 @@ class ManagedResponseServer:
|
|
|
206
213
|
)
|
|
207
214
|
time.sleep(0.01)
|
|
208
215
|
|
|
209
|
-
def stop(self, timeout_seconds:
|
|
216
|
+
def stop(self, timeout_seconds: "float" = 5.0) -> "None":
|
|
210
217
|
self._server.should_exit = True
|
|
211
218
|
self._thread.join(timeout=timeout_seconds)
|
|
212
219
|
if self._thread.is_alive():
|
|
213
|
-
raise RuntimeError(
|
|
214
|
-
"timed out waiting for managed responses server to stop"
|
|
215
|
-
)
|
|
220
|
+
raise RuntimeError("timed out waiting for managed responses server to stop")
|
|
216
221
|
|
|
217
222
|
|
|
218
|
-
def main() ->
|
|
223
|
+
def main() -> "None":
|
|
219
224
|
args = build_parser().parse_args()
|
|
220
225
|
run_server(
|
|
221
226
|
CompatServerConfig(
|
|
@@ -234,7 +239,7 @@ if __name__ == "__main__":
|
|
|
234
239
|
main()
|
|
235
240
|
|
|
236
241
|
|
|
237
|
-
def _reserve_free_port() ->
|
|
242
|
+
def _reserve_free_port() -> "int":
|
|
238
243
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
239
244
|
try:
|
|
240
245
|
sock.bind(("127.0.0.1", 0))
|
responses_server/config.py
CHANGED
|
@@ -1,43 +1,46 @@
|
|
|
1
|
-
|
|
2
1
|
import os
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
import urllib.parse
|
|
5
2
|
import typing
|
|
3
|
+
import urllib.parse
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
DEFAULT_OUTCOMMING_TIMEOUT_SECONDS = 300.0
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
@dataclass(
|
|
9
|
+
@dataclass(
|
|
10
|
+
frozen=True,
|
|
11
|
+
)
|
|
9
12
|
class CompatServerConfig:
|
|
10
|
-
host:
|
|
11
|
-
port:
|
|
12
|
-
outcomming_base_url:
|
|
13
|
-
outcomming_api:
|
|
14
|
-
outcomming_api_key_env:
|
|
15
|
-
model_provider:
|
|
16
|
-
timeout_seconds:
|
|
13
|
+
host: "str" = "127.0.0.1"
|
|
14
|
+
port: "int" = 0
|
|
15
|
+
outcomming_base_url: "str" = "http://127.0.0.1:8000/v1"
|
|
16
|
+
outcomming_api: "str" = "chat_completions"
|
|
17
|
+
outcomming_api_key_env: "typing.Union[str, None]" = None
|
|
18
|
+
model_provider: "typing.Union[str, None]" = None
|
|
19
|
+
timeout_seconds: "float" = DEFAULT_OUTCOMMING_TIMEOUT_SECONDS
|
|
17
20
|
|
|
18
|
-
def outcomming_api_key(self) ->
|
|
21
|
+
def outcomming_api_key(self) -> "typing.Union[str, None]":
|
|
19
22
|
if self.outcomming_api_key_env is None:
|
|
20
23
|
return None
|
|
21
24
|
value = os.environ.get(self.outcomming_api_key_env, "").strip()
|
|
22
25
|
return value or None
|
|
23
26
|
|
|
24
|
-
def outcomming_chat_completions_url(self) ->
|
|
27
|
+
def outcomming_chat_completions_url(self) -> "str":
|
|
25
28
|
base = self.outcomming_base_url.rstrip("/")
|
|
26
29
|
return f"{base}/chat/completions"
|
|
27
30
|
|
|
28
|
-
def outcomming_messages_url(self) ->
|
|
31
|
+
def outcomming_messages_url(self) -> "str":
|
|
29
32
|
base = self.outcomming_base_url.rstrip("/")
|
|
30
33
|
return f"{base}/messages"
|
|
31
34
|
|
|
32
|
-
def outcomming_models_url(self) ->
|
|
35
|
+
def outcomming_models_url(self) -> "str":
|
|
33
36
|
base = self.outcomming_base_url.rstrip("/")
|
|
34
37
|
return f"{base}/models"
|
|
35
38
|
|
|
36
|
-
def normalized_outcomming_api(self) ->
|
|
39
|
+
def normalized_outcomming_api(self) -> "str":
|
|
37
40
|
value = str(self.outcomming_api or "").strip().lower()
|
|
38
41
|
return value or "chat_completions"
|
|
39
42
|
|
|
40
|
-
def with_ephemeral_port(self) ->
|
|
43
|
+
def with_ephemeral_port(self) -> "CompatServerConfig":
|
|
41
44
|
return CompatServerConfig(
|
|
42
45
|
host=self.host,
|
|
43
46
|
port=0,
|
|
@@ -51,11 +54,11 @@ class CompatServerConfig:
|
|
|
51
54
|
@classmethod
|
|
52
55
|
def from_base_url(
|
|
53
56
|
cls,
|
|
54
|
-
outcomming_base_url:
|
|
55
|
-
api_key_env:
|
|
56
|
-
model_provider:
|
|
57
|
-
outcomming_api:
|
|
58
|
-
) ->
|
|
57
|
+
outcomming_base_url: "str",
|
|
58
|
+
api_key_env: "typing.Union[str, None]" = None,
|
|
59
|
+
model_provider: "typing.Union[str, None]" = None,
|
|
60
|
+
outcomming_api: "str" = "chat_completions",
|
|
61
|
+
) -> "CompatServerConfig":
|
|
59
62
|
parsed = urllib.parse.urlparse(outcomming_base_url)
|
|
60
63
|
if not parsed.scheme or not parsed.netloc:
|
|
61
64
|
raise ValueError(f"invalid outcomming base url: {outcomming_base_url}")
|