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.
Files changed (84) hide show
  1. pycodex/__init__.py +18 -14
  2. pycodex/agent.py +468 -462
  3. pycodex/bootstrap.py +417 -0
  4. pycodex/cli.py +236 -436
  5. pycodex/compat.py +19 -5
  6. pycodex/context.py +222 -212
  7. pycodex/doctor.py +52 -48
  8. pycodex/events.py +857 -0
  9. pycodex/feishu_card.py +217 -163
  10. pycodex/feishu_link.py +43 -83
  11. pycodex/model.py +329 -252
  12. pycodex/model_metadata.py +19 -7
  13. pycodex/portable.py +90 -52
  14. pycodex/portable_server.py +32 -24
  15. pycodex/prompts/models.json +235 -803
  16. pycodex/protocol.py +177 -137
  17. pycodex/runtime.py +579 -174
  18. pycodex/runtime_services.py +204 -157
  19. pycodex/tools/__init__.py +4 -1
  20. pycodex/tools/apply_patch_tool.py +69 -48
  21. pycodex/tools/base_tool.py +89 -42
  22. pycodex/tools/clock_tool.py +201 -0
  23. pycodex/tools/close_agent_tool.py +2 -2
  24. pycodex/tools/code_mode_manager.py +77 -64
  25. pycodex/tools/exec_command_tool.py +26 -11
  26. pycodex/tools/exec_tool.py +4 -4
  27. pycodex/tools/grep_files_tool.py +12 -10
  28. pycodex/tools/ipython_tool.py +10 -13
  29. pycodex/tools/list_dir_tool.py +13 -9
  30. pycodex/tools/read_file_tool.py +29 -17
  31. pycodex/tools/request_permissions_tool.py +15 -5
  32. pycodex/tools/request_user_input_tool.py +13 -104
  33. pycodex/tools/resume_agent_tool.py +2 -2
  34. pycodex/tools/send_input_tool.py +11 -8
  35. pycodex/tools/shell_command_tool.py +7 -5
  36. pycodex/tools/shell_tool.py +7 -5
  37. pycodex/tools/spawn_agent_tool.py +7 -4
  38. pycodex/tools/unified_exec_manager.py +102 -69
  39. pycodex/tools/update_plan_tool.py +8 -5
  40. pycodex/tools/view_image_tool.py +13 -13
  41. pycodex/tools/wait_agent_tool.py +27 -4
  42. pycodex/tools/wait_tool.py +5 -4
  43. pycodex/tools/web_search_tool.py +4 -2
  44. pycodex/tools/write_stdin_tool.py +12 -11
  45. pycodex/utils/__init__.py +2 -17
  46. pycodex/utils/compactor.py +50 -66
  47. pycodex/utils/debug.py +2 -2
  48. pycodex/utils/dotenv.py +6 -7
  49. pycodex/utils/event_helpers.py +190 -0
  50. pycodex/utils/get_env.py +27 -70
  51. pycodex/utils/image_utils.py +76 -0
  52. pycodex/utils/random_ids.py +1 -2
  53. pycodex/utils/session_persist.py +263 -161
  54. pycodex/utils/truncation.py +21 -45
  55. python_codex-0.3.0.dist-info/METADATA +704 -0
  56. python_codex-0.3.0.dist-info/RECORD +90 -0
  57. responses_server/__init__.py +1 -5
  58. responses_server/__main__.py +0 -1
  59. responses_server/app.py +36 -31
  60. responses_server/config.py +25 -22
  61. responses_server/messages_api.py +96 -49
  62. responses_server/payload_processors.py +25 -19
  63. responses_server/server.py +11 -11
  64. responses_server/session_store.py +14 -11
  65. responses_server/stream_router.py +196 -107
  66. responses_server/tools/custom_adapter.py +17 -16
  67. responses_server/tools/web_search.py +39 -36
  68. responses_server/trajectory_dump.py +51 -13
  69. workspace_server/__main__.py +0 -1
  70. workspace_server/app.py +470 -384
  71. workspace_server/workspace.html +859 -232
  72. workspace_server/workspaces.html +94 -95
  73. workspace_server/workspaces.py +168 -100
  74. pycodex/collaboration.py +0 -20
  75. pycodex/interactive_session.py +0 -415
  76. pycodex/prompts/collaboration_default.md +0 -11
  77. pycodex/prompts/collaboration_plan.md +0 -128
  78. pycodex/utils/toolcall_visualize.py +0 -713
  79. pycodex/utils/visualize.py +0 -553
  80. python_codex-0.2.6.dist-info/METADATA +0 -441
  81. python_codex-0.2.6.dist-info/RECORD +0 -91
  82. {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
  83. {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
  84. {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,8 +9,8 @@ class MessagesAPIAdapterError(ValueError):
9
9
 
10
10
 
11
11
  def build_messages_request(
12
- outcomming_request: 'typing.Dict[str, object]',
13
- ) -> 'typing.Dict[str, object]':
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: 'typing.List[typing.Dict[str, object]]' = []
23
- messages: 'typing.List[typing.Dict[str, object]]' = []
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: 'typing.Dict[str, object]' = {
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: 'typing.Union[str, None]',
88
- payload: 'typing.Dict[str, object]',
89
- state: 'typing.Dict[str, object]',
90
- ) -> 'typing.List[typing.Dict[str, object]]':
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: 'typing.List[typing.Dict[str, object]]' = []
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: 'typing.Dict[str, object]') -> 'bool':
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(raw_content: 'object') -> 'typing.List[typing.Dict[str, object]]':
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: 'typing.Dict[str, object]',
204
- ) -> 'typing.List[typing.Dict[str, object]]':
205
- blocks: 'typing.List[typing.Dict[str, object]]' = []
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: 'typing.Dict[str, object]',
239
- ) -> 'typing.Dict[str, object]':
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: 'object',
249
- ) -> 'typing.List[typing.Dict[str, object]]':
250
- translated: 'typing.List[typing.Dict[str, object]]' = []
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: 'object',
276
- parallel_tool_calls: 'object',
277
- ) -> 'typing.Union[typing.Dict[str, object], None]':
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: 'typing.Dict[str, object]'
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: 'object') -> 'typing.Dict[str, object]':
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: 'typing.Dict[str, object]') -> 'int':
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: 'object') -> 'typing.Union[typing.Dict[str, object], None]':
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: 'object') -> 'typing.Dict[str, object]':
419
+ def _translate_usage(raw_usage: "object") -> "typing.Dict[str, object]":
373
420
  if not isinstance(raw_usage, dict):
374
421
  return {}
375
- usage: 'typing.Dict[str, object]' = {}
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: 'typing.Dict[str, int]' = {}
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: 'object') -> 'int':
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: 'object') -> 'typing.Union[str, None]':
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: 'str') -> 'typing.Dict[str, object]':
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: 'str') -> 'typing.Dict[str, object]':
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: 'int',
434
- call_id: 'str' = "",
435
- name: 'str' = "",
436
- arguments: 'str' = "",
437
- ) -> 'typing.Dict[str, object]':
438
- tool_call: 'typing.Dict[str, object]' = {
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: 'typing.Dict[str, object]') -> 'typing.Dict[str, object]':
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: 'str') -> 'typing.Dict[str, object]':
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: 'object') -> 'str':
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
- import typing
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: 'str'
29
- messages: 'typing.List[ChatMessage]'
30
- stream: 'bool'
31
- max_tokens: 'Optional[int]'
32
- tools: 'Optional[typing.List[typing.Dict[str, object]]]'
33
- tool_choice: 'Optional[object]'
34
- parallel_tool_calls: 'Optional[bool]'
35
- return_token_ids: 'Optional[bool]'
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: 'OutgoingRequest') -> 'OutgoingRequest':
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(outcomming_request: 'OutgoingRequest') -> 'OutgoingRequest':
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
- def _replace_developer_messages(outcomming_request: 'OutgoingRequest') -> 'OutgoingRequest':
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['messages']:
66
+ for message in outcomming_request["messages"]:
61
67
  if message.get("role") == "developer":
62
- message['role'] = "system"
68
+ message["role"] = "system"
63
69
 
64
70
  return outcomming_request
65
71
 
66
72
 
67
- PAYLOAD_POST_PROCESSORS: 'typing.Dict[str, PayloadPostProcessor]' = {
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: 'OutgoingRequest',
76
- model_provider: 'typing.Union[str, None]',
77
- ) -> 'OutgoingRequest':
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
@@ -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: 'CompatServerConfig',
14
- session_store: 'typing.Union[SessionStore, None]' = None,
15
- stream_router: 'typing.Union[StreamRouter, None]' = None,
16
- ) -> 'None':
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) -> 'CompatServerConfig':
23
+ def config(self) -> "CompatServerConfig":
24
24
  return self._config
25
25
 
26
26
  @property
27
- def session_store(self) -> 'SessionStore':
27
+ def session_store(self) -> "SessionStore":
28
28
  return self._session_store
29
29
 
30
30
  @property
31
- def stream_router(self) -> 'StreamRouter':
31
+ def stream_router(self) -> "StreamRouter":
32
32
  return self._stream_router
33
33
 
34
- def list_models(self) -> 'typing.Dict[str, object]':
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: 'typing.Dict[str, object]',
40
- request_headers: 'typing.Dict[str, str]',
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(frozen=True, )
7
+ @dataclass(
8
+ frozen=True,
9
+ )
9
10
  class StoredResponse:
10
- response_id: 'str'
11
- session_id: 'typing.Union[str, None]'
12
- model: 'str'
13
- created_at: 'float'
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) -> 'None':
18
+ def __init__(self) -> "None":
18
19
  self._lock = threading.Lock()
19
20
  self._next_response_number = 1
20
- self._responses: 'typing.Dict[str, StoredResponse]' = {}
21
+ self._responses: "typing.Dict[str, StoredResponse]" = {}
21
22
 
22
- def create_response(self, session_id: 'typing.Union[str, None]', model: 'str') -> 'StoredResponse':
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: 'str') -> 'typing.Union[StoredResponse, None]':
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)