free-claude-code 6.2.64__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 (429) hide show
  1. free_claude_code/__init__.py +1 -0
  2. free_claude_code/api/__init__.py +1 -0
  3. free_claude_code/api/admin_cache.py +49 -0
  4. free_claude_code/api/admin_routes.py +501 -0
  5. free_claude_code/api/admin_security.py +67 -0
  6. free_claude_code/api/admin_static/admin.css +980 -0
  7. free_claude_code/api/admin_static/admin.js +1934 -0
  8. free_claude_code/api/admin_static/code_sessions.css +109 -0
  9. free_claude_code/api/admin_static/code_sessions.js +1693 -0
  10. free_claude_code/api/admin_static/form_controls.js +15 -0
  11. free_claude_code/api/admin_static/index.html +201 -0
  12. free_claude_code/api/admin_static/model_combobox.js +194 -0
  13. free_claude_code/api/admin_static/providers/LICENSE-llamacpp.txt +21 -0
  14. free_claude_code/api/admin_static/providers/LICENSE-lobe-icons.txt +21 -0
  15. free_claude_code/api/admin_static/providers/SOURCES.md +56 -0
  16. free_claude_code/api/admin_static/providers/agnesai.svg +1 -0
  17. free_claude_code/api/admin_static/providers/azure-color.svg +1 -0
  18. free_claude_code/api/admin_static/providers/bedrock-color.svg +1 -0
  19. free_claude_code/api/admin_static/providers/cerebras-color.svg +1 -0
  20. free_claude_code/api/admin_static/providers/chutes.png +0 -0
  21. free_claude_code/api/admin_static/providers/cline.svg +1 -0
  22. free_claude_code/api/admin_static/providers/cloudflare-color.svg +1 -0
  23. free_claude_code/api/admin_static/providers/cohere-color.svg +1 -0
  24. free_claude_code/api/admin_static/providers/deepinfra-color.svg +1 -0
  25. free_claude_code/api/admin_static/providers/deepseek-color.svg +1 -0
  26. free_claude_code/api/admin_static/providers/experiential.svg +9 -0
  27. free_claude_code/api/admin_static/providers/featherless-color.svg +1 -0
  28. free_claude_code/api/admin_static/providers/fireworks-color.svg +1 -0
  29. free_claude_code/api/admin_static/providers/gemini-color.svg +1 -0
  30. free_claude_code/api/admin_static/providers/githubcopilot.svg +1 -0
  31. free_claude_code/api/admin_static/providers/groq.svg +1 -0
  32. free_claude_code/api/admin_static/providers/huggingface-color.svg +1 -0
  33. free_claude_code/api/admin_static/providers/kilocode.svg +1 -0
  34. free_claude_code/api/admin_static/providers/kimi-color.svg +1 -0
  35. free_claude_code/api/admin_static/providers/lightning.png +0 -0
  36. free_claude_code/api/admin_static/providers/llamacpp.svg +77 -0
  37. free_claude_code/api/admin_static/providers/llm7.png +0 -0
  38. free_claude_code/api/admin_static/providers/lmstudio.svg +1 -0
  39. free_claude_code/api/admin_static/providers/minimax-color.svg +1 -0
  40. free_claude_code/api/admin_static/providers/mistral-color.svg +1 -0
  41. free_claude_code/api/admin_static/providers/nararoute.png +0 -0
  42. free_claude_code/api/admin_static/providers/nebius.svg +1 -0
  43. free_claude_code/api/admin_static/providers/novita-color.svg +1 -0
  44. free_claude_code/api/admin_static/providers/nvidia-color.svg +1 -0
  45. free_claude_code/api/admin_static/providers/ollama.svg +1 -0
  46. free_claude_code/api/admin_static/providers/openai.svg +1 -0
  47. free_claude_code/api/admin_static/providers/opencode.svg +1 -0
  48. free_claude_code/api/admin_static/providers/openrouter.svg +1 -0
  49. free_claude_code/api/admin_static/providers/poolside-color.svg +1 -0
  50. free_claude_code/api/admin_static/providers/qwen-color.svg +1 -0
  51. free_claude_code/api/admin_static/providers/sambanova-color.svg +1 -0
  52. free_claude_code/api/admin_static/providers/scaleway.svg +11 -0
  53. free_claude_code/api/admin_static/providers/siliconcloud-color.svg +1 -0
  54. free_claude_code/api/admin_static/providers/together-color.svg +1 -0
  55. free_claude_code/api/admin_static/providers/tokenrouter.png +0 -0
  56. free_claude_code/api/admin_static/providers/vercel.svg +1 -0
  57. free_claude_code/api/admin_static/providers/vertexai-color.svg +1 -0
  58. free_claude_code/api/admin_static/providers/wafer.png +0 -0
  59. free_claude_code/api/admin_static/providers/wandb.png +0 -0
  60. free_claude_code/api/admin_static/providers/xai.svg +1 -0
  61. free_claude_code/api/admin_static/providers/zai.svg +1 -0
  62. free_claude_code/api/admin_static/providers/zenmux.svg +1 -0
  63. free_claude_code/api/admin_static/session_layout.css +552 -0
  64. free_claude_code/api/admin_static/session_ui.js +202 -0
  65. free_claude_code/api/app.py +149 -0
  66. free_claude_code/api/code_sessions_routes.py +386 -0
  67. free_claude_code/api/command_utils.py +164 -0
  68. free_claude_code/api/dependencies.py +128 -0
  69. free_claude_code/api/detection.py +185 -0
  70. free_claude_code/api/handlers/__init__.py +7 -0
  71. free_claude_code/api/handlers/classifier_response.py +66 -0
  72. free_claude_code/api/handlers/messages.py +359 -0
  73. free_claude_code/api/handlers/responses.py +163 -0
  74. free_claude_code/api/handlers/token_count.py +87 -0
  75. free_claude_code/api/markdown.py +71 -0
  76. free_claude_code/api/model_catalog.py +282 -0
  77. free_claude_code/api/optimization_handlers.py +162 -0
  78. free_claude_code/api/ports.py +97 -0
  79. free_claude_code/api/request_errors.py +100 -0
  80. free_claude_code/api/request_ids.py +100 -0
  81. free_claude_code/api/request_lifetime.py +130 -0
  82. free_claude_code/api/response_streams.py +422 -0
  83. free_claude_code/api/routes.py +269 -0
  84. free_claude_code/api/validation_log.py +46 -0
  85. free_claude_code/application/__init__.py +1 -0
  86. free_claude_code/application/code_sessions/__init__.py +33 -0
  87. free_claude_code/application/code_sessions/models.py +227 -0
  88. free_claude_code/application/code_sessions/ports.py +197 -0
  89. free_claude_code/application/code_sessions/service.py +1123 -0
  90. free_claude_code/application/code_sessions/state.py +659 -0
  91. free_claude_code/application/connected_accounts.py +96 -0
  92. free_claude_code/application/errors.py +41 -0
  93. free_claude_code/application/execution.py +467 -0
  94. free_claude_code/application/model_catalog.py +95 -0
  95. free_claude_code/application/model_metadata.py +26 -0
  96. free_claude_code/application/ports.py +127 -0
  97. free_claude_code/application/readiness.py +43 -0
  98. free_claude_code/application/reasoning.py +134 -0
  99. free_claude_code/application/routing.py +291 -0
  100. free_claude_code/application/session_events.py +140 -0
  101. free_claude_code/application/web_tools/__init__.py +1 -0
  102. free_claude_code/application/web_tools/ports.py +35 -0
  103. free_claude_code/application/web_tools/request.py +348 -0
  104. free_claude_code/application/web_tools/service.py +481 -0
  105. free_claude_code/assets/app-icon.icns +0 -0
  106. free_claude_code/assets/app-icon.ico +0 -0
  107. free_claude_code/assets/app-icon.png +0 -0
  108. free_claude_code/assets/app-icon.svg +14 -0
  109. free_claude_code/cli/__init__.py +5 -0
  110. free_claude_code/cli/commands.py +379 -0
  111. free_claude_code/cli/desktop.py +156 -0
  112. free_claude_code/cli/desktop_assets.py +31 -0
  113. free_claude_code/cli/desktop_entrypoint.py +29 -0
  114. free_claude_code/cli/desktop_tray.py +67 -0
  115. free_claude_code/cli/entrypoints.py +25 -0
  116. free_claude_code/cli/launchers/__init__.py +1 -0
  117. free_claude_code/cli/launchers/aider.py +53 -0
  118. free_claude_code/cli/launchers/aider_config.py +80 -0
  119. free_claude_code/cli/launchers/catalog_http.py +134 -0
  120. free_claude_code/cli/launchers/claude.py +34 -0
  121. free_claude_code/cli/launchers/cline.py +60 -0
  122. free_claude_code/cli/launchers/cline_config.py +121 -0
  123. free_claude_code/cli/launchers/codex.py +47 -0
  124. free_claude_code/cli/launchers/common.py +103 -0
  125. free_claude_code/cli/launchers/dsh.py +69 -0
  126. free_claude_code/cli/launchers/dsh_config.py +124 -0
  127. free_claude_code/cli/launchers/grok.py +116 -0
  128. free_claude_code/cli/launchers/hermes.py +85 -0
  129. free_claude_code/cli/launchers/hermes_config.py +134 -0
  130. free_claude_code/cli/launchers/muse.py +68 -0
  131. free_claude_code/cli/launchers/opencode.py +83 -0
  132. free_claude_code/cli/launchers/opencode_config.py +107 -0
  133. free_claude_code/cli/launchers/pi.py +70 -0
  134. free_claude_code/cli/launchers/pi_extension.ts +190 -0
  135. free_claude_code/cli/launchers/runner.py +155 -0
  136. free_claude_code/cli/local_http.py +12 -0
  137. free_claude_code/cli/managed/__init__.py +6 -0
  138. free_claude_code/cli/managed/claude.py +225 -0
  139. free_claude_code/cli/managed/diagnostics.py +47 -0
  140. free_claude_code/cli/managed/manager.py +207 -0
  141. free_claude_code/cli/managed/session.py +293 -0
  142. free_claude_code/cli/process_registry.py +76 -0
  143. free_claude_code/cli/server_socket.py +71 -0
  144. free_claude_code/cli/uvicorn_server.py +41 -0
  145. free_claude_code/config/__init__.py +10 -0
  146. free_claude_code/config/admin/__init__.py +1 -0
  147. free_claude_code/config/admin/manifest.py +610 -0
  148. free_claude_code/config/admin/persistence.py +202 -0
  149. free_claude_code/config/admin/provider_manifest.py +443 -0
  150. free_claude_code/config/admin/specs.py +77 -0
  151. free_claude_code/config/admin/state.py +18 -0
  152. free_claude_code/config/admin/status.py +116 -0
  153. free_claude_code/config/admin/validation.py +40 -0
  154. free_claude_code/config/admin/values.py +115 -0
  155. free_claude_code/config/constants.py +9 -0
  156. free_claude_code/config/env_files.py +74 -0
  157. free_claude_code/config/env_migrations.py +457 -0
  158. free_claude_code/config/loader.py +196 -0
  159. free_claude_code/config/logging_config.py +197 -0
  160. free_claude_code/config/model_refs.py +118 -0
  161. free_claude_code/config/nim.py +121 -0
  162. free_claude_code/config/paths.py +104 -0
  163. free_claude_code/config/provider_catalog.py +746 -0
  164. free_claude_code/config/provider_proxies.py +44 -0
  165. free_claude_code/config/reasoning.py +24 -0
  166. free_claude_code/config/server_urls.py +55 -0
  167. free_claude_code/config/settings.py +812 -0
  168. free_claude_code/core/__init__.py +15 -0
  169. free_claude_code/core/anthropic/__init__.py +99 -0
  170. free_claude_code/core/anthropic/content.py +31 -0
  171. free_claude_code/core/anthropic/conversion.py +876 -0
  172. free_claude_code/core/anthropic/errors.py +97 -0
  173. free_claude_code/core/anthropic/image_sources.py +77 -0
  174. free_claude_code/core/anthropic/models.py +193 -0
  175. free_claude_code/core/anthropic/native.py +184 -0
  176. free_claude_code/core/anthropic/native_stream.py +306 -0
  177. free_claude_code/core/anthropic/request_serialization.py +57 -0
  178. free_claude_code/core/anthropic/request_snapshot.py +35 -0
  179. free_claude_code/core/anthropic/server_tool_sse.py +234 -0
  180. free_claude_code/core/anthropic/server_tool_types.py +9 -0
  181. free_claude_code/core/anthropic/sse_aggregation.py +144 -0
  182. free_claude_code/core/anthropic/stream_contracts.py +208 -0
  183. free_claude_code/core/anthropic/streaming/__init__.py +43 -0
  184. free_claude_code/core/anthropic/streaming/decoder.py +49 -0
  185. free_claude_code/core/anthropic/streaming/emitter.py +62 -0
  186. free_claude_code/core/anthropic/streaming/ledger.py +538 -0
  187. free_claude_code/core/anthropic/streaming/recovery.py +211 -0
  188. free_claude_code/core/anthropic/thinking.py +140 -0
  189. free_claude_code/core/anthropic/tokens.py +111 -0
  190. free_claude_code/core/anthropic/tool_results.py +63 -0
  191. free_claude_code/core/anthropic/tool_schema.py +94 -0
  192. free_claude_code/core/anthropic/tools.py +493 -0
  193. free_claude_code/core/anthropic/usage.py +38 -0
  194. free_claude_code/core/anthropic/utils.py +9 -0
  195. free_claude_code/core/async_iterators.py +26 -0
  196. free_claude_code/core/async_tasks.py +26 -0
  197. free_claude_code/core/data/README.md +12 -0
  198. free_claude_code/core/data/TIKTOKEN-LICENSE +21 -0
  199. free_claude_code/core/data/cl100k_base.tiktoken +100256 -0
  200. free_claude_code/core/diagnostics.py +319 -0
  201. free_claude_code/core/failures.py +50 -0
  202. free_claude_code/core/gateway_model_ids.py +73 -0
  203. free_claude_code/core/history_replay.py +561 -0
  204. free_claude_code/core/interprocess_lock.py +90 -0
  205. free_claude_code/core/json_types.py +7 -0
  206. free_claude_code/core/model_capabilities.py +10 -0
  207. free_claude_code/core/openai_chat.py +54 -0
  208. free_claude_code/core/openai_responses/__init__.py +90 -0
  209. free_claude_code/core/openai_responses/chat_request.py +747 -0
  210. free_claude_code/core/openai_responses/errors.py +78 -0
  211. free_claude_code/core/openai_responses/events.py +82 -0
  212. free_claude_code/core/openai_responses/ids.py +32 -0
  213. free_claude_code/core/openai_responses/items.py +35 -0
  214. free_claude_code/core/openai_responses/messages_request.py +624 -0
  215. free_claude_code/core/openai_responses/messages_stream.py +404 -0
  216. free_claude_code/core/openai_responses/models.py +33 -0
  217. free_claude_code/core/openai_responses/native.py +147 -0
  218. free_claude_code/core/openai_responses/provider_input.py +409 -0
  219. free_claude_code/core/openai_responses/provider_stream.py +282 -0
  220. free_claude_code/core/openai_responses/reasoning.py +73 -0
  221. free_claude_code/core/openai_responses/streaming/__init__.py +1 -0
  222. free_claude_code/core/openai_responses/streaming/blocks.py +36 -0
  223. free_claude_code/core/openai_responses/streaming/completion.py +169 -0
  224. free_claude_code/core/openai_responses/streaming/error_mapping.py +28 -0
  225. free_claude_code/core/openai_responses/streaming/event_builders.py +198 -0
  226. free_claude_code/core/openai_responses/streaming/ledger.py +96 -0
  227. free_claude_code/core/openai_responses/tokens.py +133 -0
  228. free_claude_code/core/openai_responses/tool_adaptation.py +828 -0
  229. free_claude_code/core/openai_responses/tool_search.py +204 -0
  230. free_claude_code/core/openai_responses/tools.py +169 -0
  231. free_claude_code/core/openai_tool_names.py +178 -0
  232. free_claude_code/core/rate_limit.py +72 -0
  233. free_claude_code/core/reasoning.py +136 -0
  234. free_claude_code/core/token_estimation.py +76 -0
  235. free_claude_code/core/tool_schema_patterns.py +168 -0
  236. free_claude_code/core/trace.py +197 -0
  237. free_claude_code/core/version.py +15 -0
  238. free_claude_code/core/web_tools.py +17 -0
  239. free_claude_code/core/windows_dpi.py +27 -0
  240. free_claude_code/harnesses/__init__.py +1 -0
  241. free_claude_code/harnesses/claude.py +42 -0
  242. free_claude_code/harnesses/claude_desktop_integration.py +418 -0
  243. free_claude_code/harnesses/claude_integration.py +167 -0
  244. free_claude_code/harnesses/codex.py +94 -0
  245. free_claude_code/harnesses/codex_integration.py +147 -0
  246. free_claude_code/harnesses/codex_model_catalog.py +109 -0
  247. free_claude_code/harnesses/config_file.py +33 -0
  248. free_claude_code/harnesses/environment.py +78 -0
  249. free_claude_code/harnesses/jetbrains_acp_integration.py +242 -0
  250. free_claude_code/harnesses/launch.py +21 -0
  251. free_claude_code/harnesses/resources.py +42 -0
  252. free_claude_code/messaging/__init__.py +16 -0
  253. free_claude_code/messaging/cli_event_constants.py +67 -0
  254. free_claude_code/messaging/command_context.py +99 -0
  255. free_claude_code/messaging/command_dispatcher.py +31 -0
  256. free_claude_code/messaging/commands.py +182 -0
  257. free_claude_code/messaging/event_parser.py +184 -0
  258. free_claude_code/messaging/limiter.py +342 -0
  259. free_claude_code/messaging/managed_protocols.py +35 -0
  260. free_claude_code/messaging/models.py +57 -0
  261. free_claude_code/messaging/node_event_pipeline.py +120 -0
  262. free_claude_code/messaging/node_runner.py +408 -0
  263. free_claude_code/messaging/platforms/__init__.py +20 -0
  264. free_claude_code/messaging/platforms/discord.py +319 -0
  265. free_claude_code/messaging/platforms/discord_inbound.py +112 -0
  266. free_claude_code/messaging/platforms/discord_io.py +167 -0
  267. free_claude_code/messaging/platforms/factory.py +109 -0
  268. free_claude_code/messaging/platforms/outbox.py +143 -0
  269. free_claude_code/messaging/platforms/ports.py +99 -0
  270. free_claude_code/messaging/platforms/telegram.py +300 -0
  271. free_claude_code/messaging/platforms/telegram_inbound.py +133 -0
  272. free_claude_code/messaging/platforms/telegram_io.py +287 -0
  273. free_claude_code/messaging/platforms/voice_flow.py +388 -0
  274. free_claude_code/messaging/rendering/__init__.py +3 -0
  275. free_claude_code/messaging/rendering/discord_markdown.py +318 -0
  276. free_claude_code/messaging/rendering/markdown_tables.py +47 -0
  277. free_claude_code/messaging/rendering/profiles.py +53 -0
  278. free_claude_code/messaging/rendering/telegram_markdown.py +331 -0
  279. free_claude_code/messaging/safe_diagnostics.py +15 -0
  280. free_claude_code/messaging/session/__init__.py +5 -0
  281. free_claude_code/messaging/session/managed_message_log.py +135 -0
  282. free_claude_code/messaging/session/persistence.py +161 -0
  283. free_claude_code/messaging/session/store.py +162 -0
  284. free_claude_code/messaging/transcript/__init__.py +6 -0
  285. free_claude_code/messaging/transcript/buffer.py +220 -0
  286. free_claude_code/messaging/transcript/context.py +18 -0
  287. free_claude_code/messaging/transcript/renderer.py +65 -0
  288. free_claude_code/messaging/transcript/segments.py +176 -0
  289. free_claude_code/messaging/transcript/subagents.py +108 -0
  290. free_claude_code/messaging/transcription.py +132 -0
  291. free_claude_code/messaging/trees/__init__.py +41 -0
  292. free_claude_code/messaging/trees/graph.py +250 -0
  293. free_claude_code/messaging/trees/identity.py +16 -0
  294. free_claude_code/messaging/trees/manager.py +587 -0
  295. free_claude_code/messaging/trees/node.py +56 -0
  296. free_claude_code/messaging/trees/processor.py +272 -0
  297. free_claude_code/messaging/trees/queue.py +47 -0
  298. free_claude_code/messaging/trees/repository.py +135 -0
  299. free_claude_code/messaging/trees/runtime.py +487 -0
  300. free_claude_code/messaging/trees/snapshot.py +221 -0
  301. free_claude_code/messaging/trees/transitions.py +174 -0
  302. free_claude_code/messaging/turn_intake.py +241 -0
  303. free_claude_code/messaging/ui_updates.py +99 -0
  304. free_claude_code/messaging/voice.py +376 -0
  305. free_claude_code/messaging/workflow.py +714 -0
  306. free_claude_code/providers/__init__.py +12 -0
  307. free_claude_code/providers/admission.py +942 -0
  308. free_claude_code/providers/anthropic_messages/__init__.py +1 -0
  309. free_claude_code/providers/anthropic_messages/request_policy.py +162 -0
  310. free_claude_code/providers/anthropic_messages/transport.py +499 -0
  311. free_claude_code/providers/base.py +73 -0
  312. free_claude_code/providers/cloudflare/__init__.py +8 -0
  313. free_claude_code/providers/cloudflare/client.py +166 -0
  314. free_claude_code/providers/credential_validation.py +314 -0
  315. free_claude_code/providers/deepseek/__init__.py +5 -0
  316. free_claude_code/providers/deepseek/client.py +117 -0
  317. free_claude_code/providers/deepseek/compat.py +241 -0
  318. free_claude_code/providers/endpoint.py +21 -0
  319. free_claude_code/providers/endpoint_types.py +21 -0
  320. free_claude_code/providers/failure_policy.py +574 -0
  321. free_claude_code/providers/gemini/__init__.py +5 -0
  322. free_claude_code/providers/gemini/client.py +42 -0
  323. free_claude_code/providers/github_copilot/__init__.py +1 -0
  324. free_claude_code/providers/github_copilot/auth.py +502 -0
  325. free_claude_code/providers/github_copilot/broker.py +248 -0
  326. free_claude_code/providers/github_copilot/lifecycle.py +18 -0
  327. free_claude_code/providers/github_copilot/login.py +112 -0
  328. free_claude_code/providers/github_copilot/native_cli.py +82 -0
  329. free_claude_code/providers/github_copilot/provider.py +367 -0
  330. free_claude_code/providers/github_copilot/request_policy.py +119 -0
  331. free_claude_code/providers/github_copilot/responses_events.py +90 -0
  332. free_claude_code/providers/github_copilot/sdk.py +496 -0
  333. free_claude_code/providers/github_copilot/types.py +67 -0
  334. free_claude_code/providers/google_openai/__init__.py +17 -0
  335. free_claude_code/providers/google_openai/provider.py +73 -0
  336. free_claude_code/providers/google_openai/reasoning.py +150 -0
  337. free_claude_code/providers/google_openai/thought_signatures.py +117 -0
  338. free_claude_code/providers/groq/__init__.py +5 -0
  339. free_claude_code/providers/groq/client.py +342 -0
  340. free_claude_code/providers/groq/tpm.py +114 -0
  341. free_claude_code/providers/history_replay.py +260 -0
  342. free_claude_code/providers/http.py +105 -0
  343. free_claude_code/providers/kilo/__init__.py +5 -0
  344. free_claude_code/providers/kilo/client.py +45 -0
  345. free_claude_code/providers/kilo/models.py +60 -0
  346. free_claude_code/providers/lmstudio/__init__.py +5 -0
  347. free_claude_code/providers/lmstudio/client.py +220 -0
  348. free_claude_code/providers/mistral/__init__.py +5 -0
  349. free_claude_code/providers/mistral/client.py +91 -0
  350. free_claude_code/providers/mistral/reasoning.py +410 -0
  351. free_claude_code/providers/model_listing.py +479 -0
  352. free_claude_code/providers/nvidia_nim/__init__.py +5 -0
  353. free_claude_code/providers/nvidia_nim/client.py +224 -0
  354. free_claude_code/providers/nvidia_nim/native_tool_stream.py +693 -0
  355. free_claude_code/providers/nvidia_nim/request_options.py +141 -0
  356. free_claude_code/providers/nvidia_nim/retry.py +85 -0
  357. free_claude_code/providers/nvidia_nim/tool_schema.py +255 -0
  358. free_claude_code/providers/nvidia_nim/voice.py +107 -0
  359. free_claude_code/providers/open_router/__init__.py +5 -0
  360. free_claude_code/providers/open_router/client.py +113 -0
  361. free_claude_code/providers/openai_api/__init__.py +5 -0
  362. free_claude_code/providers/openai_api/provider.py +150 -0
  363. free_claude_code/providers/openai_chat/__init__.py +72 -0
  364. free_claude_code/providers/openai_chat/base_url.py +7 -0
  365. free_claude_code/providers/openai_chat/behavior.py +133 -0
  366. free_claude_code/providers/openai_chat/client.py +40 -0
  367. free_claude_code/providers/openai_chat/extra_body.py +63 -0
  368. free_claude_code/providers/openai_chat/output_cap.py +161 -0
  369. free_claude_code/providers/openai_chat/profiles.py +778 -0
  370. free_claude_code/providers/openai_chat/provider.py +244 -0
  371. free_claude_code/providers/openai_chat/reasoning.py +169 -0
  372. free_claude_code/providers/openai_chat/reasoning_details.py +128 -0
  373. free_claude_code/providers/openai_chat/request_policy.py +140 -0
  374. free_claude_code/providers/openai_chat/stream_output.py +752 -0
  375. free_claude_code/providers/openai_chat/tool_calls.py +462 -0
  376. free_claude_code/providers/openai_chat/transport.py +1643 -0
  377. free_claude_code/providers/openai_chat/usage.py +117 -0
  378. free_claude_code/providers/openai_client.py +74 -0
  379. free_claude_code/providers/openai_codex/__init__.py +1 -0
  380. free_claude_code/providers/openai_codex/auth.py +626 -0
  381. free_claude_code/providers/openai_codex/endpoint.py +53 -0
  382. free_claude_code/providers/openai_codex/login.py +265 -0
  383. free_claude_code/providers/openai_codex/provider.py +259 -0
  384. free_claude_code/providers/openai_responses/__init__.py +5 -0
  385. free_claude_code/providers/openai_responses/presentation.py +115 -0
  386. free_claude_code/providers/openai_responses/transport.py +621 -0
  387. free_claude_code/providers/openai_stream.py +21 -0
  388. free_claude_code/providers/opencode/__init__.py +5 -0
  389. free_claude_code/providers/opencode/catalog.py +340 -0
  390. free_claude_code/providers/opencode/provider.py +359 -0
  391. free_claude_code/providers/reasoning_compatibility.py +199 -0
  392. free_claude_code/providers/request_recovery.py +122 -0
  393. free_claude_code/providers/runtime/__init__.py +10 -0
  394. free_claude_code/providers/runtime/config.py +91 -0
  395. free_claude_code/providers/runtime/discovery.py +63 -0
  396. free_claude_code/providers/runtime/factory.py +282 -0
  397. free_claude_code/providers/runtime/model_cache.py +93 -0
  398. free_claude_code/providers/runtime/runtime.py +108 -0
  399. free_claude_code/providers/stream_recovery.py +164 -0
  400. free_claude_code/providers/vertex/__init__.py +5 -0
  401. free_claude_code/providers/vertex/auth.py +105 -0
  402. free_claude_code/providers/vertex/client.py +140 -0
  403. free_claude_code/providers/vertex/endpoint.py +50 -0
  404. free_claude_code/providers/vertex/models.py +52 -0
  405. free_claude_code/runtime/__init__.py +1 -0
  406. free_claude_code/runtime/application.py +1121 -0
  407. free_claude_code/runtime/asgi.py +63 -0
  408. free_claude_code/runtime/bootstrap.py +153 -0
  409. free_claude_code/runtime/code_sessions_sqlite.py +785 -0
  410. free_claude_code/runtime/codex_app_server.py +775 -0
  411. free_claude_code/runtime/codex_catalog.py +62 -0
  412. free_claude_code/runtime/codex_protocol.py +662 -0
  413. free_claude_code/runtime/configuration.py +53 -0
  414. free_claude_code/runtime/folder_picker.py +134 -0
  415. free_claude_code/runtime/native_folder_dialog.py +123 -0
  416. free_claude_code/runtime/provider_manager.py +716 -0
  417. free_claude_code/runtime/retired_chat.py +47 -0
  418. free_claude_code/runtime/web_tools/__init__.py +1 -0
  419. free_claude_code/runtime/web_tools/client.py +238 -0
  420. free_claude_code/runtime/web_tools/constants.py +17 -0
  421. free_claude_code/runtime/web_tools/egress.py +89 -0
  422. free_claude_code/runtime/web_tools/parsers.py +78 -0
  423. free_claude_code-6.2.64.data/scripts/fcc-update +4 -0
  424. free_claude_code-6.2.64.data/scripts/fcc-update.cmd +5 -0
  425. free_claude_code-6.2.64.dist-info/METADATA +555 -0
  426. free_claude_code-6.2.64.dist-info/RECORD +429 -0
  427. free_claude_code-6.2.64.dist-info/WHEEL +4 -0
  428. free_claude_code-6.2.64.dist-info/entry_points.txt +15 -0
  429. free_claude_code-6.2.64.dist-info/licenses/LICENSE +239 -0
@@ -0,0 +1,1693 @@
1
+ (() => {
2
+ "use strict";
3
+ const UI = window.SessionUI;
4
+ const modelComboboxes = new Set();
5
+ let modelControl,
6
+ reasoningControl,
7
+ providerControl,
8
+ harnessControl,
9
+ modeControl,
10
+ providerDraft = null,
11
+ harnesses = [],
12
+ catalog = [],
13
+ catalogLoaded = false,
14
+ catalogPhase = "starting",
15
+ catalogVersion = null,
16
+ catalogInstance = null,
17
+ catalogRequest = 0,
18
+ settingsPending = false;
19
+ let visibleIds = [],
20
+ query = "",
21
+ libraryToken = 0,
22
+ listFlight = null,
23
+ listAgain = false,
24
+ retryTimer = null;
25
+ let syncNotice = "",
26
+ availabilityNotice = "";
27
+ const base = "/admin/api/code";
28
+ const activeStatuses = new Set(["preparing", "running", "stopping"]);
29
+ const records = new Map();
30
+ const deleted = new Set();
31
+ const sends = new Set();
32
+ let api,
33
+ root,
34
+ feed,
35
+ epoch,
36
+ connected = false,
37
+ synchronized = false;
38
+ let desiredPath = null,
39
+ selected = null,
40
+ rendered = null,
41
+ syncToken = 0,
42
+ viewToken = 0;
43
+ let nextCursor = null,
44
+ libraryLoading = false,
45
+ notice = "",
46
+ available = false;
47
+ let activeDialog = null;
48
+ let viewActive = false,
49
+ activityKey = null,
50
+ activityTimer = null;
51
+
52
+ function element(tag, text, className) {
53
+ const node = document.createElement(tag);
54
+ if (text !== undefined) node.textContent = text;
55
+ if (className) node.className = className;
56
+ return window.FccFormControls.configure(node);
57
+ }
58
+ function button(text, action, className = "secondary-button") {
59
+ const node = element("button", text, className);
60
+ node.type = "button";
61
+ node.addEventListener("click", action);
62
+ return node;
63
+ }
64
+ function saved(id) {
65
+ try {
66
+ return JSON.parse(sessionStorage.getItem(`fcc.code.${id}`)) || {};
67
+ } catch {
68
+ return {};
69
+ }
70
+ }
71
+ function save(id, value) {
72
+ try {
73
+ sessionStorage.setItem(`fcc.code.${id}`, JSON.stringify(value));
74
+ } catch {
75
+ /* This tab still works when browser storage is unavailable. */
76
+ }
77
+ }
78
+ function get(id) {
79
+ if (!records.has(id))
80
+ records.set(id, {
81
+ id,
82
+ session: null,
83
+ run: null,
84
+ version: -1,
85
+ cursor: -1,
86
+ items: new Map(),
87
+ prompts: new Map(),
88
+ activeReviewIds: new Set(),
89
+ activePrompts: new Map(),
90
+ runs: new Map(),
91
+ loaded: false,
92
+ nextBefore: null,
93
+ });
94
+ return records.get(id);
95
+ }
96
+ function busy(record) {
97
+ return activeStatuses.has(record?.run?.status);
98
+ }
99
+ function pending(record) {
100
+ return [...(record?.activePrompts.values() || [])].some(
101
+ (entry) => entry.active,
102
+ );
103
+ }
104
+
105
+ function ready(record) {
106
+ return synchronized && record?.loaded && record.session?.status === "ready";
107
+ }
108
+ function compactTokens(tokens) {
109
+ let value = tokens;
110
+ let unit = 0;
111
+ const units = ["", "K", "M", "B"];
112
+ while (value >= 1000 && unit < units.length - 1) {
113
+ value /= 1000;
114
+ unit += 1;
115
+ }
116
+ let rounded = Math.round(value * 10) / 10;
117
+ if (rounded >= 1000 && unit < units.length - 1) {
118
+ rounded /= 1000;
119
+ unit += 1;
120
+ }
121
+ return `${rounded}${units[unit]}`;
122
+ }
123
+ function renderContextUsage(record) {
124
+ const node = root.querySelector("#codeContextUsage"),
125
+ used = record?.session?.context_used_tokens;
126
+ if (!node || !Number.isInteger(used) || used < 0) {
127
+ if (node) {
128
+ node.hidden = true;
129
+ node.textContent = "";
130
+ node.removeAttribute("title");
131
+ node.removeAttribute("aria-label");
132
+ }
133
+ return;
134
+ }
135
+ const maximum = catalog.find(
136
+ (model) => model.id === record.session.model,
137
+ )?.context_window_tokens,
138
+ knownMaximum = Number.isInteger(maximum) && maximum > 0,
139
+ percentage = knownMaximum ? Math.round((used / maximum) * 100) : null,
140
+ exactUsed = used.toLocaleString("en-US");
141
+ node.textContent = knownMaximum
142
+ ? `${compactTokens(used)} / ${compactTokens(maximum)} (${percentage}%)`
143
+ : compactTokens(used);
144
+ const description = knownMaximum
145
+ ? `Context used: ${exactUsed} of ${maximum.toLocaleString("en-US")} tokens (${percentage}%)`
146
+ : `Context used: ${exactUsed} tokens`;
147
+ node.title = description;
148
+ node.setAttribute("aria-label", description);
149
+ node.hidden = false;
150
+ }
151
+ function mergeEntries(target, entries, version) {
152
+ for (const value of entries || []) {
153
+ if ((target.get(value.id)?.version ?? -1) <= version)
154
+ target.set(value.id, { value, version });
155
+ }
156
+ }
157
+ function merge(data) {
158
+ if (data.epoch !== epoch) return;
159
+ const id = data.session_id || data.session?.id;
160
+ if (!id || deleted.has(id)) return;
161
+ const record = get(id),
162
+ version = data.version;
163
+ if (version >= record.version) {
164
+ if (!record.session || data.session.revision >= record.session.revision)
165
+ record.session = data.session;
166
+ record.run = data.run;
167
+ if (data.active_review_ids)
168
+ record.activeReviewIds = new Set(data.active_review_ids);
169
+ record.version = version;
170
+ record.cursor = Math.max(record.cursor, data.cursor || 0);
171
+ if (record.run) accepted(id, record.run.id);
172
+ }
173
+ mergeEntries(
174
+ record.runs,
175
+ [...(data.runs || []), ...(data.run ? [data.run] : [])],
176
+ version,
177
+ );
178
+ const items = data.item ? [data.item] : data.items || [],
179
+ outputChanged =
180
+ id === selected &&
181
+ items.some((item) => {
182
+ const previous = record.items.get(item.id);
183
+ return (
184
+ item.run_id === record.run?.id &&
185
+ version > (previous?.version ?? -1) &&
186
+ ["kind", "title", "text", "html", "detail", "complete"].some(
187
+ (key) => previous?.value[key] !== item[key],
188
+ )
189
+ );
190
+ });
191
+ mergeEntries(record.items, items, version);
192
+ const prompts = data.prompt ? [data.prompt] : data.prompts || [];
193
+ mergeEntries(record.prompts, prompts, version);
194
+ for (const prompt of prompts) {
195
+ if ((record.activePrompts.get(prompt.id)?.version ?? -1) <= version)
196
+ record.activePrompts.set(prompt.id, {
197
+ active: ["pending", "answering"].includes(prompt.status),
198
+ version,
199
+ });
200
+ }
201
+ if (data.active_prompt_ids) {
202
+ const active = new Set(data.active_prompt_ids);
203
+ for (const promptId of new Set([
204
+ ...record.activePrompts.keys(),
205
+ ...active,
206
+ ])) {
207
+ if ((record.activePrompts.get(promptId)?.version ?? -1) <= version)
208
+ record.activePrompts.set(promptId, {
209
+ active: active.has(promptId),
210
+ version,
211
+ });
212
+ }
213
+ }
214
+ return outputChanged;
215
+ }
216
+
217
+ function receive(type, event) {
218
+ const data = JSON.parse(event.data);
219
+ if (data.epoch !== epoch) return;
220
+ const previousRevision = records.get(data.session_id)?.session?.revision;
221
+ let outputChanged = false;
222
+ if (type === "session.deleted") {
223
+ removeDeletedSession(data.session_id, "Session deleted.");
224
+ } else {
225
+ outputChanged = merge(data) && type === "item.updated";
226
+ if (type === "session.notice" && data.session_id === selected)
227
+ notice = data.message;
228
+ }
229
+ if (
230
+ !selected &&
231
+ (type === "session.deleted" ||
232
+ data.session?.revision !== previousRevision)
233
+ )
234
+ void refreshLibrary();
235
+ render(outputChanged);
236
+ }
237
+
238
+ function clearActivity() {
239
+ clearTimeout(activityTimer);
240
+ activityTimer = null;
241
+ activityKey = null;
242
+ const node = root?.querySelector("#codeActivity");
243
+ if (node) node.hidden = true;
244
+ }
245
+
246
+ function renderActivity(outputChanged) {
247
+ const record = records.get(selected),
248
+ node = root?.querySelector("#codeActivity");
249
+ if (!viewActive || !node || !busy(record) || pending(record)) {
250
+ clearActivity();
251
+ return;
252
+ }
253
+ const key = `${selected}:${record.run.id}`;
254
+ if (activityKey === key && !outputChanged) return;
255
+ clearActivity();
256
+ activityKey = key;
257
+ activityTimer = setTimeout(() => {
258
+ activityTimer = null;
259
+ if (activityKey !== key) return;
260
+ const current = records.get(selected);
261
+ if (!viewActive || !busy(current) || pending(current)) {
262
+ clearActivity();
263
+ return;
264
+ }
265
+ const transcript = root.querySelector("#codeTranscript"),
266
+ bottom = UI.nearBottom(transcript);
267
+ node.hidden = false;
268
+ if (bottom) transcript.scrollTop = transcript.scrollHeight;
269
+ }, 500);
270
+ }
271
+
272
+ async function list(cursor = null) {
273
+ const requestEpoch = epoch,
274
+ token = syncToken,
275
+ route = viewToken,
276
+ search = query,
277
+ request = ++libraryToken;
278
+ const params = new URLSearchParams({ query: search });
279
+ if (cursor) params.set("cursor", cursor);
280
+ const data = await api(`${base}/sessions?${params}`);
281
+ if (
282
+ requestEpoch !== epoch ||
283
+ data.epoch !== epoch ||
284
+ token !== syncToken ||
285
+ route !== viewToken ||
286
+ search !== query ||
287
+ request !== libraryToken
288
+ )
289
+ return;
290
+ const ids = cursor ? [...visibleIds] : [];
291
+ for (const session of data.sessions) {
292
+ if (deleted.has(session.id)) continue;
293
+ const record = get(session.id);
294
+ if (
295
+ (!record.session || session.revision >= record.session.revision) &&
296
+ record.cursor <= data.cursor
297
+ )
298
+ record.session = session;
299
+ if (!ids.includes(session.id)) ids.push(session.id);
300
+ }
301
+ visibleIds = ids;
302
+ nextCursor = data.next_cursor;
303
+ render();
304
+ }
305
+ async function refreshLibrary() {
306
+ listAgain = true;
307
+ if (listFlight) return listFlight;
308
+ listFlight = (async () => {
309
+ while (listAgain && !selected) {
310
+ listAgain = false;
311
+ try {
312
+ await list();
313
+ } catch (error) {
314
+ restart(error.message);
315
+ break;
316
+ }
317
+ }
318
+ })();
319
+ try {
320
+ await listFlight;
321
+ } finally {
322
+ listFlight = null;
323
+ }
324
+ }
325
+
326
+ async function detail(id, before = null) {
327
+ const requestEpoch = epoch,
328
+ token = viewToken,
329
+ connection = syncToken;
330
+ const params = new URLSearchParams();
331
+ if (before) params.set("before", before);
332
+ else
333
+ // Reconcile unfinished entries even after they fall outside the newest page.
334
+ for (const { value } of get(id).items.values())
335
+ if (!value.complete) params.append("include_item_ids", value.id);
336
+ let data;
337
+ try {
338
+ data = await api(
339
+ `${base}/sessions/${id}${before ? "/items" : ""}?${params}`,
340
+ );
341
+ } catch (error) {
342
+ if (token !== viewToken || connection !== syncToken || selected !== id)
343
+ return;
344
+ if (error.status === 404) {
345
+ removeDeletedSession(id, "This session was deleted.");
346
+ render();
347
+ return;
348
+ }
349
+ throw error;
350
+ }
351
+ if (
352
+ requestEpoch !== epoch ||
353
+ data.epoch !== epoch ||
354
+ token !== viewToken ||
355
+ connection !== syncToken ||
356
+ selected !== id ||
357
+ deleted.has(id)
358
+ )
359
+ return;
360
+ const outputChanged = merge(data);
361
+ const record = get(id);
362
+ record.loaded = true;
363
+ record.nextBefore = data.next_before;
364
+ render(!before && outputChanged);
365
+ }
366
+ function olderCatalog(candidate) {
367
+ return candidate && catalogVersion && (candidate.generation_id < catalogVersion.generation_id ||
368
+ (candidate.generation_id === catalogVersion.generation_id && candidate.catalog_revision < catalogVersion.catalog_revision));
369
+ }
370
+ async function bootstrap() {
371
+ const token = syncToken;
372
+ const request = ++catalogRequest;
373
+ try {
374
+ const data = await api(`${base}/bootstrap`);
375
+ if (request !== catalogRequest || token !== syncToken || (epoch && data.epoch !== epoch) || olderCatalog(data.startup)) return false;
376
+ if (data.startup) catalogVersion = data.startup;
377
+ available = data.available;
378
+ catalogPhase = data.startup?.catalog || "ready";
379
+ if (catalogPhase === "ready" || !catalogLoaded) {
380
+ catalog = data.models;
381
+ harnesses = data.harnesses;
382
+ }
383
+ if (catalogPhase === "ready") catalogLoaded = true;
384
+ availabilityNotice = data.message || "";
385
+ return true;
386
+ } catch (error) {
387
+ if (request === catalogRequest && token === syncToken) {
388
+ availabilityNotice = error.message;
389
+ if (!catalogLoaded) catalogPhase = "failed";
390
+ }
391
+ throw error;
392
+ }
393
+ }
394
+
395
+ async function synchronize(readyData) {
396
+ const token = ++syncToken;
397
+ synchronized = false;
398
+ connected = true;
399
+ clearTimeout(retryTimer);
400
+ retryTimer = null;
401
+ if (epoch !== readyData.epoch) {
402
+ if (epoch) {
403
+ catalogVersion = null;
404
+ catalogLoaded = false;
405
+ catalogPhase = "starting";
406
+ }
407
+ epoch = readyData.epoch;
408
+ records.clear();
409
+ providerDraft = null;
410
+ deleted.clear();
411
+ rendered = null;
412
+ }
413
+ const included = new Set(
414
+ readyData.sessions.map((summary) => summary.session_id),
415
+ );
416
+ for (const record of records.values()) {
417
+ if (
418
+ !included.has(record.id) &&
419
+ busy(record) &&
420
+ record.cursor <= readyData.cursor
421
+ ) {
422
+ // The run is no longer active; detail supplies its actual outcome.
423
+ record.run = null;
424
+ record.loaded = false;
425
+ record.cursor = readyData.cursor;
426
+ }
427
+ }
428
+ for (const summary of readyData.sessions)
429
+ merge({ ...summary, cursor: readyData.cursor });
430
+ render();
431
+ void bootstrap().then(() => render()).catch(() => render());
432
+ try {
433
+ const results = await Promise.allSettled([
434
+ list(),
435
+ selected ? detail(selected) : Promise.resolve(),
436
+ ]);
437
+ if (token !== syncToken || !connected) return;
438
+ for (const result of results)
439
+ if (result.status === "rejected") throw result.reason;
440
+ synchronized = true;
441
+ syncNotice = "";
442
+ for (const id of records.keys()) if (saved(id).pending) void deliver(id);
443
+ } catch (error) {
444
+ if (token === syncToken) restart(error.message);
445
+ }
446
+ render();
447
+ }
448
+ function restart(message = "Reconnecting…") {
449
+ if (feed) feed.close();
450
+ feed = null;
451
+ connected = false;
452
+ synchronized = false;
453
+ ++syncToken;
454
+ syncNotice = message;
455
+ if (retryTimer === null)
456
+ retryTimer = setTimeout(() => {
457
+ retryTimer = null;
458
+ connect();
459
+ }, 1000);
460
+ render();
461
+ }
462
+
463
+ function connect() {
464
+ if (feed || retryTimer !== null) return;
465
+ const source = new EventSource(`${base}/events`);
466
+ feed = source;
467
+ source.addEventListener("feed.ready", (event) => {
468
+ if (feed === source) void synchronize(JSON.parse(event.data));
469
+ });
470
+ for (const type of [
471
+ "session.updated",
472
+ "session.deleted",
473
+ "run.updated",
474
+ "item.updated",
475
+ "prompt.updated",
476
+ "session.notice",
477
+ ])
478
+ source.addEventListener(type, (event) => {
479
+ if (feed === source) receive(type, event);
480
+ });
481
+ const disconnected = async () => {
482
+ if (feed !== source) return;
483
+ restart();
484
+ // A stopped store cannot open its event feed. Read its availability
485
+ // so that the retry screen can explain why it is unavailable.
486
+ try {
487
+ await bootstrap();
488
+ render();
489
+ } catch {
490
+ /* The scheduled connection retries the reads. */
491
+ }
492
+ };
493
+ source.addEventListener("feed.resync_required", disconnected);
494
+ source.onerror = disconnected;
495
+ }
496
+
497
+ function replaceDeletedRoute(id) {
498
+ const view = root?.closest(".admin-view");
499
+ if (
500
+ !view ||
501
+ view.hidden ||
502
+ window.location.pathname !== `/admin/code/${id}`
503
+ )
504
+ return false;
505
+ history.replaceState({}, "", "/admin/code");
506
+ return true;
507
+ }
508
+ function removeDeletedSession(id, message) {
509
+ deleted.add(id);
510
+ records.delete(id);
511
+ visibleIds = visibleIds.filter((visibleId) => visibleId !== id);
512
+ const replaced = replaceDeletedRoute(id);
513
+ if (selected === id || replaced) {
514
+ activate("/admin/code");
515
+ notice = message;
516
+ }
517
+ }
518
+ function navigate(id) {
519
+ const path = id ? `/admin/code/${id}` : "/admin/code";
520
+ history.pushState({}, "", path);
521
+ activate(path);
522
+ }
523
+ function activate(path) {
524
+ viewActive = true;
525
+ if (path !== desiredPath) dismissDialog();
526
+ desiredPath = path;
527
+ if (!api) return;
528
+ let id = /^\/admin\/code\/([0-9a-f-]+)$/.exec(path)?.[1] || null;
529
+ const missing = id !== null && deleted.has(id);
530
+ if (missing) {
531
+ replaceDeletedRoute(id);
532
+ desiredPath = "/admin/code";
533
+ id = null;
534
+ }
535
+ if (id !== selected || !rendered) {
536
+ selected = id;
537
+ ++viewToken;
538
+ rendered = null;
539
+ settingsPending = false;
540
+ providerDraft = null;
541
+ notice = "";
542
+ render();
543
+ if (connected && epoch) {
544
+ const reading = id ? detail(id) : refreshLibrary();
545
+ const token = viewToken;
546
+ void reading.catch((error) => {
547
+ if (token === viewToken) restart(error.message);
548
+ });
549
+ }
550
+ }
551
+ if (missing) {
552
+ notice = "This session was deleted.";
553
+ render();
554
+ }
555
+ connect();
556
+ render();
557
+ }
558
+
559
+ function accepted(id, operationId) {
560
+ const state = saved(id);
561
+ if (state.pending?.operation_id !== operationId) return;
562
+ const oldText = state.pending.text;
563
+ delete state.pending;
564
+ if (state.draft === oldText) state.draft = "";
565
+ save(id, state);
566
+ if (
567
+ selected === id &&
568
+ root?.querySelector("#codeComposer")?.value === oldText
569
+ )
570
+ root.querySelector("#codeComposer").value = "";
571
+ }
572
+ async function deliver(id) {
573
+ const command = saved(id).pending;
574
+ if (!command || sends.has(id) || !synchronized) return;
575
+ sends.add(id);
576
+ render();
577
+ try {
578
+ const receipt = await api(`${base}/sessions/${id}/turns`, {
579
+ method: "POST",
580
+ body: JSON.stringify(command),
581
+ });
582
+ accepted(id, receipt.id);
583
+ } catch (error) {
584
+ const state = saved(id);
585
+ if (state.pending?.operation_id === command.operation_id) {
586
+ if (error.status && error.status < 500) {
587
+ delete state.pending;
588
+ save(id, state);
589
+ }
590
+ if (selected === id) notice = error.message;
591
+ }
592
+ if (error.status === 400 || error.status === 409) {
593
+ try {
594
+ await bootstrap();
595
+ } catch (failure) {
596
+ restart(failure.message);
597
+ }
598
+ }
599
+ } finally {
600
+ sends.delete(id);
601
+ render();
602
+ }
603
+ }
604
+ function submit() {
605
+ const record = records.get(selected);
606
+ if (
607
+ !ready(record) ||
608
+ busy(record) ||
609
+ pending(record) ||
610
+ !available ||
611
+ settingsPending ||
612
+ selectionError(record)
613
+ )
614
+ return;
615
+ const state = saved(selected),
616
+ text = root.querySelector("#codeComposer").value;
617
+ if (!state.pending && !text.trim()) return;
618
+ state.draft = text;
619
+ state.pending ||= {
620
+ operation_id: crypto.randomUUID(),
621
+ expected_revision: record.session.revision,
622
+ expected_epoch: epoch,
623
+ text,
624
+ };
625
+ save(selected, state);
626
+ notice = "";
627
+ void deliver(selected);
628
+ }
629
+ async function stop() {
630
+ const record = records.get(selected);
631
+ if (!busy(record)) return;
632
+ const id = selected,
633
+ run = record.run.id;
634
+ try {
635
+ await api(`${base}/sessions/${id}/stop`, {
636
+ method: "POST",
637
+ body: JSON.stringify({ operation_id: run }),
638
+ });
639
+ } catch (error) {
640
+ if (selected === id) notice = error.message;
641
+ }
642
+ render();
643
+ }
644
+ function dismissDialog() {
645
+ activeDialog?.();
646
+ }
647
+ function dialog(title, build, actionLabel, action) {
648
+ dismissDialog();
649
+ const node = element("dialog", undefined, "code-dialog"),
650
+ form = element("form");
651
+ const controller = new AbortController();
652
+ let disposed = false;
653
+ const heading = element("h3", title),
654
+ error = element("p", "", "code-notice");
655
+ error.setAttribute("role", "status");
656
+ heading.id = `code-dialog-${crypto.randomUUID()}`;
657
+ node.setAttribute("aria-labelledby", heading.id);
658
+ form.append(heading);
659
+ const actions = element("div", undefined, "code-actions");
660
+ const cancel = button("Cancel", close);
661
+ const confirm = button(actionLabel, () => {}, "primary-button");
662
+ confirm.type = "submit";
663
+ const context = {
664
+ signal: controller.signal,
665
+ isOpen: () => !disposed && node.open && node.isConnected,
666
+ isBusy: () => confirm.disabled,
667
+ setBusy(value) {
668
+ for (const control of form.querySelectorAll("input, select, button"))
669
+ if (control !== cancel) control.disabled = value;
670
+ },
671
+ message(text) {
672
+ error.textContent = text;
673
+ },
674
+ };
675
+ function close() {
676
+ if (disposed) return;
677
+ disposed = true;
678
+ controller.abort();
679
+ if (activeDialog === close) activeDialog = null;
680
+ node.close();
681
+ node.remove();
682
+ }
683
+ const value = build(form, context);
684
+ actions.append(cancel, confirm);
685
+ form.append(error, actions);
686
+ node.append(form);
687
+ node.addEventListener("close", close);
688
+ node.addEventListener("cancel", (event) => {
689
+ event.preventDefault();
690
+ close();
691
+ });
692
+ form.addEventListener("submit", async (event) => {
693
+ event.preventDefault();
694
+ if (!context.isOpen() || context.isBusy()) return;
695
+ context.setBusy(true);
696
+ try {
697
+ await action(value, context);
698
+ if (context.isOpen()) close();
699
+ } catch (failure) {
700
+ if (context.isOpen()) {
701
+ context.message(failure.message);
702
+ context.setBusy(false);
703
+ }
704
+ }
705
+ });
706
+ activeDialog = close;
707
+ root.append(node);
708
+ node.showModal();
709
+ }
710
+ function newSession() {
711
+ const id = crypto.randomUUID();
712
+ dialog(
713
+ "New code session",
714
+ (form, context) => {
715
+ const harness = element("select");
716
+ harness.append(new Option("Codex", "codex"));
717
+ const folder = element("input");
718
+ folder.required = true;
719
+ folder.autocomplete = "off";
720
+ label(form, "Harness", harness);
721
+ const field = element("div", undefined, "code-field");
722
+ const caption = element("label", "Folder");
723
+ folder.id = `code-folder-${id}`;
724
+ caption.htmlFor = folder.id;
725
+ const row = element("div", undefined, "code-folder-row");
726
+ let selectedPath = null;
727
+ const readFolder = () => selectedPath ?? folder.value;
728
+ const lineBreakNotice = element("p", "", "code-notice");
729
+ lineBreakNotice.id = `${folder.id}-notice`;
730
+ folder.setAttribute("aria-describedby", lineBreakNotice.id);
731
+ const manual = button("Enter path manually", () => {
732
+ setFolder("");
733
+ folder.focus();
734
+ });
735
+ manual.hidden = true;
736
+ function setFolder(path) {
737
+ const hasLineBreaks = /[\r\n]/.test(path);
738
+ selectedPath = hasLineBreaks ? path : null;
739
+ folder.readOnly = hasLineBreaks;
740
+ folder.value = path.replaceAll("\r", "␍").replaceAll("\n", "␊");
741
+ manual.hidden = !hasLineBreaks;
742
+ lineBreakNotice.textContent = hasLineBreaks
743
+ ? "Line breaks in this folder name are shown as ␍ and ␊."
744
+ : "";
745
+ }
746
+ const browse = button("Browse…", async () => {
747
+ if (!context.isOpen() || context.isBusy()) return;
748
+ const initial = readFolder();
749
+ context.setBusy(true);
750
+ context.message("");
751
+ try {
752
+ const result = await api(`${base}/folder-picker`, {
753
+ method: "POST",
754
+ body: JSON.stringify({ initial_path: initial || null }),
755
+ signal: context.signal,
756
+ });
757
+ if (context.isOpen()) {
758
+ if (result.path !== null) setFolder(result.path);
759
+ context.message("");
760
+ }
761
+ } catch (failure) {
762
+ if (context.isOpen()) context.message(failure.message);
763
+ } finally {
764
+ if (context.isOpen()) {
765
+ context.setBusy(false);
766
+ folder.focus();
767
+ }
768
+ }
769
+ });
770
+ row.append(folder, browse);
771
+ field.append(caption, row, lineBreakNotice, manual);
772
+ form.append(field);
773
+ form.append(
774
+ element(
775
+ "p",
776
+ "Choose an existing folder on the computer running FCC. You can also enter its path manually.",
777
+ "code-notice",
778
+ ),
779
+ );
780
+ return readFolder;
781
+ },
782
+ "Create session",
783
+ async (readFolder, context) => {
784
+ await api(`${base}/sessions`, {
785
+ method: "POST",
786
+ body: JSON.stringify({
787
+ session_id: id,
788
+ harness: "codex",
789
+ cwd: readFolder(),
790
+ }),
791
+ });
792
+ if (context.isOpen()) navigate(id);
793
+ },
794
+ );
795
+ }
796
+ async function updateSettings(changes) {
797
+ const id = selected,
798
+ record = records.get(id);
799
+ if (!ready(record) || settingsPending) return;
800
+ const revision = providerDraft?.revision ?? record.session.revision;
801
+ providerDraft = null;
802
+ settingsPending = true;
803
+ notice = "";
804
+ renderControls();
805
+ try {
806
+ const session = await api(`${base}/sessions/${id}`, {
807
+ method: "PATCH",
808
+ body: JSON.stringify({
809
+ expected_revision: revision,
810
+ ...changes,
811
+ }),
812
+ });
813
+ if (
814
+ !deleted.has(id) &&
815
+ session.revision > (records.get(id)?.session?.revision || 0)
816
+ )
817
+ get(id).session = session;
818
+ } catch (error) {
819
+ if (selected === id) {
820
+ notice = error.message;
821
+ try {
822
+ await detail(id);
823
+ } catch (failure) {
824
+ restart(failure.message);
825
+ }
826
+ }
827
+ } finally {
828
+ if (selected === id) {
829
+ settingsPending = false;
830
+ render();
831
+ }
832
+ }
833
+ }
834
+ function selectionError(record) {
835
+ if (!record?.session) return "";
836
+ if (providerDraft) return "Choose a model for the selected provider.";
837
+ if (!catalogLoaded) return catalogPhase === "starting" ? "Models are loading…" : "Model list unavailable.";
838
+ const model = catalog.find((model) => model.id === record.session.model);
839
+ if (!model) return "Selected model is unavailable. Choose another model.";
840
+ if (
841
+ record.session.reasoning_effort &&
842
+ !model.reasoning_efforts.includes(record.session.reasoning_effort)
843
+ )
844
+ return "Selected effort is unavailable. Choose another effort.";
845
+ return "";
846
+ }
847
+
848
+ function remove() {
849
+ const record = records.get(selected),
850
+ id = selected,
851
+ revision = record.session.revision;
852
+ if (record.session.status === "delete_uncertain") {
853
+ void api(`${base}/sessions/${id}?expected_revision=${revision}`, {
854
+ method: "DELETE",
855
+ }).catch((error) => {
856
+ notice = error.message;
857
+ render();
858
+ });
859
+ return;
860
+ }
861
+ dialog(
862
+ "Delete session?",
863
+ (form) => {
864
+ form.append(
865
+ element(
866
+ "p",
867
+ "This deletes the conversation from FCC and Codex history. Your project files stay in place.",
868
+ ),
869
+ );
870
+ },
871
+ "Delete session",
872
+ () =>
873
+ api(`${base}/sessions/${id}?expected_revision=${revision}`, {
874
+ method: "DELETE",
875
+ }),
876
+ );
877
+ }
878
+ function label(parent, title, input, description) {
879
+ const node = element("label", undefined, "code-field");
880
+ node.append(element("span", title), input);
881
+ if (description) node.append(element("small", description));
882
+ parent.append(node);
883
+ return node;
884
+ }
885
+ function shell() {
886
+ clearActivity();
887
+ dismissDialog();
888
+ root.replaceChildren();
889
+ modelComboboxes.clear();
890
+ modelControl =
891
+ reasoningControl =
892
+ providerControl =
893
+ harnessControl =
894
+ modeControl =
895
+ null;
896
+ const message = element("div", "", "session-notice");
897
+ message.id = "codeNotice";
898
+ message.setAttribute("role", "status");
899
+ if (selected) {
900
+ const id = selected,
901
+ record = get(id);
902
+ const controls = element(
903
+ "div",
904
+ undefined,
905
+ "session-controls code-session-controls",
906
+ );
907
+ providerControl = UI.selectControl(
908
+ "codeProvider",
909
+ "Provider",
910
+ [],
911
+ "",
912
+ (provider) => {
913
+ const current = records.get(selected);
914
+ modelControl.combobox.close();
915
+ providerDraft =
916
+ provider === current.session.provider_id
917
+ ? null
918
+ : {
919
+ id: selected,
920
+ revision: current.session.revision,
921
+ provider,
922
+ };
923
+ renderControls();
924
+ },
925
+ );
926
+ modelControl = UI.modelControl(
927
+ "codeModel",
928
+ record.session?.model_name || "",
929
+ () =>
930
+ catalog
931
+ .filter(
932
+ (model) =>
933
+ model.provider_id ===
934
+ (providerDraft?.provider ||
935
+ records.get(selected)?.session?.provider_id),
936
+ )
937
+ .map((model) => model.model_name),
938
+ modelComboboxes,
939
+ (name) => {
940
+ const provider =
941
+ providerDraft?.provider ||
942
+ records.get(selected)?.session?.provider_id;
943
+ const model = catalog.find(
944
+ (model) =>
945
+ model.provider_id === provider && model.model_name === name,
946
+ );
947
+ if (model) void updateSettings({ model: model.id });
948
+ },
949
+ );
950
+ reasoningControl = UI.selectControl(
951
+ "codeReasoning",
952
+ "Effort",
953
+ [],
954
+ "",
955
+ (value) => {
956
+ void updateSettings({ reasoning_effort: value });
957
+ },
958
+ );
959
+ harnessControl = UI.selectControl(
960
+ "codeHarness",
961
+ "Harness",
962
+ [],
963
+ "",
964
+ () => {},
965
+ );
966
+ modeControl = UI.selectControl(
967
+ "codeMode",
968
+ "Mode",
969
+ [
970
+ ["config", "Use config"],
971
+ ["ask", "Ask"],
972
+ ["auto_review", "Auto-review"],
973
+ ["full_access", "Full access"],
974
+ ],
975
+ record.session?.mode || "config",
976
+ (mode) => {
977
+ void updateSettings({ mode });
978
+ },
979
+ );
980
+ modeControl.select.title =
981
+ "Use config uses this session's original configured permission selection.";
982
+ controls.append(
983
+ providerControl.group,
984
+ modelControl.group,
985
+ reasoningControl.group,
986
+ harnessControl.group,
987
+ modeControl.group,
988
+ );
989
+ const selectionMessage = element(
990
+ "div",
991
+ "",
992
+ "code-control-error code-selection-error",
993
+ );
994
+ selectionMessage.id = "codeSelectionError";
995
+ selectionMessage.setAttribute("role", "status");
996
+ selectionMessage.hidden = true;
997
+ controls.append(selectionMessage);
998
+ for (const control of [
999
+ providerControl.select,
1000
+ modelControl.input,
1001
+ reasoningControl.select,
1002
+ ])
1003
+ control.setAttribute("aria-describedby", selectionMessage.id);
1004
+ const deletion = button("Delete", remove, "danger-button");
1005
+ deletion.id = "codeDelete";
1006
+ const deletionMessage = element("div", "", "code-control-error");
1007
+ deletionMessage.id = "codeDeletionError";
1008
+ deletionMessage.setAttribute("role", "status");
1009
+ deletionMessage.hidden = true;
1010
+ deletion.setAttribute("aria-describedby", deletionMessage.id);
1011
+ const deletionGroup = element("div");
1012
+ deletionGroup.append(deletion, deletionMessage);
1013
+ const header = UI.header(
1014
+ "← Code sessions",
1015
+ () => navigate(null),
1016
+ record.session?.title || "Code session",
1017
+ "Code title",
1018
+ (title) => {
1019
+ if (
1020
+ selected === id &&
1021
+ title.value !== records.get(id)?.session?.title
1022
+ )
1023
+ void updateSettings({ title: title.value });
1024
+ },
1025
+ [deletionGroup],
1026
+ controls,
1027
+ );
1028
+ header.append(element("p", "", "code-folder"));
1029
+ const transcript = element("div", undefined, "session-transcript");
1030
+ transcript.id = "codeTranscript";
1031
+ transcript.setAttribute("aria-label", "Conversation");
1032
+ const older = button(
1033
+ "Load older messages",
1034
+ async () => {
1035
+ const oldHeight = transcript.scrollHeight,
1036
+ top = transcript.scrollTop;
1037
+ older.disabled = true;
1038
+ try {
1039
+ await detail(id, records.get(id).nextBefore);
1040
+ if (selected === id)
1041
+ transcript.scrollTop = top + transcript.scrollHeight - oldHeight;
1042
+ } catch (error) {
1043
+ restart(error.message);
1044
+ }
1045
+ older.disabled = false;
1046
+ render();
1047
+ },
1048
+ "secondary-button session-older",
1049
+ );
1050
+ older.id = "codeOlder";
1051
+ const activity = element("div", "Thinking…", "code-activity");
1052
+ activity.id = "codeActivity";
1053
+ activity.setAttribute("role", "status");
1054
+ activity.hidden = true;
1055
+ transcript.append(older, element("div", undefined, "code-items"), activity);
1056
+ const composer = UI.composer(
1057
+ "code",
1058
+ saved(id).draft || "",
1059
+ "Ask Codex to work on this folder…",
1060
+ (value) => {
1061
+ save(id, { ...saved(id), draft: value });
1062
+ renderControls();
1063
+ },
1064
+ submit,
1065
+ () => {
1066
+ void stop();
1067
+ },
1068
+ );
1069
+ const contextUsage = element("span", "", "code-context-usage");
1070
+ contextUsage.id = "codeContextUsage";
1071
+ contextUsage.setAttribute("role", "status");
1072
+ contextUsage.hidden = true;
1073
+ composer.querySelector(".session-composer-actions").prepend(contextUsage);
1074
+ root.append(UI.shell(header, message, transcript, composer));
1075
+ UI.resizeComposer(composer.querySelector("textarea"));
1076
+ } else {
1077
+ const create = button("New code session", newSession, "primary-button");
1078
+ create.id = "codeNew";
1079
+ const header = UI.libraryHeader(
1080
+ "Code sessions",
1081
+ "Work with Codex in a project folder.",
1082
+ create,
1083
+ );
1084
+ const search = UI.search("Search titles and folders", query, (value) => {
1085
+ query = value;
1086
+ visibleIds = [];
1087
+ nextCursor = null;
1088
+ ++libraryToken;
1089
+ render();
1090
+ void refreshLibrary();
1091
+ });
1092
+ const more = button(
1093
+ "Load more sessions",
1094
+ async () => {
1095
+ libraryLoading = true;
1096
+ render();
1097
+ try {
1098
+ await list(nextCursor);
1099
+ } catch (error) {
1100
+ restart(error.message);
1101
+ }
1102
+ libraryLoading = false;
1103
+ render();
1104
+ },
1105
+ "secondary-button session-load-more",
1106
+ );
1107
+ more.id = "codeMore";
1108
+ const library = element("div", undefined, "session-list");
1109
+ library.id = "codeLibrary";
1110
+ const wrapper = element("div", undefined, "session-library");
1111
+ wrapper.append(header, search, message, library, more);
1112
+ root.append(wrapper);
1113
+ }
1114
+ rendered = selected || "library";
1115
+ }
1116
+
1117
+ function render(outputChanged = false) {
1118
+ if (!root) return;
1119
+ if (rendered !== (selected || "library")) shell();
1120
+ const message = root.querySelector("#codeNotice");
1121
+ message.textContent =
1122
+ notice ||
1123
+ availabilityNotice ||
1124
+ syncNotice ||
1125
+ (!connected ? "Connecting…" : !synchronized ? "Synchronizing…" : "");
1126
+ message.hidden = !message.textContent;
1127
+ if (!selected) {
1128
+ const library = root.querySelector("#codeLibrary");
1129
+ for (const child of [...library.children])
1130
+ if (!child.dataset.id || !visibleIds.includes(child.dataset.id))
1131
+ child.remove();
1132
+ for (const record of visibleIds
1133
+ .map((id) => records.get(id))
1134
+ .filter((record) => record?.session)
1135
+ .sort(
1136
+ (a, b) =>
1137
+ b.session.updated_at - a.session.updated_at ||
1138
+ b.id.localeCompare(a.id),
1139
+ )) {
1140
+ let card = library.querySelector(
1141
+ `[data-id="${CSS.escape(record.id)}"]`,
1142
+ );
1143
+ if (!card) {
1144
+ card = UI.card("", "", "", () => navigate(record.id));
1145
+ card.dataset.id = record.id;
1146
+ }
1147
+ card.children[0].textContent = record.session.title;
1148
+ card.children[1].textContent = record.session.cwd;
1149
+ card.children[2].textContent = busy(record)
1150
+ ? "Running"
1151
+ : record.session.error || record.session.model;
1152
+ library.append(card);
1153
+ }
1154
+ if (!library.children.length)
1155
+ library.append(
1156
+ element(
1157
+ "div",
1158
+ synchronized
1159
+ ? query
1160
+ ? "No matching sessions."
1161
+ : "Start your first code session."
1162
+ : "Loading sessions…",
1163
+ "session-empty",
1164
+ ),
1165
+ );
1166
+ root.querySelector("#codeNew").disabled = !synchronized;
1167
+ const more = root.querySelector("#codeMore");
1168
+ more.hidden = !nextCursor;
1169
+ more.disabled = !synchronized || libraryLoading;
1170
+ return;
1171
+ }
1172
+ const record = records.get(selected),
1173
+ title = root.querySelector(".session-title");
1174
+ if (record?.session) {
1175
+ if (document.activeElement !== title) title.value = record.session.title;
1176
+ root.querySelector(".code-folder").textContent = record.session.cwd;
1177
+ }
1178
+ const transcript = root.querySelector("#codeTranscript"),
1179
+ bottom = UI.nearBottom(transcript),
1180
+ items = root.querySelector(".code-items");
1181
+ const runs = [...(record?.runs.values() || [])]
1182
+ .map((entry) => entry.value)
1183
+ .sort((a, b) => a.ordinal - b.ordinal);
1184
+ for (const run of runs) {
1185
+ const source = [...record.items.values()]
1186
+ .map((entry) => entry.value)
1187
+ .filter((item) => item.run_id === run.id)
1188
+ .sort((a, b) => a.sequence - b.sequence);
1189
+ if (!source.length) continue;
1190
+ let group = items.querySelector(`[data-run-id="${CSS.escape(run.id)}"]`);
1191
+ if (!group) {
1192
+ group = element("div", undefined, "code-run");
1193
+ group.dataset.runId = run.id;
1194
+ group.dataset.ordinal = run.ordinal;
1195
+ group.append(
1196
+ element("div", undefined, "code-run-items"),
1197
+ UI.message("assistant", "Codex"),
1198
+ );
1199
+ group.lastChild.classList.add("code-outcome");
1200
+ group.lastChild.append(element("div", "", "session-generation-status"));
1201
+ const next = [...items.children].find(
1202
+ (child) => Number(child.dataset.ordinal) > run.ordinal,
1203
+ );
1204
+ items.insertBefore(group, next || null);
1205
+ }
1206
+ for (const item of source) renderItem(group.firstChild, item, run);
1207
+ const outcome = group.querySelector(".code-outcome");
1208
+ outcome.hidden = !["failed", "interrupted"].includes(run.status);
1209
+ const status = outcome.querySelector(".session-generation-status");
1210
+ status.className = `session-generation-status ${run.status}`;
1211
+ status.textContent =
1212
+ run.error ||
1213
+ (run.status === "interrupted" ? "Turn stopped." : "This turn failed.");
1214
+ }
1215
+ renderActivity(outputChanged);
1216
+ if (bottom) transcript.scrollTop = transcript.scrollHeight;
1217
+ root.querySelector("#codeOlder").hidden = !record?.nextBefore;
1218
+ renderControls();
1219
+ }
1220
+
1221
+ function renderControls() {
1222
+ if (!selected || !root?.querySelector("#codeComposer")) return;
1223
+ const record = records.get(selected),
1224
+ isBusy = busy(record),
1225
+ input = root.querySelector("#codeComposer");
1226
+ renderContextUsage(record);
1227
+ if (
1228
+ providerDraft &&
1229
+ (providerDraft.id !== selected ||
1230
+ providerDraft.revision !== record?.session?.revision ||
1231
+ isBusy ||
1232
+ pending(record))
1233
+ ) {
1234
+ providerDraft = null;
1235
+ modelControl.combobox.close();
1236
+ }
1237
+ const send = root.querySelector("#codeSend"),
1238
+ stop = root.querySelector("#codeStop");
1239
+ send.hidden = isBusy;
1240
+ stop.hidden = !isBusy;
1241
+ stop.disabled = !connected || record?.run?.stop_requested;
1242
+ send.textContent = saved(selected).pending ? "Retry Send" : "Send";
1243
+ send.classList.toggle("startup-busy", !catalogLoaded && catalogPhase === "starting");
1244
+ send.setAttribute("aria-busy", String(!catalogLoaded && catalogPhase === "starting"));
1245
+ send.disabled =
1246
+ !ready(record) ||
1247
+ !available ||
1248
+ !catalogLoaded ||
1249
+ pending(record) ||
1250
+ sends.has(selected) ||
1251
+ settingsPending ||
1252
+ !!selectionError(record) ||
1253
+ (!input.value.trim() && !saved(selected).pending);
1254
+ input.disabled = !record?.loaded || record?.session?.status !== "ready";
1255
+ root.querySelector(".session-title").disabled =
1256
+ !ready(record) || settingsPending;
1257
+ const deletion = root.querySelector("#codeDelete");
1258
+ deletion.textContent =
1259
+ record?.session?.status === "delete_uncertain"
1260
+ ? "Check deletion"
1261
+ : record?.session?.status === "deleting"
1262
+ ? "Deleting…"
1263
+ : "Delete";
1264
+ deletion.disabled =
1265
+ !synchronized ||
1266
+ !record?.loaded ||
1267
+ record.session?.status === "deleting" ||
1268
+ isBusy ||
1269
+ pending(record);
1270
+ const disabled =
1271
+ !ready(record) ||
1272
+ isBusy ||
1273
+ pending(record) ||
1274
+ settingsPending ||
1275
+ !catalogLoaded;
1276
+ const provider =
1277
+ providerDraft?.provider || record?.session?.provider_id || "";
1278
+ if (modelControl.group.dataset.provider !== provider) {
1279
+ modelControl.combobox.close();
1280
+ modelControl.group.dataset.provider = provider;
1281
+ }
1282
+ const providers = [...new Set(catalog.map((model) => model.provider_id))];
1283
+ if (provider && !providers.includes(provider)) providers.push(provider);
1284
+ providerControl.update(
1285
+ providers.map((value) => [value, value]),
1286
+ provider,
1287
+ );
1288
+ providerControl.select.disabled = disabled;
1289
+ modelControl.update(
1290
+ providerDraft ? "" : record?.session?.model_name || "",
1291
+ disabled,
1292
+ );
1293
+ modelControl.input.placeholder = "Choose a model";
1294
+ harnessControl.update(
1295
+ harnesses.map((harness) => [harness.id, harness.name]),
1296
+ record?.session?.harness || "codex",
1297
+ );
1298
+ harnessControl.select.disabled = true;
1299
+ modeControl.select.value = record?.session?.mode || "config";
1300
+ modeControl.select.disabled = disabled;
1301
+ const model = catalog.find((model) => model.id === record?.session?.model),
1302
+ effort =
1303
+ record?.session?.reasoning_effort ||
1304
+ model?.default_reasoning_effort ||
1305
+ "off";
1306
+ const options = ["off", "low", "medium", "high", "xhigh", "max"].map(
1307
+ (value) => [value, value],
1308
+ );
1309
+ reasoningControl.update(options, effort);
1310
+ for (const option of reasoningControl.select.options)
1311
+ option.disabled = !model?.reasoning_efforts.includes(option.value);
1312
+ reasoningControl.select.disabled = disabled || !model;
1313
+ const selectionMessage = root.querySelector("#codeSelectionError"),
1314
+ deletionMessage = root.querySelector("#codeDeletionError");
1315
+ selectionMessage.textContent = selectionError(record);
1316
+ selectionMessage.hidden = !selectionMessage.textContent;
1317
+ deletionMessage.textContent = record?.session?.error || "";
1318
+ deletionMessage.hidden = !deletionMessage.textContent;
1319
+ for (const node of root.querySelectorAll(".code-prompt")) {
1320
+ const prompt = record?.prompts.get(node.dataset.id)?.value;
1321
+ for (const control of node.querySelectorAll("input, select, button"))
1322
+ control.disabled =
1323
+ !ready(record) ||
1324
+ !record.activePrompts.get(node.dataset.id)?.active ||
1325
+ prompt?.status !== "pending" ||
1326
+ node.dataset.claiming === "true";
1327
+ }
1328
+ UI.resizeComposer(input);
1329
+ }
1330
+
1331
+ function renderItem(parent, item, run) {
1332
+ if (item.kind === "prompt") {
1333
+ const prompt = records.get(selected).prompts.get(item.id)?.value;
1334
+ if (prompt) renderPrompt(parent, prompt, item.sequence);
1335
+ return;
1336
+ }
1337
+ let node = parent.querySelector(`[data-id="${CSS.escape(item.id)}"]`);
1338
+ if (!node) {
1339
+ node = UI.message(
1340
+ item.kind === "user" ? "user" : "assistant",
1341
+ item.kind === "user" ? "You" : "Codex",
1342
+ );
1343
+ node.classList.add("code-item");
1344
+ node.dataset.id = item.id;
1345
+ node.dataset.kind = item.kind;
1346
+ let content = node;
1347
+ if (item.kind === "reasoning") {
1348
+ content = UI.thinking();
1349
+ node.append(content);
1350
+ } else if (!["text", "user"].includes(item.kind)) {
1351
+ content = element("details", undefined, "code-tool");
1352
+ content.append(element("summary", item.title || "Tool"));
1353
+ node.append(content);
1354
+ }
1355
+ content.append(
1356
+ element(
1357
+ "div",
1358
+ "",
1359
+ `code-prose ${item.kind === "user" ? "session-message-plain" : "session-markdown"}`,
1360
+ ),
1361
+ element("pre", "", "code-item-detail"),
1362
+ );
1363
+ insertEntry(parent, node, item.sequence);
1364
+ }
1365
+ const summary = node.querySelector(".code-tool > summary");
1366
+ if (summary) {
1367
+ const childReview = item.kind === "subagent_auto_review";
1368
+ const active = childReview
1369
+ ? records.get(selected).activeReviewIds.has(item.id)
1370
+ : activeStatuses.has(run.status);
1371
+ summary.textContent =
1372
+ (childReview || item.kind === "auto_review") &&
1373
+ !item.complete &&
1374
+ !active
1375
+ ? `${childReview ? "Sub-agent " : ""}Auto-review: Result unavailable`
1376
+ : item.title || "Tool";
1377
+ }
1378
+ const content = node.querySelector(".code-prose"),
1379
+ value = item.html ?? item.text;
1380
+ if (node.codeText !== value) {
1381
+ if (item.html != null) content.innerHTML = item.html;
1382
+ else content.textContent = item.text;
1383
+ node.codeText = value;
1384
+ }
1385
+ const detail = node.querySelector(".code-item-detail");
1386
+ detail.textContent = item.detail;
1387
+ detail.hidden = !item.detail;
1388
+ }
1389
+
1390
+ function insertEntry(parent, node, sequence) {
1391
+ node.dataset.sequence = sequence;
1392
+ const next = [...parent.children].find(
1393
+ (child) => Number(child.dataset.sequence) > sequence,
1394
+ );
1395
+ parent.insertBefore(node, next || null);
1396
+ }
1397
+
1398
+ function renderPrompt(parent, prompt, sequence) {
1399
+ let node = parent.querySelector(`[data-id="${CSS.escape(prompt.id)}"]`);
1400
+ if (!node) {
1401
+ node = element("form", undefined, "code-prompt");
1402
+ node.dataset.id = prompt.id;
1403
+ node.append(
1404
+ element("h3", prompt.form.title),
1405
+ element("pre", prompt.form.detail || ""),
1406
+ );
1407
+ buildPrompt(node, prompt);
1408
+ node.append(element("p", "", "code-prompt-state"));
1409
+ insertEntry(parent, node, sequence);
1410
+ }
1411
+ node.querySelector(".code-prompt-state").textContent =
1412
+ prompt.error ||
1413
+ (!records.get(selected)?.activePrompts.get(prompt.id)?.active &&
1414
+ ["pending", "answering"].includes(prompt.status)
1415
+ ? "No longer active"
1416
+ : "") ||
1417
+ {
1418
+ answering: "Answer sent…",
1419
+ resolved: "Resolved",
1420
+ expired: "No longer active",
1421
+ }[prompt.status] ||
1422
+ "";
1423
+ }
1424
+ function buildPrompt(form, prompt) {
1425
+ const sessionId = selected,
1426
+ actions = element("div", undefined, "code-actions");
1427
+ let responseId = null,
1428
+ submitting = false;
1429
+ const answer = async (value) => {
1430
+ if (
1431
+ submitting ||
1432
+ !ready(records.get(sessionId)) ||
1433
+ !records.get(sessionId).activePrompts.get(prompt.id)?.active
1434
+ )
1435
+ return;
1436
+ responseId ||= crypto.randomUUID();
1437
+ submitting = true;
1438
+ form.dataset.claiming = "true";
1439
+ renderControls();
1440
+ try {
1441
+ await api(
1442
+ `${base}/sessions/${sessionId}/prompts/${prompt.id}/responses`,
1443
+ {
1444
+ method: "POST",
1445
+ body: JSON.stringify({ response_id: responseId, answer: value }),
1446
+ },
1447
+ );
1448
+ } catch (error) {
1449
+ form.querySelector(".code-prompt-state").textContent = error.message;
1450
+ } finally {
1451
+ submitting = false;
1452
+ form.dataset.claiming = "false";
1453
+ renderControls();
1454
+ }
1455
+ };
1456
+ form.addEventListener("submit", (event) => event.preventDefault());
1457
+ const choice = (text, value) =>
1458
+ actions.append(
1459
+ button(text, () => {
1460
+ void answer(value);
1461
+ }),
1462
+ );
1463
+ if (prompt.kind === "approval") {
1464
+ for (const item of prompt.form.choices)
1465
+ choice(item.label, { choice: item.id });
1466
+ } else if (prompt.kind === "permissions") {
1467
+ const checks = prompt.form.choices.map((item) => {
1468
+ const input = element("input");
1469
+ input.type = "checkbox";
1470
+ input.value = item.id;
1471
+ const label = element("label", undefined, "code-choice");
1472
+ label.append(input, document.createTextNode(item.label));
1473
+ form.append(label);
1474
+ return input;
1475
+ });
1476
+ const scope = element("select");
1477
+ scope.append(
1478
+ new Option("This turn", "turn"),
1479
+ new Option("This session", "session"),
1480
+ );
1481
+ label(form, "Allow for", scope);
1482
+ actions.append(
1483
+ button("Allow selected", () => {
1484
+ void answer({
1485
+ selected: checks
1486
+ .filter((input) => input.checked)
1487
+ .map((input) => input.value),
1488
+ scope: scope.value,
1489
+ });
1490
+ }),
1491
+ );
1492
+ choice("Decline", { selected: [], scope: "turn" });
1493
+ } else if (prompt.kind === "questions") {
1494
+ const readers = prompt.form.questions.map((question) => {
1495
+ const field = element("fieldset");
1496
+ field.append(element("legend", question.label));
1497
+ form.append(field);
1498
+ if (question.options?.length) {
1499
+ const name = `question-${crypto.randomUUID()}`,
1500
+ radios = [];
1501
+ for (const option of question.options) {
1502
+ const radio = element("input");
1503
+ radio.type = "radio";
1504
+ radio.name = name;
1505
+ radio.value = option.label;
1506
+ radio.required = true;
1507
+ const optionLabel = element("label", undefined, "code-choice");
1508
+ optionLabel.append(
1509
+ radio,
1510
+ document.createTextNode(
1511
+ option.label +
1512
+ (option.description ? ` — ${option.description}` : ""),
1513
+ ),
1514
+ );
1515
+ field.append(optionLabel);
1516
+ radios.push(radio);
1517
+ }
1518
+ let other;
1519
+ if (question.allow_other) {
1520
+ const radio = element("input");
1521
+ radio.type = "radio";
1522
+ radio.name = name;
1523
+ radio.value = "__other__";
1524
+ radios.push(radio);
1525
+ const otherLabel = element("label", undefined, "code-choice");
1526
+ otherLabel.append(radio, document.createTextNode("Other"));
1527
+ field.append(otherLabel);
1528
+ other = element("input");
1529
+ other.type = question.secret ? "password" : "text";
1530
+ other.autocomplete = "off";
1531
+ label(field, "Your answer", other);
1532
+ other.addEventListener("input", () => {
1533
+ radio.checked = true;
1534
+ });
1535
+ }
1536
+ return () => [
1537
+ question.id,
1538
+ [
1539
+ radios.find((radio) => radio.checked)?.value === "__other__"
1540
+ ? other.value
1541
+ : radios.find((radio) => radio.checked)?.value || "",
1542
+ ],
1543
+ ];
1544
+ }
1545
+ const input = element("input");
1546
+ input.type = question.secret ? "password" : "text";
1547
+ input.required = true;
1548
+ input.autocomplete = "off";
1549
+ label(field, question.header || "Your answer", input);
1550
+ return () => [question.id, [input.value]];
1551
+ });
1552
+ actions.append(
1553
+ button(
1554
+ "Submit answers",
1555
+ () => {
1556
+ if (form.reportValidity())
1557
+ void answer({
1558
+ answers: Object.fromEntries(readers.map((read) => read())),
1559
+ });
1560
+ },
1561
+ "primary-button",
1562
+ ),
1563
+ );
1564
+ } else {
1565
+ const readers = [];
1566
+ if (prompt.kind === "url") {
1567
+ try {
1568
+ const url = new URL(prompt.form.url);
1569
+ if (["https:", "http:"].includes(url.protocol)) {
1570
+ const link = element("a", "Open tool request");
1571
+ link.href = url.href;
1572
+ link.target = "_blank";
1573
+ link.rel = "noopener noreferrer";
1574
+ form.append(link);
1575
+ } else throw new Error();
1576
+ } catch {
1577
+ form.append(element("p", "This tool supplied an unsupported link."));
1578
+ }
1579
+ } else if (prompt.form.unsupported)
1580
+ form.append(
1581
+ element(
1582
+ "p",
1583
+ "This tool's form cannot be displayed. Decline or cancel this request.",
1584
+ ),
1585
+ );
1586
+ else
1587
+ for (const field of prompt.form.fields || []) {
1588
+ const input = element(field.options?.length ? "select" : "input");
1589
+ if (field.options?.length) {
1590
+ input.multiple = field.type === "array";
1591
+ if (!input.multiple) input.append(new Option("Choose…", ""));
1592
+ field.options.forEach((option, index) =>
1593
+ input.append(new Option(option.label, String(index))),
1594
+ );
1595
+ } else
1596
+ input.type =
1597
+ field.type === "boolean"
1598
+ ? "checkbox"
1599
+ : ["integer", "number"].includes(field.type)
1600
+ ? "number"
1601
+ : "text";
1602
+ if (input.type === "number")
1603
+ input.step = field.type === "integer" ? "1" : "any";
1604
+ for (const [source, target] of [
1605
+ ["minimum", "min"],
1606
+ ["maximum", "max"],
1607
+ ["minLength", "minLength"],
1608
+ ["maxLength", "maxLength"],
1609
+ ])
1610
+ if (field[source] != null) input[target] = field[source];
1611
+ input.required = field.required && field.type !== "boolean";
1612
+ input.autocomplete = "off";
1613
+ if (field.default != null && !field.options?.length) {
1614
+ if (input.type === "checkbox") input.checked = field.default;
1615
+ else input.value = field.default;
1616
+ }
1617
+ label(form, field.label, input, field.description);
1618
+ readers.push(() => {
1619
+ if (input.type === "checkbox") return [field.id, input.checked];
1620
+ if (!input.value && !field.required) return null;
1621
+ if (field.options?.length)
1622
+ return [
1623
+ field.id,
1624
+ input.multiple
1625
+ ? [...input.selectedOptions].map(
1626
+ (option) => field.options[Number(option.value)].value,
1627
+ )
1628
+ : field.options[Number(input.value)].value,
1629
+ ];
1630
+ return [
1631
+ field.id,
1632
+ input.type === "number" ? Number(input.value) : input.value,
1633
+ ];
1634
+ });
1635
+ }
1636
+ if (!prompt.form.unsupported)
1637
+ actions.append(
1638
+ button(
1639
+ "Submit",
1640
+ () => {
1641
+ if (form.reportValidity())
1642
+ void answer({
1643
+ action: "accept",
1644
+ values: Object.fromEntries(
1645
+ readers.map((read) => read()).filter(Boolean),
1646
+ ),
1647
+ });
1648
+ },
1649
+ "primary-button",
1650
+ ),
1651
+ );
1652
+ choice("Decline", { action: "decline" });
1653
+ choice("Cancel request", { action: "cancel" });
1654
+ }
1655
+ form.append(actions);
1656
+ }
1657
+ function deactivate() {
1658
+ viewActive = false;
1659
+ clearActivity();
1660
+ dismissDialog();
1661
+ }
1662
+ window.addEventListener("pagehide", deactivate);
1663
+ window.CodeSessions = {
1664
+ initialize(client) {
1665
+ api = client;
1666
+ root = document.getElementById("codeRoot");
1667
+ if (desiredPath) activate(desiredPath);
1668
+ },
1669
+ activate,
1670
+ deactivate,
1671
+ async refresh(status) {
1672
+ if (!api) return false;
1673
+ if (status?.startup) {
1674
+ if (catalogInstance !== status.instance_id) catalogVersion = null;
1675
+ catalogInstance = status.instance_id;
1676
+ if (!olderCatalog(status.startup)) catalogVersion = status.startup;
1677
+ }
1678
+ try {
1679
+ const refreshed = await bootstrap();
1680
+ render();
1681
+ return refreshed;
1682
+ } catch {
1683
+ render();
1684
+ return false;
1685
+ }
1686
+ },
1687
+ };
1688
+ document.addEventListener("pointerdown", (event) => {
1689
+ for (const combobox of modelComboboxes)
1690
+ if (combobox.isOpen && !combobox.element.contains(event.target))
1691
+ combobox.close();
1692
+ });
1693
+ })();