netizen-cli 0.10.2__tar.gz → 0.11.1__tar.gz

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 (253) hide show
  1. {netizen_cli-0.10.2/netizen_cli.egg-info → netizen_cli-0.11.1}/PKG-INFO +3 -2
  2. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/README.md +21 -9
  3. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/config.example.yaml +6 -5
  4. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/__init__.py +1 -1
  5. netizen_cli-0.11.1/netizen_cli/account_rate_limits.py +340 -0
  6. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/auth.py +99 -169
  7. netizen_cli-0.11.1/netizen_cli/admin/credentials.py +233 -0
  8. netizen_cli-0.11.1/netizen_cli/admin/feishu.py +335 -0
  9. netizen_cli-0.11.1/netizen_cli/admin/login.py +234 -0
  10. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/port_config.py +2 -2
  11. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/presentation.py +5 -18
  12. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/static/admin.css +32 -0
  13. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/static/admin.js +336 -55
  14. netizen_cli-0.11.1/netizen_cli/admin/static/chat-picker.css +28 -0
  15. netizen_cli-0.11.1/netizen_cli/admin/static/chat-picker.js +301 -0
  16. netizen_cli-0.11.1/netizen_cli/admin/static/identity.js +272 -0
  17. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/static/index.html +61 -10
  18. netizen_cli-0.11.1/netizen_cli/admin/static/login.html +29 -0
  19. netizen_cli-0.11.1/netizen_cli/admin/static/login.js +134 -0
  20. netizen_cli-0.11.1/netizen_cli/admin/users.py +378 -0
  21. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/web.py +319 -98
  22. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/bindings.py +54 -2
  23. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/__init__.py +4 -0
  24. netizen_cli-0.11.1/netizen_cli/cards/chat_target.py +283 -0
  25. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/controls.py +99 -28
  26. netizen_cli-0.11.1/netizen_cli/cards/fork.py +350 -0
  27. netizen_cli-0.11.1/netizen_cli/cards/pagination.py +50 -0
  28. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/questions.py +79 -21
  29. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/reply.py +122 -42
  30. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/scheduled.py +441 -189
  31. netizen_cli-0.11.1/netizen_cli/cards/usage.py +101 -0
  32. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/reply_presenter.py +200 -50
  33. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel_app.py +756 -114
  34. netizen_cli-0.11.1/netizen_cli/chat_avatars.py +114 -0
  35. netizen_cli-0.11.1/netizen_cli/chat_targets.py +24 -0
  36. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli.py +76 -20
  37. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli_data.py +2 -5
  38. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli_services.py +0 -1
  39. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli_setup.py +77 -17
  40. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli_update.py +25 -6
  41. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli_update_worker.py +71 -13
  42. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/codex_runtime.py +388 -125
  43. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/defaults/service.py +9 -1
  44. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/domain.py +12 -0
  45. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/experience.py +25 -1
  46. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/feishu_app_onboarding.py +7 -3
  47. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/feishu_app_permissions.py +18 -15
  48. netizen_cli-0.11.1/netizen_cli/feishu_links.py +26 -0
  49. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/main.py +48 -43
  50. netizen_cli-0.11.1/netizen_cli/management/chat_directory.py +269 -0
  51. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/management/service.py +52 -5
  52. netizen_cli-0.11.1/netizen_cli/pagination.py +46 -0
  53. netizen_cli-0.11.1/netizen_cli/partial_answers.py +54 -0
  54. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/runtime/contracts.py +22 -11
  55. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/schedules/service.py +43 -18
  56. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/sdk_gap_adapter.py +24 -2
  57. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/service_launcher.py +0 -5
  58. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/settings.py +2 -26
  59. netizen_cli-0.11.1/netizen_cli/task_text.py +174 -0
  60. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/terminal_cleanup.py +2 -2
  61. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/turn_activity.py +16 -2
  62. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/turn_files.py +11 -22
  63. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/turn_plan_observer.py +8 -2
  64. {netizen_cli-0.10.2 → netizen_cli-0.11.1/netizen_cli.egg-info}/PKG-INFO +3 -2
  65. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli.egg-info/SOURCES.txt +41 -0
  66. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli.egg-info/requires.txt +2 -1
  67. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/pyproject.toml +3 -2
  68. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/skills/netizen-user-guide/SKILL.md +6 -2
  69. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/skills/netizen-user-guide/references/user-guide.md +204 -48
  70. netizen_cli-0.11.1/tests/test_account_rate_limits.py +445 -0
  71. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cards.py +232 -1
  72. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_channel_app.py +417 -16
  73. netizen_cli-0.11.1/tests/test_chat_avatars.py +133 -0
  74. netizen_cli-0.11.1/tests/test_chat_target_cards.py +181 -0
  75. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli.py +126 -1
  76. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_data.py +20 -1
  77. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_integration.py +28 -5
  78. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_setup.py +106 -4
  79. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_update.py +173 -7
  80. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_codex_runtime.py +322 -143
  81. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_codex_sdk_capabilities.py +1 -1
  82. netizen_cli-0.11.1/tests/test_codex_sdk_upgrade_0161.py +207 -0
  83. netizen_cli-0.11.1/tests/test_cron_chat_targets.py +461 -0
  84. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_deployment.py +2 -2
  85. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_feishu_app_onboarding.py +4 -2
  86. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_feishu_app_permissions.py +23 -7
  87. netizen_cli-0.11.1/tests/test_fork_cards.py +533 -0
  88. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_main.py +49 -26
  89. netizen_cli-0.11.1/tests/test_pagination.py +75 -0
  90. netizen_cli-0.11.1/tests/test_partial_answer_delivery.py +487 -0
  91. netizen_cli-0.11.1/tests/test_partial_answers.py +54 -0
  92. netizen_cli-0.11.1/tests/test_persistent_fork_bindings.py +120 -0
  93. netizen_cli-0.11.1/tests/test_persistent_fork_channel.py +842 -0
  94. netizen_cli-0.11.1/tests/test_persistent_fork_runtime.py +312 -0
  95. netizen_cli-0.11.1/tests/test_probe_feishu_partial_answers.py +138 -0
  96. netizen_cli-0.11.1/tests/test_probe_persistent_fork.py +42 -0
  97. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_probe_scheduled_tasks.py +1 -0
  98. netizen_cli-0.11.1/tests/test_probe_skill_execution.py +167 -0
  99. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_question_cards.py +68 -0
  100. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_question_channel.py +130 -9
  101. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_question_target_parity.py +79 -7
  102. netizen_cli-0.11.1/tests/test_resume_channel.py +91 -0
  103. netizen_cli-0.11.1/tests/test_runtime_resume_activation.py +226 -0
  104. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_schedule_card_retry.py +56 -1
  105. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_schedule_cards.py +355 -10
  106. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_schedule_scope_matrix.py +3 -1
  107. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_schedule_service.py +109 -1
  108. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_scheduled_channel.py +107 -0
  109. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_sdk_gap_adapter.py +69 -3
  110. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_service_launcher.py +1 -6
  111. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_session_defaults.py +24 -0
  112. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_settings_projects.py +12 -43
  113. netizen_cli-0.11.1/tests/test_task_text.py +250 -0
  114. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_terminal_cleanup.py +2 -2
  115. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_turn_activity.py +31 -1
  116. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_turn_plan_observer.py +21 -1
  117. netizen_cli-0.11.1/tests/test_usage_card.py +135 -0
  118. netizen_cli-0.11.1/tests/test_usage_control.py +167 -0
  119. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/MANIFEST.in +0 -0
  120. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/_build.py +0 -0
  121. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/__main__.py +0 -0
  122. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/__init__.py +0 -0
  123. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/errors.py +0 -0
  124. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/queries.py +0 -0
  125. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/admin/transport.py +0 -0
  126. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/builtin_skills.py +0 -0
  127. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/callbacks.py +0 -0
  128. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/defaults.py +0 -0
  129. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cards/model_info.py +0 -0
  130. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/__init__.py +0 -0
  131. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/completion_mentions.py +0 -0
  132. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/input_preparation.py +0 -0
  133. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/messages.py +0 -0
  134. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/ports.py +0 -0
  135. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/question_inputs.py +0 -0
  136. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/reactions.py +0 -0
  137. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/channel/topics.py +0 -0
  138. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli_packages.py +0 -0
  139. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/cli_update_restore.py +0 -0
  140. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/completion_mention.py +0 -0
  141. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/database_migrations.py +0 -0
  142. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/defaults/__init__.py +0 -0
  143. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/defaults/models.py +0 -0
  144. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/defaults/store.py +0 -0
  145. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/deployment/__init__.py +0 -0
  146. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/deployment/restart_worker.py +0 -0
  147. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/deployment/update_executor.py +0 -0
  148. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/deployment/update_protocol.py +0 -0
  149. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/error_messages.py +0 -0
  150. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/git_status.py +0 -0
  151. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/image_inputs.py +0 -0
  152. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/instance.py +0 -0
  153. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/lark_app.py +0 -0
  154. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/management/__init__.py +0 -0
  155. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/management/blocking_io.py +0 -0
  156. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/management/chat_labels.py +0 -0
  157. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/management/coordination.py +0 -0
  158. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/management/updates.py +0 -0
  159. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/markdown_images.py +0 -0
  160. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/message_content.py +0 -0
  161. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/message_history.py +0 -0
  162. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/message_preparation.py +0 -0
  163. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/message_projection.py +0 -0
  164. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/migrations/__init__.py +0 -0
  165. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/migrations/schema.py +0 -0
  166. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/migrations/v14.py +0 -0
  167. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/model_settings.py +0 -0
  168. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/package_resources.py +0 -0
  169. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/projects.py +0 -0
  170. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/prompt_projection.py +0 -0
  171. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/quoted_context.py +0 -0
  172. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/result_images.py +0 -0
  173. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/runtime/__init__.py +0 -0
  174. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/runtime/name_writes.py +0 -0
  175. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/runtime/thread_naming.py +0 -0
  176. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/schedules/__init__.py +0 -0
  177. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/schedules/mcp.py +0 -0
  178. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/schedules/models.py +0 -0
  179. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/schedules/scheduler.py +0 -0
  180. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/schedules/store.py +0 -0
  181. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/session_settings.py +0 -0
  182. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/skill_references.py +0 -0
  183. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/turn_patch_children.py +0 -0
  184. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli/user_questions.py +0 -0
  185. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli.egg-info/dependency_links.txt +0 -0
  186. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli.egg-info/entry_points.txt +0 -0
  187. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/netizen_cli.egg-info/top_level.txt +0 -0
  188. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/setup.cfg +0 -0
  189. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/skills/netizen-lark/SKILL.md +0 -0
  190. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_admin_update_executor_probe.py +0 -0
  191. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_binding_scheduled_channel.py +0 -0
  192. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_bindings.py +0 -0
  193. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_channel_fixtures.py +0 -0
  194. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_channel_input_preparation.py +0 -0
  195. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_check_sdk.py +0 -0
  196. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_distribution.py +0 -0
  197. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_packages.py +0 -0
  198. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_packaging.py +0 -0
  199. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_cli_services.py +0 -0
  200. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_completion_mention_cards.py +0 -0
  201. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_completion_mentions.py +0 -0
  202. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_completion_topic_delivery.py +0 -0
  203. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_database_migrations.py +0 -0
  204. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_defaults_cards.py +0 -0
  205. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_defaults_channel.py +0 -0
  206. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_defaults_runtime.py +0 -0
  207. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_documentation_links.py +0 -0
  208. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_domain_experience.py +0 -0
  209. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_error_messages.py +0 -0
  210. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_file_pagination.py +0 -0
  211. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_git_status.py +0 -0
  212. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_image_inputs.py +0 -0
  213. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_lark_app.py +0 -0
  214. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_lark_sdk_contract.py +0 -0
  215. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_markdown_images.py +0 -0
  216. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_message_content.py +0 -0
  217. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_message_history.py +0 -0
  218. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_message_material_inputs.py +0 -0
  219. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_message_preparation.py +0 -0
  220. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_migration_schema_validation.py +0 -0
  221. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_model_info_cards.py +0 -0
  222. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_model_settings.py +0 -0
  223. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_name_writes.py +0 -0
  224. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_probe_child_files.py +0 -0
  225. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_probe_feishu_completion_mention.py +0 -0
  226. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_probe_feishu_turn_file_card.py +0 -0
  227. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_probe_python_sdk.py +0 -0
  228. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_probe_sdk_completion_race.py +0 -0
  229. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_probe_thread_naming.py +0 -0
  230. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_project_delete_channel.py +0 -0
  231. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_project_delete_runtime.py +0 -0
  232. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_project_deletion_storage.py +0 -0
  233. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_prompt_projection.py +0 -0
  234. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_quoted_context.py +0 -0
  235. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_release_script.py +0 -0
  236. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_repository_hygiene.py +0 -0
  237. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_result_images.py +0 -0
  238. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_retired_deployment.py +0 -0
  239. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_runtime_thread_naming.py +0 -0
  240. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_schedule_mcp.py +0 -0
  241. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_schedule_run_now.py +0 -0
  242. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_scheduled_runtime.py +0 -0
  243. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_scheduler.py +0 -0
  244. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_scheduler_presenter.py +0 -0
  245. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_schedules_storage.py +0 -0
  246. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_session_settings.py +0 -0
  247. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_thread_naming.py +0 -0
  248. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_turn_files.py +0 -0
  249. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_turn_patch_children.py +0 -0
  250. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_turn_patch_statistics.py +0 -0
  251. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_update_executor.py +0 -0
  252. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_user_guide_skill.py +0 -0
  253. {netizen_cli-0.10.2 → netizen_cli-0.11.1}/tests/test_user_questions.py +0 -0
@@ -1,16 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netizen-cli
3
- Version: 0.10.2
3
+ Version: 0.11.1
4
4
  Summary: Use native Codex threads through a Feishu Channel.
5
5
  Requires-Python: <3.15,>=3.11
6
6
  Requires-Dist: h11==0.16.0
7
+ Requires-Dist: httpx==0.28.1
7
8
  Requires-Dist: lark-channel-sdk==1.4.0
8
9
  Requires-Dist: lark-oapi==1.7.2
9
10
  Requires-Dist: markdown-it-py==4.0.0
10
11
  Requires-Dist: mdformat==1.0.0
11
12
  Requires-Dist: mdformat-gfm==1.0.0
12
13
  Requires-Dist: mcp==2.2.0
13
- Requires-Dist: openai-codex==0.159.2
14
+ Requires-Dist: openai-codex==0.162.1
14
15
  Requires-Dist: pydantic==2.13.4
15
16
  Requires-Dist: PyYAML==6.0.3
16
17
  Requires-Dist: qrcode==8.2
@@ -41,6 +41,9 @@
41
41
  [群聊上下文](skills/netizen-user-guide/references/user-guide.md#发送消息引用与图片)。
42
42
  - **取回本轮文件。** 完成后从回复卡片中按需发送有原生执行记录的文件;发送的是点击时
43
43
  的当前内容,详见[文件说明](skills/netizen-user-guide/references/user-guide.md#查看和发送本轮文件)。
44
+ - **保留上下文,另开一条讨论。** 用
45
+ [持久分支](skills/netizen-user-guide/references/user-guide.md#fork-持久分支)在当前聊天或其他群
46
+ 创建普通话题,之后独立继续。
44
47
  - **展开临时讨论,推进持续目标。** 用
45
48
  [Side](skills/netizen-user-guide/references/user-guide.md#side-临时话题)另开临时话题,或用
46
49
  [Goal](skills/netizen-user-guide/references/user-guide.md#goal)让 Codex 围绕目标持续推进。
@@ -80,16 +83,20 @@ netizen start
80
83
  netizen status
81
84
  ```
82
85
 
83
- setup 准备配置、飞书授权和实例数据并注册服务,不默认启动。首次缺凭据会输出浏览器
84
- 验证链接;Agent 可运行 `netizen setup </dev/null`,转交链接并保留同一进程,确认后自动
85
- 保存凭据。不要把 App Secret 发到聊天里。start 等待服务真正就绪,每次实际启动均检查
86
- 数据格式并按需迁移。
86
+ setup 准备配置、飞书应用、首位管理员和实例数据并注册服务,不默认启动。缺应用凭据或
87
+ 管理员时会输出浏览器验证链接;Agent 可运行 `netizen setup </dev/null`,逐次转交链接并
88
+ 保留同一进程,确认后自动保存。已有应用也可用 `netizen setup --admin-open-id ou_...`
89
+ 指定该应用下的首位管理员;已有名单不会被覆盖。不要把 App Secret 发到聊天里。
90
+ start 等待服务真正就绪,每次实际启动均检查管理员配置、数据格式并按需迁移;后台启动
91
+ 缺配置会明确失败,不等待扫码。
87
92
 
88
93
  默认实例数据集中在 `~/.netizen`;程序位于选定 Python 环境。用 `--root` 或 NETIZEN_ROOT
89
94
  选择其他实例,例如 `netizen setup --root "$HOME/work/.netizen" --admin-port 8890`。
90
95
  各实例共享原生 Codex 状态;服务固定注册时的 Python,终端换 venv 不改变已有绑定。
91
96
 
92
97
  日常维护用 `netizen update` 更新当前环境并恢复原来运行的实例;它不接受 --root。
98
+ Agent/脚本使用 `netizen update --json`:stdout 只输出最终 JSON,进度和包管理器输出
99
+ 写入 stderr。以 JSON 和退出码判断结果,包验证成功与实例恢复 ready 分别报告。
93
100
  `netizen remove --root ...` 默认保留数据,--purge 才清理精确范围,-y 只省略确认。
94
101
  完整流程、失败边界、跨环境切换和卸载见 [CLI 安装与维护](docs/cli.md)。
95
102
 
@@ -122,8 +129,10 @@ setup 准备配置、飞书授权和实例数据并注册服务,不默认启
122
129
  | 新建会话,选择项目 | `/new` |
123
130
  | 查看、保存或删除当前聊天的会话默认配置 | `/defaults` |
124
131
  | 查看并切换已有会话 | `/sessions` |
132
+ | 从当前空闲会话另建持久分支话题 | `/fork` |
125
133
  | 调整模型、思考强度和过程卡 | `/config`,当前会话空闲时使用 |
126
134
  | 查看当前任务和上下文用量 | `/status` |
135
+ | 查看共享 Codex 账号额度和重置时间 | `/usage` |
127
136
  | 中断当前任务 | `/stop`;不保证前台工具进程退出,见[停止说明](skills/netizen-user-guide/references/user-guide.md#stop) |
128
137
  | 查找当前机器人的管理入口 | `/admin`;返回实际管理 URL 和实例根目录,不返回登录凭据 |
129
138
  | 管理定时任务 | `/cron`,也可以直接用自然语言描述计划 |
@@ -157,10 +166,13 @@ Side 会在同一聊天中新建话题,可继续多轮讨论。它与原会话
157
166
  ## 管理与维护
158
167
 
159
168
  实例管理员可以通过 **Admin Web** 集中管理项目、会话、Side 话题、会话默认配置和定时任务,
160
- 并在“系统维护”中检查正式更新或重启服务。发送 `/admin` 可获取当前实例的管理 URL
169
+ 并在“系统维护”中查看安装信息或重启服务。发送 `/admin` 可获取当前实例的管理 URL
161
170
  和根目录,无需创建会话。未指定端口时首次从 `8787` 起分配并保存,各实例独立监听;
162
- 管理页仍使用安装器生成的独立管理员凭据,命令不会返回凭据或免登录链接。
163
- [访问方式](docs/deployment.md#配置与管理页访问)见部署文档。
171
+ 管理页使用飞书登录,并核对当前应用的管理员名单,命令不会返回凭据或免登录链接。
172
+ Admin 是实例必备能力;管理员可在“系统管理 → 权限管理 → 管理员”中维护名单。
173
+ 服务重启后需要重新登录。
174
+ [访问方式](docs/deployment.md#配置与管理页访问)见部署文档。飞书登录与管理员维护描述
175
+ 当前源码行为,真人和平台验收尚未完成,见[验收状态](docs/deployment.md#admin-飞书登录与管理员维护验收)。
164
176
 
165
177
  升级与重启都会影响正在执行的任务,重启后不会自动续跑。
166
178
  [正式升级、启停与卸载](docs/deployment.md#升级启停和卸载) ·
@@ -169,13 +181,13 @@ Side 会在同一聊天中新建话题,可继续多轮讨论。它与原会话
169
181
  ## 使用前了解
170
182
 
171
183
  - **共享环境。** 飞书应用的可用范围和群成员关系控制谁能访问;同一实例复用服务账号,
172
- 不提供按用户或项目隔离的权限体系。Admin Web 面向受信内网的单管理员。
184
+ 不提供按用户或项目隔离的权限体系。Admin Web 面向受信内网,名单中的管理员权限相同。
173
185
  - **输入与输出。** 支持文本、普通图片和富文本图片,也可提供飞书 2.0 卡片、合并转发与转发话题作为
174
186
  [背景材料](skills/netizen-user-guide/references/user-guide.md#卡片合并转发与转发话题)。材料内部的媒体只提供未读附件描述;直接文件和音视频仍不支持。
175
187
  飞书 1.0 卡片明确不支持。
176
188
  输出文件按本轮原生记录发现,不扫描工作区补齐,也不自动上传。
177
189
  - **Codex 能力有宿主差异。** 飞书复用原生会话和工具,但并非每个 App/CLI 控件都可用;
178
- 新会话采用 `auto_review`,不能完整继承 App 的 Ask/Custom。
190
+ 全新会话采用 `auto_review`,持久分支沿用原生继承权限;不提供 App 的 Ask/Custom 选择器。
179
191
  [完整差异与当前限制](skills/netizen-user-guide/references/user-guide.md#与-codex-appcli-的差异)
180
192
 
181
193
  <a id="用户指南-skill"></a>
@@ -15,10 +15,9 @@ projects:
15
15
  channel:
16
16
  securityMode: audit
17
17
 
18
- # Single-administrator management UI. It is enabled by default and listens on
19
- # all interfaces for direct access inside the trusted private network.
18
+ # Required management UI, using this app's Feishu administrator identities.
19
+ # It listens on all interfaces for direct access inside the trusted private network.
20
20
  adminWeb:
21
- enabled: true
22
21
  host: 0.0.0.0
23
22
  # Omit port for first-start allocation from 8787 through 8886. Once bound,
24
23
  # the selected port is written here and stays fixed on subsequent starts.
@@ -31,5 +30,7 @@ adminWeb:
31
30
  # The installer prepares this file; see docs/deployment.md for its JSON format.
32
31
  # An explicit override must be an absolute path. Lark CLI is optional.
33
32
  # NETIZEN_LARK_APP_CONFIG=/home/your-user/.netizen/lark-app/config.json
34
- # Admin Web supports only a canonical 32-byte base64url credential file.
35
- # NETIZEN_ADMIN_SECRET_FILE=/home/your-user/.netizen/credentials/admin-web-secret
33
+ # Setup binds the first administrator through Feishu login or --admin-open-id.
34
+ # Administrator membership: <NETIZEN_ROOT>/state/admin-users.json
35
+ # Private user credentials: <NETIZEN_ROOT>/credentials/admin-user-tokens.json
36
+ # Admin browser sessions stay in memory and require login after service restart.
@@ -1,3 +1,3 @@
1
1
  """Netizen: a thin Feishu Channel for native Codex Threads."""
2
2
 
3
- __version__ = "0.10.2"
3
+ __version__ = "0.11.1"
@@ -0,0 +1,340 @@
1
+ """One read-only, removable bridge for native account quota snapshots."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import re
8
+ from dataclasses import dataclass
9
+ from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
10
+ from typing import Any, Protocol
11
+
12
+ from openai_codex import AsyncCodex
13
+ from pydantic import ValidationError, field_validator
14
+
15
+ from .sdk_gap_adapter import (
16
+ SdkFacadeMigrationRequired,
17
+ SdkGapCapabilityUnavailable,
18
+ _generated_type,
19
+ _initialized_client,
20
+ _require_model_fields,
21
+ )
22
+
23
+
24
+ _CAPABILITY = "account-rate-limits"
25
+ _READ_METHOD = "account/rateLimits/read"
26
+ _READ_TIMEOUT_SECONDS = 10.0
27
+ _MAX_BUCKETS = 64
28
+ _MAX_LABEL_LENGTH = 256
29
+
30
+
31
+ class AccountRateLimitsUnavailable(RuntimeError):
32
+ """The response cannot supply a trustworthy quota projection."""
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class AccountRateLimitWindow:
37
+ used_percent: int
38
+ window_duration_mins: int | None
39
+ resets_at: int | None
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class AccountCredits:
44
+ has_credits: bool
45
+ unlimited: bool
46
+ balance: int | None
47
+
48
+
49
+ @dataclass(frozen=True, slots=True)
50
+ class AccountMonthlyCreditLimit:
51
+ limit: int
52
+ used: int
53
+ remaining_percent: int
54
+ resets_at: int
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class AccountRateLimitBucket:
59
+ limit_id: str | None
60
+ limit_name: str | None
61
+ primary: AccountRateLimitWindow | None
62
+ secondary: AccountRateLimitWindow | None
63
+ credits: AccountCredits | None = None
64
+ monthly_limit: AccountMonthlyCreditLimit | None = None
65
+
66
+
67
+ @dataclass(frozen=True, slots=True)
68
+ class AccountRateLimitsSnapshot:
69
+ buckets: tuple[AccountRateLimitBucket, ...]
70
+
71
+
72
+ class AccountRateLimits(Protocol):
73
+ async def read(self) -> AccountRateLimitsSnapshot: ...
74
+
75
+
76
+ def facade_migration_requirements() -> tuple[str, ...]:
77
+ """Require review when a candidate public account-quota facade appears."""
78
+
79
+ candidates = (
80
+ "rate_limits",
81
+ "rate_limits_read",
82
+ "account_rate_limits",
83
+ "account_rate_limits_read",
84
+ "get_account_rate_limits",
85
+ "read_account_rate_limits",
86
+ )
87
+ names = tuple(
88
+ f"AsyncCodex.{name}" for name in candidates if hasattr(AsyncCodex, name)
89
+ )
90
+ return (
91
+ (f"migration-required:{_CAPABILITY}:{','.join(names)}",) if names else ()
92
+ )
93
+
94
+
95
+ class AppServerAccountRateLimits:
96
+ """Read once through the same initialized SDK-owned client.
97
+
98
+ Timeout/cancellation stops waiting, not the SDK's underlying sync request.
99
+ This bridge never retries, resets quota, consumes notifications, or closes
100
+ the shared client. Late results remain owned by the SDK transport.
101
+ """
102
+
103
+ __slots__ = ("_client", "_params_model", "_response_model", "_bucket_model")
104
+
105
+ def __init__(self, codex: AsyncCodex) -> None:
106
+ if requirements := facade_migration_requirements():
107
+ raise SdkFacadeMigrationRequired("; ".join(requirements))
108
+ self._client = _initialized_client(codex, capability=_CAPABILITY)
109
+ self._params_model = _model(
110
+ "GetAccountRateLimitsParams",
111
+ {
112
+ "exclude_reset_credit_details": "excludeResetCreditDetails",
113
+ "supports_luna_reserve": "supportsLunaReserve",
114
+ },
115
+ )
116
+ self._response_model = _model(
117
+ "GetAccountRateLimitsResponse",
118
+ {
119
+ "account_id": "accountId",
120
+ "ordinary_usage_allowed": "ordinaryUsageAllowed",
121
+ "rate_limit_reset_credits": "rateLimitResetCredits",
122
+ "rate_limit_upsell": "rateLimitUpsell",
123
+ "rate_limits": "rateLimits",
124
+ "rate_limits_by_limit_id": "rateLimitsByLimitId",
125
+ },
126
+ )
127
+ self._bucket_model = _model(
128
+ "RateLimitSnapshot",
129
+ {
130
+ "credits": "credits",
131
+ "individual_limit": "individualLimit",
132
+ "limit_id": "limitId",
133
+ "limit_name": "limitName",
134
+ "normal_model_slug": "normalModelSlug",
135
+ "plan_type": "planType",
136
+ "primary": "primary",
137
+ "rate_limit_reached_type": "rateLimitReachedType",
138
+ "secondary": "secondary",
139
+ "spend_control_reached": "spendControlReached",
140
+ },
141
+ )
142
+ _model(
143
+ "RateLimitWindow",
144
+ {
145
+ "resets_at": "resetsAt",
146
+ "used_percent": "usedPercent",
147
+ "window_duration_mins": "windowDurationMins",
148
+ },
149
+ )
150
+ _model(
151
+ "CreditsSnapshot",
152
+ {
153
+ "balance": "balance",
154
+ "has_credits": "hasCredits",
155
+ "unlimited": "unlimited",
156
+ },
157
+ )
158
+ _model(
159
+ "SpendControlLimitSnapshot",
160
+ {
161
+ "limit": "limit",
162
+ "used": "used",
163
+ "remaining_percent": "remainingPercent",
164
+ "resets_at": "resetsAt",
165
+ },
166
+ )
167
+ self._response_model = _strict_response_model(
168
+ self._response_model, self._bucket_model
169
+ )
170
+
171
+ async def read(self) -> AccountRateLimitsSnapshot:
172
+ params = self._params_model(
173
+ excludeResetCreditDetails=True, supportsLunaReserve=False
174
+ )
175
+ try:
176
+ async with asyncio.timeout(_READ_TIMEOUT_SECONDS):
177
+ response = await self._client.request(
178
+ _READ_METHOD,
179
+ params.model_dump(by_alias=True),
180
+ response_model=self._response_model,
181
+ )
182
+ except ValidationError as error:
183
+ raise AccountRateLimitsUnavailable("Codex 额度响应格式无效。") from error
184
+ if not isinstance(response, self._response_model):
185
+ raise AccountRateLimitsUnavailable("Codex 额度响应类型无效。")
186
+ multiple = response.rate_limits_by_limit_id
187
+ if multiple is None:
188
+ buckets = (self._project_bucket(response.rate_limits),)
189
+ else:
190
+ if not isinstance(multiple, dict) or not 0 < len(multiple) <= _MAX_BUCKETS:
191
+ raise AccountRateLimitsUnavailable("Codex 额度分组为空或超出读取范围。")
192
+ projected = []
193
+ for key, raw in multiple.items():
194
+ limit_id = _label(key, required=True)
195
+ if not isinstance(raw, dict):
196
+ raise AccountRateLimitsUnavailable("Codex 额度分组格式无效。")
197
+ try:
198
+ # JSON strict validation preserves valid string enums while
199
+ # rejecting coercion of window strings/bools into numbers.
200
+ bucket = self._bucket_model.model_validate_json(
201
+ json.dumps(raw, allow_nan=False), strict=True
202
+ )
203
+ except (ValidationError, TypeError, ValueError) as error:
204
+ raise AccountRateLimitsUnavailable(
205
+ "Codex 额度分组格式无效。"
206
+ ) from error
207
+ projected.append(self._project_bucket(bucket, limit_id=limit_id))
208
+ buckets = tuple(projected)
209
+ if not any(
210
+ bucket.primary is not None or bucket.secondary is not None
211
+ or bucket.credits is not None or bucket.monthly_limit is not None
212
+ for bucket in buckets
213
+ ):
214
+ raise AccountRateLimitsUnavailable("Codex 尚未提供可用的额度数据。")
215
+ return AccountRateLimitsSnapshot(buckets)
216
+
217
+ def _project_bucket(
218
+ self, bucket: Any, *, limit_id: str | None = None
219
+ ) -> AccountRateLimitBucket:
220
+ if not isinstance(bucket, self._bucket_model):
221
+ raise AccountRateLimitsUnavailable("Codex 额度分组格式无效。")
222
+ supplied_id = _label(bucket.limit_id)
223
+ if limit_id is not None and supplied_id is not None and limit_id != supplied_id:
224
+ raise AccountRateLimitsUnavailable("Codex 额度分组标识不一致。")
225
+ return AccountRateLimitBucket(
226
+ limit_id=limit_id if limit_id is not None else supplied_id,
227
+ limit_name=_label(bucket.limit_name),
228
+ primary=_window(bucket.primary),
229
+ secondary=_window(bucket.secondary),
230
+ credits=_credits(bucket.credits),
231
+ monthly_limit=_monthly_limit(bucket.individual_limit),
232
+ )
233
+
234
+
235
+ def _model(name: str, aliases: dict[str, str]) -> type[Any]:
236
+ model = _generated_type(name, capability=_CAPABILITY)
237
+ _require_model_fields(model, capability=_CAPABILITY, aliases=aliases)
238
+ if not callable(getattr(model, "model_validate_json", None)):
239
+ raise SdkGapCapabilityUnavailable(f"{_CAPABILITY} model validation changed")
240
+ return model
241
+
242
+
243
+ def _strict_response_model(
244
+ response_model: type[Any], bucket_model: type[Any]
245
+ ) -> type[Any]:
246
+ # Inherit every field from the installed SDK. Its request decoder calls
247
+ # model_validate on the raw response, so validate legacy windows before
248
+ # Pydantic can coerce false, "0", or 0.0 into a known zero percent.
249
+ class StrictAccountRateLimitsResponse(response_model):
250
+ @field_validator("rate_limits", mode="before")
251
+ @classmethod
252
+ def _strict_legacy_bucket(cls, value: Any) -> Any:
253
+ if not isinstance(value, dict):
254
+ raise ValueError("expected a quota bucket object")
255
+ try:
256
+ return bucket_model.model_validate_json(
257
+ json.dumps(value, allow_nan=False), strict=True
258
+ )
259
+ except TypeError as error:
260
+ raise ValueError("invalid quota bucket object") from error
261
+
262
+ return StrictAccountRateLimitsResponse
263
+
264
+
265
+ def _label(value: Any, *, required: bool = False) -> str | None:
266
+ if value is None and not required:
267
+ return None
268
+ if (
269
+ not isinstance(value, str)
270
+ or not value
271
+ or value.strip() != value
272
+ or len(value) > _MAX_LABEL_LENGTH
273
+ or any(ord(char) < 32 or ord(char) == 127 for char in value)
274
+ ):
275
+ raise AccountRateLimitsUnavailable("Codex 额度分组名称或标识无效。")
276
+ return value
277
+
278
+
279
+ def _window(value: Any) -> AccountRateLimitWindow | None:
280
+ if value is None:
281
+ return None
282
+ used = _nonnegative_int(value.used_percent)
283
+ duration = (
284
+ None if value.window_duration_mins is None
285
+ else _nonnegative_int(value.window_duration_mins)
286
+ )
287
+ resets = None if value.resets_at is None else _nonnegative_int(value.resets_at)
288
+ if duration == 0:
289
+ raise AccountRateLimitsUnavailable("Codex 额度窗口时长无效。")
290
+ # Native i32 percentages can exceed 100; do not invent a capped usage value.
291
+ return AccountRateLimitWindow(used, duration, resets)
292
+
293
+
294
+ def _credits(value: Any) -> AccountCredits | None:
295
+ if value is None or not (value.unlimited or value.has_credits):
296
+ return None
297
+ # The native display treats a hidden or invalid balance as Available; it
298
+ # never exposes the raw string or infers requests from credit balances.
299
+ balance = _credit_amount(value.balance, positive=True)
300
+ return AccountCredits(value.has_credits, value.unlimited, balance)
301
+
302
+
303
+ def _monthly_limit(value: Any) -> AccountMonthlyCreditLimit | None:
304
+ if value is None:
305
+ return None
306
+ limit, used = _credit_amount(value.limit), _credit_amount(value.used)
307
+ if limit is None or used is None:
308
+ return None
309
+ # Native remainingPercent can fall outside the display range; clamp only
310
+ # while rendering, independently of the reported used/limit amounts.
311
+ remaining = value.remaining_percent
312
+ if isinstance(remaining, bool) or not isinstance(remaining, int):
313
+ raise AccountRateLimitsUnavailable("Codex 月度额度数值无效。")
314
+ return AccountMonthlyCreditLimit(
315
+ limit, used, remaining, _nonnegative_int(value.resets_at)
316
+ )
317
+
318
+
319
+ def _credit_amount(value: str | None, *, positive: bool = False) -> int | None:
320
+ if value is None:
321
+ return None
322
+ raw = value.strip()
323
+ if re.fullmatch(r"[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?", raw) is None:
324
+ return None
325
+ try:
326
+ amount = Decimal(raw)
327
+ except InvalidOperation:
328
+ return None
329
+ if (
330
+ not amount.is_finite() or amount < 0 or (positive and amount == 0)
331
+ or amount > 2**63 - 1
332
+ ):
333
+ return None
334
+ return int(amount.to_integral_value(rounding=ROUND_HALF_UP))
335
+
336
+
337
+ def _nonnegative_int(value: Any) -> int:
338
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0:
339
+ raise AccountRateLimitsUnavailable("Codex 额度窗口数值无效。")
340
+ return value