codex-autorunner 0.1.2__py3-none-any.whl → 1.0.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 (189) hide show
  1. codex_autorunner/__main__.py +4 -0
  2. codex_autorunner/agents/opencode/client.py +68 -35
  3. codex_autorunner/agents/opencode/logging.py +21 -5
  4. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  5. codex_autorunner/agents/opencode/runtime.py +118 -30
  6. codex_autorunner/agents/opencode/supervisor.py +36 -48
  7. codex_autorunner/agents/registry.py +136 -8
  8. codex_autorunner/api.py +25 -0
  9. codex_autorunner/bootstrap.py +16 -35
  10. codex_autorunner/cli.py +157 -139
  11. codex_autorunner/core/about_car.py +44 -32
  12. codex_autorunner/core/adapter_utils.py +21 -0
  13. codex_autorunner/core/app_server_logging.py +7 -3
  14. codex_autorunner/core/app_server_prompts.py +27 -260
  15. codex_autorunner/core/app_server_threads.py +15 -26
  16. codex_autorunner/core/codex_runner.py +6 -0
  17. codex_autorunner/core/config.py +390 -100
  18. codex_autorunner/core/docs.py +10 -2
  19. codex_autorunner/core/drafts.py +82 -0
  20. codex_autorunner/core/engine.py +278 -262
  21. codex_autorunner/core/flows/__init__.py +25 -0
  22. codex_autorunner/core/flows/controller.py +178 -0
  23. codex_autorunner/core/flows/definition.py +82 -0
  24. codex_autorunner/core/flows/models.py +75 -0
  25. codex_autorunner/core/flows/runtime.py +351 -0
  26. codex_autorunner/core/flows/store.py +485 -0
  27. codex_autorunner/core/flows/transition.py +133 -0
  28. codex_autorunner/core/flows/worker_process.py +242 -0
  29. codex_autorunner/core/hub.py +15 -9
  30. codex_autorunner/core/locks.py +4 -0
  31. codex_autorunner/core/prompt.py +15 -7
  32. codex_autorunner/core/redaction.py +29 -0
  33. codex_autorunner/core/review_context.py +5 -8
  34. codex_autorunner/core/run_index.py +6 -0
  35. codex_autorunner/core/runner_process.py +5 -2
  36. codex_autorunner/core/state.py +0 -88
  37. codex_autorunner/core/static_assets.py +55 -0
  38. codex_autorunner/core/supervisor_utils.py +67 -0
  39. codex_autorunner/core/update.py +20 -11
  40. codex_autorunner/core/update_runner.py +2 -0
  41. codex_autorunner/core/utils.py +29 -2
  42. codex_autorunner/discovery.py +2 -4
  43. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  44. codex_autorunner/flows/ticket_flow/definition.py +91 -0
  45. codex_autorunner/integrations/agents/__init__.py +27 -0
  46. codex_autorunner/integrations/agents/agent_backend.py +142 -0
  47. codex_autorunner/integrations/agents/codex_backend.py +307 -0
  48. codex_autorunner/integrations/agents/opencode_backend.py +325 -0
  49. codex_autorunner/integrations/agents/run_event.py +71 -0
  50. codex_autorunner/integrations/app_server/client.py +576 -92
  51. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  52. codex_autorunner/integrations/telegram/adapter.py +141 -167
  53. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  54. codex_autorunner/integrations/telegram/config.py +175 -0
  55. codex_autorunner/integrations/telegram/constants.py +16 -1
  56. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  57. codex_autorunner/integrations/telegram/doctor.py +47 -0
  58. codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
  59. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  60. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  61. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  62. codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
  63. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  64. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  65. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  66. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
  67. codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
  68. codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
  69. codex_autorunner/integrations/telegram/helpers.py +88 -16
  70. codex_autorunner/integrations/telegram/outbox.py +208 -37
  71. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  72. codex_autorunner/integrations/telegram/service.py +214 -40
  73. codex_autorunner/integrations/telegram/state.py +100 -2
  74. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
  75. codex_autorunner/integrations/telegram/transport.py +36 -3
  76. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  77. codex_autorunner/manifest.py +2 -0
  78. codex_autorunner/plugin_api.py +22 -0
  79. codex_autorunner/routes/__init__.py +23 -14
  80. codex_autorunner/routes/analytics.py +239 -0
  81. codex_autorunner/routes/base.py +81 -109
  82. codex_autorunner/routes/file_chat.py +836 -0
  83. codex_autorunner/routes/flows.py +980 -0
  84. codex_autorunner/routes/messages.py +459 -0
  85. codex_autorunner/routes/system.py +6 -1
  86. codex_autorunner/routes/usage.py +87 -0
  87. codex_autorunner/routes/workspace.py +271 -0
  88. codex_autorunner/server.py +2 -1
  89. codex_autorunner/static/agentControls.js +1 -0
  90. codex_autorunner/static/agentEvents.js +248 -0
  91. codex_autorunner/static/app.js +25 -22
  92. codex_autorunner/static/autoRefresh.js +29 -1
  93. codex_autorunner/static/bootstrap.js +1 -0
  94. codex_autorunner/static/bus.js +1 -0
  95. codex_autorunner/static/cache.js +1 -0
  96. codex_autorunner/static/constants.js +20 -4
  97. codex_autorunner/static/dashboard.js +162 -196
  98. codex_autorunner/static/diffRenderer.js +37 -0
  99. codex_autorunner/static/docChatCore.js +324 -0
  100. codex_autorunner/static/docChatStorage.js +65 -0
  101. codex_autorunner/static/docChatVoice.js +65 -0
  102. codex_autorunner/static/docEditor.js +133 -0
  103. codex_autorunner/static/env.js +1 -0
  104. codex_autorunner/static/eventSummarizer.js +166 -0
  105. codex_autorunner/static/fileChat.js +182 -0
  106. codex_autorunner/static/health.js +155 -0
  107. codex_autorunner/static/hub.js +41 -118
  108. codex_autorunner/static/index.html +787 -858
  109. codex_autorunner/static/liveUpdates.js +1 -0
  110. codex_autorunner/static/loader.js +1 -0
  111. codex_autorunner/static/messages.js +470 -0
  112. codex_autorunner/static/mobileCompact.js +2 -1
  113. codex_autorunner/static/settings.js +24 -211
  114. codex_autorunner/static/styles.css +7567 -3865
  115. codex_autorunner/static/tabs.js +28 -5
  116. codex_autorunner/static/terminal.js +14 -0
  117. codex_autorunner/static/terminalManager.js +34 -59
  118. codex_autorunner/static/ticketChatActions.js +333 -0
  119. codex_autorunner/static/ticketChatEvents.js +16 -0
  120. codex_autorunner/static/ticketChatStorage.js +16 -0
  121. codex_autorunner/static/ticketChatStream.js +264 -0
  122. codex_autorunner/static/ticketEditor.js +750 -0
  123. codex_autorunner/static/ticketVoice.js +9 -0
  124. codex_autorunner/static/tickets.js +1315 -0
  125. codex_autorunner/static/utils.js +32 -3
  126. codex_autorunner/static/voice.js +1 -0
  127. codex_autorunner/static/workspace.js +672 -0
  128. codex_autorunner/static/workspaceApi.js +53 -0
  129. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  130. codex_autorunner/tickets/__init__.py +20 -0
  131. codex_autorunner/tickets/agent_pool.py +377 -0
  132. codex_autorunner/tickets/files.py +85 -0
  133. codex_autorunner/tickets/frontmatter.py +55 -0
  134. codex_autorunner/tickets/lint.py +102 -0
  135. codex_autorunner/tickets/models.py +95 -0
  136. codex_autorunner/tickets/outbox.py +232 -0
  137. codex_autorunner/tickets/replies.py +179 -0
  138. codex_autorunner/tickets/runner.py +823 -0
  139. codex_autorunner/tickets/spec_ingest.py +77 -0
  140. codex_autorunner/web/app.py +269 -91
  141. codex_autorunner/web/middleware.py +3 -4
  142. codex_autorunner/web/schemas.py +89 -109
  143. codex_autorunner/web/static_assets.py +1 -44
  144. codex_autorunner/workspace/__init__.py +40 -0
  145. codex_autorunner/workspace/paths.py +319 -0
  146. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
  147. codex_autorunner-1.0.0.dist-info/RECORD +251 -0
  148. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
  149. codex_autorunner/agents/execution/policy.py +0 -292
  150. codex_autorunner/agents/factory.py +0 -52
  151. codex_autorunner/agents/orchestrator.py +0 -358
  152. codex_autorunner/core/doc_chat.py +0 -1446
  153. codex_autorunner/core/snapshot.py +0 -580
  154. codex_autorunner/integrations/github/chatops.py +0 -268
  155. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  156. codex_autorunner/routes/docs.py +0 -381
  157. codex_autorunner/routes/github.py +0 -327
  158. codex_autorunner/routes/runs.py +0 -250
  159. codex_autorunner/spec_ingest.py +0 -812
  160. codex_autorunner/static/docChatActions.js +0 -287
  161. codex_autorunner/static/docChatEvents.js +0 -300
  162. codex_autorunner/static/docChatRender.js +0 -205
  163. codex_autorunner/static/docChatStream.js +0 -361
  164. codex_autorunner/static/docs.js +0 -20
  165. codex_autorunner/static/docsClipboard.js +0 -69
  166. codex_autorunner/static/docsCrud.js +0 -257
  167. codex_autorunner/static/docsDocUpdates.js +0 -62
  168. codex_autorunner/static/docsDrafts.js +0 -16
  169. codex_autorunner/static/docsElements.js +0 -69
  170. codex_autorunner/static/docsInit.js +0 -285
  171. codex_autorunner/static/docsParse.js +0 -160
  172. codex_autorunner/static/docsSnapshot.js +0 -87
  173. codex_autorunner/static/docsSpecIngest.js +0 -263
  174. codex_autorunner/static/docsState.js +0 -127
  175. codex_autorunner/static/docsThreadRegistry.js +0 -44
  176. codex_autorunner/static/docsUi.js +0 -153
  177. codex_autorunner/static/docsVoice.js +0 -56
  178. codex_autorunner/static/github.js +0 -504
  179. codex_autorunner/static/logs.js +0 -678
  180. codex_autorunner/static/review.js +0 -157
  181. codex_autorunner/static/runs.js +0 -418
  182. codex_autorunner/static/snapshot.js +0 -124
  183. codex_autorunner/static/state.js +0 -94
  184. codex_autorunner/static/todoPreview.js +0 -27
  185. codex_autorunner/workspace.py +0 -16
  186. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  187. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
  188. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
  189. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,381 +0,0 @@
1
- """
2
- Document management routes: read/write docs and chat functionality.
3
- """
4
-
5
- from pathlib import Path
6
- from typing import Optional
7
-
8
- from fastapi import APIRouter, HTTPException, Request
9
- from fastapi.responses import StreamingResponse
10
-
11
- from ..core.doc_chat import (
12
- DocChatBusyError,
13
- DocChatConflictError,
14
- DocChatError,
15
- DocChatValidationError,
16
- _normalize_kind,
17
- )
18
- from ..core.snapshot import (
19
- SnapshotError,
20
- load_snapshot,
21
- load_snapshot_state,
22
- )
23
- from ..core.usage import (
24
- UsageError,
25
- default_codex_home,
26
- get_repo_usage_series_cached,
27
- get_repo_usage_summary_cached,
28
- parse_iso_datetime,
29
- )
30
- from ..core.utils import atomic_write
31
- from ..spec_ingest import (
32
- SpecIngestError,
33
- clear_work_docs,
34
- )
35
- from ..web.schemas import (
36
- DocChatPayload,
37
- DocContentRequest,
38
- DocsResponse,
39
- DocWriteResponse,
40
- IngestSpecRequest,
41
- IngestSpecResponse,
42
- RepoUsageResponse,
43
- SnapshotCreateResponse,
44
- SnapshotRequest,
45
- SnapshotResponse,
46
- UsageSeriesResponse,
47
- )
48
- from .shared import SSE_HEADERS
49
-
50
-
51
- def build_docs_routes() -> APIRouter:
52
- """Build routes for document management and chat."""
53
- router = APIRouter()
54
-
55
- @router.get("/api/docs", response_model=DocsResponse)
56
- def get_docs(request: Request):
57
- engine = request.app.state.engine
58
- return {
59
- "todo": engine.docs.read_doc("todo"),
60
- "progress": engine.docs.read_doc("progress"),
61
- "opinions": engine.docs.read_doc("opinions"),
62
- "spec": engine.docs.read_doc("spec"),
63
- "summary": engine.docs.read_doc("summary"),
64
- }
65
-
66
- @router.put("/api/docs/{kind}", response_model=DocWriteResponse)
67
- def put_doc(kind: str, payload: DocContentRequest, request: Request):
68
- engine = request.app.state.engine
69
- key = kind.lower()
70
- if key not in ("todo", "progress", "opinions", "spec", "summary"):
71
- raise HTTPException(status_code=400, detail="invalid doc kind")
72
- content = payload.content
73
- atomic_write(engine.config.doc_path(key), content)
74
- return {"kind": key, "content": content}
75
-
76
- @router.get("/api/snapshot", response_model=SnapshotResponse)
77
- def get_snapshot(request: Request):
78
- engine = request.app.state.engine
79
- content = load_snapshot(engine)
80
- state = load_snapshot_state(engine)
81
- return {"exists": bool(content), "content": content or "", "state": state or {}}
82
-
83
- @router.post("/api/snapshot", response_model=SnapshotCreateResponse)
84
- async def post_snapshot(
85
- request: Request, payload: Optional[SnapshotRequest] = None
86
- ):
87
- # Snapshot generation has a single default behavior now; we accept an
88
- # optional JSON object for backwards compatibility, but ignore any fields.
89
- snapshot_service = request.app.state.snapshot_service
90
- try:
91
- result = await snapshot_service.generate_snapshot()
92
- except SnapshotError as exc:
93
- raise HTTPException(status_code=400, detail=str(exc)) from exc
94
- except Exception as exc:
95
- raise HTTPException(status_code=500, detail=str(exc)) from exc
96
-
97
- return {
98
- "content": result.content,
99
- "truncated": result.truncated,
100
- "state": result.state,
101
- }
102
-
103
- async def _handle_doc_chat_request(
104
- request: Request,
105
- *,
106
- kind: Optional[str],
107
- payload: Optional[DocChatPayload],
108
- ):
109
- doc_chat = request.app.state.doc_chat
110
- repo_blocked = doc_chat.repo_blocked_reason()
111
- if repo_blocked:
112
- raise HTTPException(status_code=409, detail=repo_blocked)
113
- try:
114
- payload_dict = payload.model_dump(exclude_none=True) if payload else None
115
- doc_req = doc_chat.parse_request(payload_dict, kind=kind)
116
- except DocChatValidationError as exc:
117
- raise HTTPException(status_code=400, detail=str(exc)) from exc
118
-
119
- if doc_chat.doc_busy():
120
- raise HTTPException(
121
- status_code=409,
122
- detail="Doc chat already running",
123
- )
124
-
125
- if doc_req.stream:
126
- return StreamingResponse(
127
- doc_chat.stream(doc_req),
128
- media_type="text/event-stream",
129
- headers=SSE_HEADERS,
130
- )
131
-
132
- try:
133
- async with doc_chat.doc_lock():
134
- result = await doc_chat.execute(doc_req)
135
- except DocChatBusyError as exc:
136
- raise HTTPException(status_code=409, detail=str(exc)) from exc
137
-
138
- if result.get("status") != "ok":
139
- detail = result.get("detail") or "Doc chat failed"
140
- raise HTTPException(status_code=500, detail=detail)
141
- return result
142
-
143
- @router.post("/api/docs/chat")
144
- async def chat_docs(request: Request, payload: Optional[DocChatPayload] = None):
145
- return await _handle_doc_chat_request(request, kind=None, payload=payload)
146
-
147
- @router.post("/api/docs/{kind}/chat")
148
- async def chat_doc(
149
- kind: str, request: Request, payload: Optional[DocChatPayload] = None
150
- ):
151
- return await _handle_doc_chat_request(request, kind=kind, payload=payload)
152
-
153
- @router.post("/api/docs/chat/interrupt")
154
- async def interrupt_chat(request: Request):
155
- doc_chat = request.app.state.doc_chat
156
- try:
157
- return await doc_chat.interrupt()
158
- except DocChatValidationError as exc:
159
- raise HTTPException(status_code=400, detail=str(exc)) from exc
160
-
161
- @router.post("/api/docs/{kind}/chat/interrupt")
162
- async def interrupt_chat_kind(kind: str, request: Request):
163
- doc_chat = request.app.state.doc_chat
164
- try:
165
- return await doc_chat.interrupt(kind)
166
- except DocChatValidationError as exc:
167
- raise HTTPException(status_code=400, detail=str(exc)) from exc
168
-
169
- @router.post("/api/docs/{kind}/chat/apply")
170
- async def apply_chat_patch(kind: str, request: Request):
171
- doc_chat = request.app.state.doc_chat
172
- key = _normalize_kind(kind)
173
- repo_blocked = doc_chat.repo_blocked_reason()
174
- if repo_blocked:
175
- raise HTTPException(status_code=409, detail=repo_blocked)
176
-
177
- try:
178
- async with doc_chat.doc_lock(key):
179
- pending = doc_chat.pending_patch(key)
180
- content = doc_chat.apply_saved_patch(key)
181
- except DocChatBusyError as exc:
182
- raise HTTPException(status_code=409, detail=str(exc)) from exc
183
- except DocChatConflictError as exc:
184
- raise HTTPException(status_code=409, detail=str(exc)) from exc
185
- except DocChatError as exc:
186
- raise HTTPException(status_code=500, detail=str(exc)) from exc
187
- return {
188
- "status": "ok",
189
- "kind": key,
190
- "content": content,
191
- "agent_message": (pending or {}).get("agent_message")
192
- or f"Updated {key.upper()} via doc chat.",
193
- "created_at": (pending or {}).get("created_at"),
194
- "base_hash": (pending or {}).get("base_hash"),
195
- }
196
-
197
- @router.post("/api/docs/{kind}/chat/discard")
198
- async def discard_chat_patch(kind: str, request: Request):
199
- doc_chat = request.app.state.doc_chat
200
- key = _normalize_kind(kind)
201
- repo_blocked = doc_chat.repo_blocked_reason()
202
- if repo_blocked:
203
- raise HTTPException(status_code=409, detail=repo_blocked)
204
- try:
205
- async with doc_chat.doc_lock(key):
206
- content = doc_chat.discard_patch(key)
207
- except DocChatError as exc:
208
- raise HTTPException(status_code=500, detail=str(exc)) from exc
209
- return {"status": "ok", "kind": key, "content": content}
210
-
211
- @router.get("/api/docs/{kind}/chat/pending")
212
- async def pending_chat_patch(kind: str, request: Request):
213
- doc_chat = request.app.state.doc_chat
214
- key = _normalize_kind(kind)
215
- repo_blocked = doc_chat.repo_blocked_reason()
216
- if repo_blocked:
217
- raise HTTPException(status_code=409, detail=repo_blocked)
218
- pending = doc_chat.pending_patch(key)
219
- if not pending:
220
- raise HTTPException(status_code=404, detail="No pending patch")
221
- return pending
222
-
223
- @router.post("/api/ingest-spec", response_model=IngestSpecResponse)
224
- async def ingest_spec(
225
- request: Request, payload: Optional[IngestSpecRequest] = None
226
- ):
227
- engine = request.app.state.engine
228
- repo_blocked = engine.repo_busy_reason()
229
- if repo_blocked:
230
- raise HTTPException(status_code=409, detail=repo_blocked)
231
- spec_ingest = request.app.state.spec_ingest
232
- force = False
233
- spec_override: Optional[Path] = None
234
- message: Optional[str] = None
235
- agent: Optional[str] = None
236
- model: Optional[str] = None
237
- reasoning: Optional[str] = None
238
- if payload:
239
- force = payload.force
240
- if payload.spec_path:
241
- spec_override = Path(str(payload.spec_path))
242
- message = payload.message
243
- agent = payload.agent
244
- model = payload.model
245
- reasoning = payload.reasoning
246
- try:
247
- docs = await spec_ingest.execute(
248
- force=force,
249
- spec_path=spec_override,
250
- message=message,
251
- agent=agent,
252
- model=model,
253
- reasoning=reasoning,
254
- )
255
- except SpecIngestError as exc:
256
- raise HTTPException(status_code=400, detail=str(exc)) from exc
257
- return docs
258
-
259
- @router.post("/api/ingest-spec/interrupt", response_model=IngestSpecResponse)
260
- async def ingest_spec_interrupt(request: Request):
261
- spec_ingest = request.app.state.spec_ingest
262
- try:
263
- docs = await spec_ingest.interrupt()
264
- except SpecIngestError as exc:
265
- raise HTTPException(status_code=400, detail=str(exc)) from exc
266
- return docs
267
-
268
- @router.get("/api/ingest-spec/pending", response_model=IngestSpecResponse)
269
- def ingest_spec_pending(request: Request):
270
- engine = request.app.state.engine
271
- repo_blocked = engine.repo_busy_reason()
272
- if repo_blocked:
273
- raise HTTPException(status_code=409, detail=repo_blocked)
274
- spec_ingest = request.app.state.spec_ingest
275
- try:
276
- pending = spec_ingest.pending_patch()
277
- if not pending:
278
- raise HTTPException(
279
- status_code=404, detail="No pending spec ingest patch"
280
- )
281
- return pending
282
- except SpecIngestError as exc:
283
- raise HTTPException(status_code=400, detail=str(exc)) from exc
284
-
285
- @router.post("/api/ingest-spec/apply", response_model=IngestSpecResponse)
286
- def ingest_spec_apply(request: Request):
287
- engine = request.app.state.engine
288
- repo_blocked = engine.repo_busy_reason()
289
- if repo_blocked:
290
- raise HTTPException(status_code=409, detail=repo_blocked)
291
- spec_ingest = request.app.state.spec_ingest
292
- try:
293
- return spec_ingest.apply_patch()
294
- except SpecIngestError as exc:
295
- raise HTTPException(status_code=400, detail=str(exc)) from exc
296
-
297
- @router.post("/api/ingest-spec/discard", response_model=IngestSpecResponse)
298
- def ingest_spec_discard(request: Request):
299
- engine = request.app.state.engine
300
- repo_blocked = engine.repo_busy_reason()
301
- if repo_blocked:
302
- raise HTTPException(status_code=409, detail=repo_blocked)
303
- spec_ingest = request.app.state.spec_ingest
304
- try:
305
- return spec_ingest.discard_patch()
306
- except SpecIngestError as exc:
307
- raise HTTPException(status_code=400, detail=str(exc)) from exc
308
-
309
- @router.post("/api/docs/clear", response_model=DocsResponse)
310
- def clear_docs(request: Request):
311
- engine = request.app.state.engine
312
- try:
313
- docs = clear_work_docs(engine)
314
- docs["spec"] = engine.docs.read_doc("spec")
315
- docs["summary"] = engine.docs.read_doc("summary")
316
- except Exception as exc:
317
- raise HTTPException(status_code=500, detail=str(exc)) from exc
318
- return docs
319
-
320
- @router.get("/api/usage", response_model=RepoUsageResponse)
321
- def get_usage(
322
- request: Request, since: Optional[str] = None, until: Optional[str] = None
323
- ):
324
- engine = request.app.state.engine
325
- try:
326
- since_dt = parse_iso_datetime(since)
327
- until_dt = parse_iso_datetime(until)
328
- except UsageError as exc:
329
- raise HTTPException(status_code=400, detail=str(exc)) from exc
330
- summary, status = get_repo_usage_summary_cached(
331
- engine.repo_root,
332
- default_codex_home(),
333
- since=since_dt,
334
- until=until_dt,
335
- )
336
- return {
337
- "mode": "repo",
338
- "repo": str(engine.repo_root),
339
- "codex_home": str(default_codex_home()),
340
- "since": since,
341
- "until": until,
342
- "status": status,
343
- **summary.to_dict(),
344
- }
345
-
346
- @router.get("/api/usage/series", response_model=UsageSeriesResponse)
347
- def get_usage_series(
348
- request: Request,
349
- since: Optional[str] = None,
350
- until: Optional[str] = None,
351
- bucket: str = "day",
352
- segment: str = "none",
353
- ):
354
- engine = request.app.state.engine
355
- try:
356
- since_dt = parse_iso_datetime(since)
357
- until_dt = parse_iso_datetime(until)
358
- except UsageError as exc:
359
- raise HTTPException(status_code=400, detail=str(exc)) from exc
360
- try:
361
- series, status = get_repo_usage_series_cached(
362
- engine.repo_root,
363
- default_codex_home(),
364
- since=since_dt,
365
- until=until_dt,
366
- bucket=bucket,
367
- segment=segment,
368
- )
369
- except UsageError as exc:
370
- raise HTTPException(status_code=400, detail=str(exc)) from exc
371
- return {
372
- "mode": "repo",
373
- "repo": str(engine.repo_root),
374
- "codex_home": str(default_codex_home()),
375
- "since": since,
376
- "until": until,
377
- "status": status,
378
- **series,
379
- }
380
-
381
- return router