open-codex-ui 0.1.4__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 (40) hide show
  1. open_codex_ui-0.1.4.dist-info/METADATA +249 -0
  2. open_codex_ui-0.1.4.dist-info/RECORD +40 -0
  3. open_codex_ui-0.1.4.dist-info/WHEEL +5 -0
  4. open_codex_ui-0.1.4.dist-info/entry_points.txt +7 -0
  5. open_codex_ui-0.1.4.dist-info/top_level.txt +1 -0
  6. yier_web/__init__.py +6 -0
  7. yier_web/app.py +223 -0
  8. yier_web/auth.py +269 -0
  9. yier_web/cli.py +172 -0
  10. yier_web/codex/__init__.py +3 -0
  11. yier_web/codex/ipc_manager.py +1761 -0
  12. yier_web/codex/session_events.py +110 -0
  13. yier_web/codex/ws_commands.py +299 -0
  14. yier_web/config.py +651 -0
  15. yier_web/directory_picker.py +93 -0
  16. yier_web/event_stream.py +29 -0
  17. yier_web/frontend.py +204 -0
  18. yier_web/routes/__init__.py +17 -0
  19. yier_web/routes/codex.py +573 -0
  20. yier_web/routes/core.py +281 -0
  21. yier_web/schemas.py +534 -0
  22. yier_web/static/assets/CodexEmbedView-CN_-Mhe2.js +1 -0
  23. yier_web/static/assets/CodexView-wpI61iXa.js +606 -0
  24. yier_web/static/assets/LoginView-CELCom2O.js +101 -0
  25. yier_web/static/assets/api-CeihACIV.js +1099 -0
  26. yier_web/static/assets/index-BdFqJ-Kl.css +1 -0
  27. yier_web/static/assets/index-CjVNk6ja.js +183 -0
  28. yier_web/static/assets/index-mSBvq1p8.js +79 -0
  29. yier_web/static/assets/open-codex-ui-icon-DKJ1ZKj4.svg +99 -0
  30. yier_web/static/assets/primeicons-C6QP2o4f.woff2 +0 -0
  31. yier_web/static/assets/primeicons-DMOk5skT.eot +0 -0
  32. yier_web/static/assets/primeicons-Dr5RGzOO.svg +345 -0
  33. yier_web/static/assets/primeicons-MpK4pl85.ttf +0 -0
  34. yier_web/static/assets/primeicons-WjwUDZjB.woff +0 -0
  35. yier_web/static/assets/useCodexWorkspace-6QenDzvb.css +1 -0
  36. yier_web/static/assets/useCodexWorkspace-D1rCOO1x.js +1128 -0
  37. yier_web/static/brand/open-codex-ui-logo.svg +85 -0
  38. yier_web/static/favicon.ico +0 -0
  39. yier_web/static/favicon.svg +99 -0
  40. yier_web/static/index.html +24 -0
yier_web/schemas.py ADDED
@@ -0,0 +1,534 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal
4
+ from uuid import uuid4
5
+
6
+ from pydantic import BaseModel, Field, field_validator
7
+
8
+
9
+ MCPRuntimeStatus = Literal[
10
+ "connected",
11
+ "disabled",
12
+ "failed",
13
+ "needs_auth",
14
+ "needs_client_registration",
15
+ ]
16
+ FrontendMode = Literal["proxy", "static", "missing"]
17
+ LLMProvider = Literal["", "zai", "zai-coding-plan"]
18
+ BackendId = Literal["yier", "codex"]
19
+ WorkspaceSurface = Literal["yier", "codex", "claude"]
20
+ CodexApprovalPolicy = Literal["untrusted", "on-failure", "on-request", "never"]
21
+ CodexSandboxMode = Literal["read-only", "workspace-write", "danger-full-access"]
22
+ CodexPersonality = Literal["none", "friendly", "pragmatic"]
23
+ CodexReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh"]
24
+ CodexServiceTier = Literal["", "fast", "flex"]
25
+ CodexApprovalsReviewer = Literal["user", "guardian_subagent"]
26
+ CodexProjectKind = Literal["local", "remote"]
27
+
28
+
29
+ class StoredLLMSettings(BaseModel):
30
+ provider: LLMProvider = ""
31
+ base_url: str = ""
32
+ api_key: str = ""
33
+ model: str = ""
34
+
35
+ @property
36
+ def is_preset(self) -> bool:
37
+ return self.provider in {"zai", "zai-coding-plan"}
38
+
39
+ @property
40
+ def is_ready(self) -> bool:
41
+ has_model = bool(self.model.strip())
42
+ has_api_key = bool(self.api_key.strip())
43
+ if self.is_preset:
44
+ return bool(self.provider and has_model and has_api_key)
45
+ return bool(self.base_url.strip() and has_model and has_api_key)
46
+
47
+
48
+ class SessionDefaultsSettings(BaseModel):
49
+ default_backend_id: BackendId = "codex"
50
+ default_project_path: str = ""
51
+ channel_backend_id: BackendId = "codex"
52
+ channel_project_path: str = ""
53
+ channel_auto_approve_codex_requests: bool = True
54
+ workspace_surface: WorkspaceSurface = "codex"
55
+
56
+ @field_validator("default_backend_id", "channel_backend_id")
57
+ @classmethod
58
+ def normalize_backend_id(cls, value: str) -> BackendId:
59
+ return "codex"
60
+
61
+ @field_validator("workspace_surface")
62
+ @classmethod
63
+ def normalize_workspace_surface(cls, value: str) -> WorkspaceSurface:
64
+ return "codex"
65
+
66
+
67
+ class StoredCodexSettings(BaseModel):
68
+ launcher_command: str = "codex app-server --listen stdio://"
69
+ model: str = "gpt-5.4"
70
+ sandbox: CodexSandboxMode = "workspace-write"
71
+ approval_policy: CodexApprovalPolicy = "on-request"
72
+ approvals_reviewer: CodexApprovalsReviewer = "user"
73
+ personality: CodexPersonality = "friendly"
74
+ reasoning_effort: CodexReasoningEffort = "medium"
75
+ show_reasoning_cards: bool = False
76
+ service_tier: CodexServiceTier = ""
77
+ active_remote_connection_id: str = ""
78
+ remote_connections: list["CodexRemoteConnection"] = Field(default_factory=list)
79
+ projects: list["CodexProjectDefinition"] = Field(default_factory=list)
80
+ thread_project_assignments: dict[str, "CodexThreadProjectAssignment"] = Field(
81
+ default_factory=dict
82
+ )
83
+ projectless_thread_ids: list[str] = Field(default_factory=list)
84
+
85
+ @field_validator("launcher_command", "model", "active_remote_connection_id")
86
+ @classmethod
87
+ def strip_string_fields(cls, value: str) -> str:
88
+ return value.strip()
89
+
90
+ @field_validator("projectless_thread_ids")
91
+ @classmethod
92
+ def normalize_projectless_thread_ids(cls, value: list[str]) -> list[str]:
93
+ return list(
94
+ dict.fromkeys(thread_id.strip() for thread_id in value if thread_id.strip())
95
+ )
96
+
97
+
98
+ class CodexRemoteConnection(BaseModel):
99
+ id: str = Field(default_factory=lambda: uuid4().hex)
100
+ display_name: str = ""
101
+ ssh_host: str = ""
102
+ ssh_username: str = ""
103
+ ssh_port: int | None = None
104
+ ssh_alias: str = ""
105
+ identity_file: str = ""
106
+ remote_path: str = "~"
107
+ auto_connect: bool = False
108
+
109
+ @field_validator(
110
+ "id",
111
+ "display_name",
112
+ "ssh_host",
113
+ "ssh_username",
114
+ "ssh_alias",
115
+ "identity_file",
116
+ "remote_path",
117
+ )
118
+ @classmethod
119
+ def strip_remote_connection_strings(cls, value: str) -> str:
120
+ return value.strip()
121
+
122
+ @field_validator("ssh_port")
123
+ @classmethod
124
+ def validate_ssh_port(cls, value: int | None) -> int | None:
125
+ if value is None:
126
+ return None
127
+ if value < 1 or value > 65535:
128
+ raise ValueError("ssh_port must be between 1 and 65535.")
129
+ return value
130
+
131
+ def normalized(self) -> "CodexRemoteConnection":
132
+ if self.ssh_alias:
133
+ display_name = self.display_name or self.ssh_alias
134
+ return self.model_copy(
135
+ update={
136
+ "display_name": display_name,
137
+ "ssh_host": "",
138
+ "ssh_username": "",
139
+ "ssh_port": None,
140
+ "identity_file": "",
141
+ "remote_path": self.remote_path or "~",
142
+ }
143
+ )
144
+
145
+ ssh_host, ssh_username = self._direct_target()
146
+ display_name = self.display_name or "@".join(
147
+ part for part in (ssh_username, ssh_host) if part
148
+ )
149
+ return self.model_copy(
150
+ update={
151
+ "display_name": display_name,
152
+ "ssh_host": ssh_host,
153
+ "ssh_username": ssh_username,
154
+ "remote_path": self.remote_path or "~",
155
+ }
156
+ )
157
+
158
+ def _direct_target(self) -> tuple[str, str]:
159
+ if self.ssh_username or "@" not in self.ssh_host:
160
+ return self.ssh_host, self.ssh_username
161
+ ssh_username, ssh_host = self.ssh_host.split("@", maxsplit=1)
162
+ return ssh_host, ssh_username
163
+
164
+
165
+ class CodexProjectDefinition(BaseModel):
166
+ id: str = Field(default_factory=lambda: uuid4().hex)
167
+ name: str = ""
168
+ kind: CodexProjectKind = "local"
169
+ host_id: str = "local"
170
+ root_paths: list[str] = Field(default_factory=list)
171
+ created_at: float = 0.0
172
+ updated_at: float = 0.0
173
+
174
+ @field_validator("id", "name", "host_id")
175
+ @classmethod
176
+ def strip_project_strings(cls, value: str) -> str:
177
+ return value.strip()
178
+
179
+ @field_validator("root_paths")
180
+ @classmethod
181
+ def normalize_project_roots(cls, value: list[str]) -> list[str]:
182
+ return list(dict.fromkeys(path.strip() for path in value if path.strip()))
183
+
184
+
185
+ class CodexProjectPayload(BaseModel):
186
+ name: str = ""
187
+ kind: CodexProjectKind = "local"
188
+ host_id: str = "local"
189
+ project_path: str
190
+
191
+ @field_validator("name", "host_id", "project_path")
192
+ @classmethod
193
+ def strip_project_payload_strings(cls, value: str) -> str:
194
+ return value.strip()
195
+
196
+
197
+ class CodexThreadProjectAssignment(BaseModel):
198
+ project_id: str
199
+ project_kind: CodexProjectKind
200
+ host_id: str = "local"
201
+ cwd: str
202
+
203
+ @field_validator("project_id", "host_id", "cwd")
204
+ @classmethod
205
+ def strip_assignment_strings(cls, value: str) -> str:
206
+ return value.strip()
207
+
208
+
209
+ class CodexRemoteConnectionPayload(BaseModel):
210
+ display_name: str = ""
211
+ ssh_host: str = ""
212
+ ssh_username: str = ""
213
+ ssh_port: int | None = None
214
+ ssh_alias: str = ""
215
+ identity_file: str = ""
216
+ remote_path: str = "~"
217
+ auto_connect: bool = False
218
+
219
+ @field_validator(
220
+ "display_name",
221
+ "ssh_host",
222
+ "ssh_username",
223
+ "ssh_alias",
224
+ "identity_file",
225
+ "remote_path",
226
+ )
227
+ @classmethod
228
+ def strip_payload_strings(cls, value: str) -> str:
229
+ return value.strip()
230
+
231
+
232
+ class CodexRemoteConnectionApiKeyLoginPayload(BaseModel):
233
+ api_key: str = Field(alias="apiKey")
234
+
235
+ @field_validator("api_key")
236
+ @classmethod
237
+ def strip_api_key(cls, value: str) -> str:
238
+ stripped = value.strip()
239
+ if not stripped:
240
+ raise ValueError("apiKey is required.")
241
+ return stripped
242
+
243
+
244
+ class CodexRemoteConnectionResponse(BaseModel):
245
+ connection: CodexRemoteConnection
246
+
247
+
248
+ CodexRemoteConnectionRuntimeStatus = Literal[
249
+ "connected",
250
+ "connecting",
251
+ "disconnected",
252
+ "error",
253
+ ]
254
+
255
+
256
+ class CodexRemoteConnectionStatus(BaseModel):
257
+ status: CodexRemoteConnectionRuntimeStatus = "disconnected"
258
+ detail: str = ""
259
+
260
+
261
+ class CodexRemoteConnectionsResponse(BaseModel):
262
+ connections: list[CodexRemoteConnection] = Field(default_factory=list)
263
+ active_connection_id: str = ""
264
+ statuses: dict[str, CodexRemoteConnectionStatus] = Field(default_factory=dict)
265
+
266
+
267
+ class CodexRemoteConnectionTestResponse(BaseModel):
268
+ ok: bool
269
+ detail: str = ""
270
+
271
+
272
+ class WebSettings(BaseModel):
273
+ llm: StoredLLMSettings = Field(default_factory=StoredLLMSettings)
274
+ session_defaults: SessionDefaultsSettings = Field(
275
+ default_factory=SessionDefaultsSettings
276
+ )
277
+ codex: StoredCodexSettings = Field(default_factory=StoredCodexSettings)
278
+ allowed_roots: list[str] = Field(default_factory=list)
279
+
280
+
281
+ class BackendHealth(BaseModel):
282
+ ready: bool
283
+ detail: str | None = None
284
+
285
+
286
+ class BackendOption(BaseModel):
287
+ id: BackendId
288
+ label: str
289
+
290
+
291
+ class CodexConfigPayload(BaseModel):
292
+ launcher_command: str = ""
293
+ model: str = ""
294
+ sandbox: CodexSandboxMode = "workspace-write"
295
+ approval_policy: CodexApprovalPolicy = "on-request"
296
+ approvals_reviewer: CodexApprovalsReviewer = "user"
297
+ personality: CodexPersonality = "friendly"
298
+ reasoning_effort: CodexReasoningEffort = "medium"
299
+ show_reasoning_cards: bool = False
300
+ service_tier: CodexServiceTier = ""
301
+ active_remote_connection_id: str = ""
302
+ remote_connections: list[CodexRemoteConnection] = Field(default_factory=list)
303
+ projects: list[CodexProjectDefinition] = Field(default_factory=list)
304
+
305
+
306
+ class LLMConfigPayload(BaseModel):
307
+ provider: LLMProvider = ""
308
+ base_url: str = ""
309
+ model: str = ""
310
+ has_api_key: bool = False
311
+
312
+
313
+ class MCPRuntimeEntry(BaseModel):
314
+ status: MCPRuntimeStatus
315
+ tool_count: int = 0
316
+ error: str | None = None
317
+
318
+
319
+ class FrontendHealth(BaseModel):
320
+ ready: bool
321
+ mode: FrontendMode
322
+ detail: str | None = None
323
+
324
+
325
+ class LLMHealth(BaseModel):
326
+ ready: bool
327
+ detail: str | None = None
328
+
329
+
330
+ class MCPHealth(BaseModel):
331
+ ready: bool
332
+ detail: str | None = None
333
+ runtime: dict[str, MCPRuntimeEntry] = Field(default_factory=dict)
334
+
335
+
336
+ class HealthResponse(BaseModel):
337
+ frontend: FrontendHealth
338
+ llm: LLMHealth
339
+ mcp: MCPHealth
340
+ backends: dict[BackendId, BackendHealth] = Field(default_factory=dict)
341
+ allowed_roots: list[str] = Field(default_factory=list)
342
+
343
+
344
+ class ConfigResponse(BaseModel):
345
+ llm: LLMConfigPayload
346
+ backends: list[BackendOption] = Field(default_factory=list)
347
+ session_defaults: SessionDefaultsSettings = Field(
348
+ default_factory=SessionDefaultsSettings
349
+ )
350
+ codex: CodexConfigPayload = Field(default_factory=CodexConfigPayload)
351
+ allowed_roots: list[str] = Field(default_factory=list)
352
+ mcp_runtime: dict[str, MCPRuntimeEntry] = Field(default_factory=dict)
353
+
354
+
355
+ class AuthSessionResponse(BaseModel):
356
+ enabled: bool = False
357
+ authenticated: bool = True
358
+
359
+
360
+ class AuthLoginRequest(BaseModel):
361
+ password: str = ""
362
+
363
+ @field_validator("password")
364
+ @classmethod
365
+ def strip_password(cls, value: str) -> str:
366
+ return value.strip()
367
+
368
+
369
+ class SaveLLMRequest(BaseModel):
370
+ provider: LLMProvider = ""
371
+ base_url: str
372
+ model: str
373
+ api_key: str | None = None
374
+
375
+ @field_validator("base_url", "model")
376
+ @classmethod
377
+ def strip_required_fields(cls, value: str) -> str:
378
+ return value.strip()
379
+
380
+
381
+ class SaveAllowedRootsRequest(BaseModel):
382
+ allowed_roots: list[str]
383
+
384
+ @field_validator("allowed_roots")
385
+ @classmethod
386
+ def validate_allowed_roots(cls, value: list[str]) -> list[str]:
387
+ return [item.strip() for item in value if item.strip()]
388
+
389
+
390
+ class MCPConfigResponse(BaseModel):
391
+ mcp_servers: dict[str, dict[str, Any]] = Field(default_factory=dict)
392
+ runtime: dict[str, MCPRuntimeEntry] = Field(default_factory=dict)
393
+
394
+
395
+ class SaveMCPConfigRequest(BaseModel):
396
+ mcp_servers: dict[str, dict[str, Any]]
397
+
398
+
399
+ class SaveAppSettingsRequest(BaseModel):
400
+ session_defaults: SessionDefaultsSettings = Field(
401
+ default_factory=SessionDefaultsSettings
402
+ )
403
+ codex: StoredCodexSettings = Field(default_factory=StoredCodexSettings)
404
+
405
+
406
+ class SelectDirectoryRequest(BaseModel):
407
+ initial_path: str | None = None
408
+
409
+ @field_validator("initial_path")
410
+ @classmethod
411
+ def strip_initial_path(cls, value: str | None) -> str | None:
412
+ if value is None:
413
+ return None
414
+ stripped = value.strip()
415
+ return stripped or None
416
+
417
+
418
+ class SelectDirectoryResponse(BaseModel):
419
+ selected: bool = False
420
+ project_path: str = ""
421
+
422
+
423
+ class ArchiveCodexSessionResponse(BaseModel):
424
+ thread_id: str
425
+ archived: bool = True
426
+
427
+
428
+ class CodexNativeSessionSummary(BaseModel):
429
+ thread_id: str
430
+ host_id: str = "local"
431
+ title: str
432
+ preview: str
433
+ updated_at: float
434
+ started_at: float
435
+ status: str = "idle"
436
+ cwd: str
437
+ project: str
438
+ project_path: str
439
+ source: str = "active"
440
+ model_provider: str = ""
441
+
442
+
443
+ class CodexPairingExtensionSummary(BaseModel):
444
+ id: str
445
+ app_name: str
446
+ workspace_name: str
447
+ extension_name: str
448
+ extension_version: str
449
+ bundle_id: str
450
+ marketplace_id: str
451
+ capability_names: list[str] = Field(default_factory=list)
452
+ capability_count: int = 0
453
+ socket_path: str
454
+ is_online: bool = False
455
+ needs_reload: bool = False
456
+ last_seen_at: float = 0.0
457
+
458
+
459
+ class CodexProjectGroup(BaseModel):
460
+ id: str
461
+ project: str
462
+ project_path: str
463
+ host_id: str = "local"
464
+ kind: CodexProjectKind = "local"
465
+ root_paths: list[str] = Field(default_factory=list)
466
+ session_count: int
467
+ sessions: list[CodexNativeSessionSummary] = Field(default_factory=list)
468
+
469
+
470
+ class CodexWorkspaceResponse(BaseModel):
471
+ projects: list[CodexProjectGroup] = Field(default_factory=list)
472
+ recent_threads: list[CodexNativeSessionSummary] = Field(default_factory=list)
473
+ paired_editors: list[CodexPairingExtensionSummary] = Field(default_factory=list)
474
+ remote_connections: list[CodexRemoteConnection] = Field(default_factory=list)
475
+ active_remote_connection_id: str = ""
476
+ remote_connection_statuses: dict[str, CodexRemoteConnectionStatus] = Field(
477
+ default_factory=dict
478
+ )
479
+
480
+
481
+ CodexFilesystemEntryKind = Literal["directory", "file", "other"]
482
+
483
+
484
+ class CodexFilesystemEntry(BaseModel):
485
+ name: str
486
+ path: str
487
+ kind: CodexFilesystemEntryKind
488
+ extension: str = ""
489
+ readable: bool = True
490
+
491
+
492
+ class CodexFilesystemResponse(BaseModel):
493
+ path: str
494
+ parent_path: str | None = None
495
+ roots: list[CodexFilesystemEntry] = Field(default_factory=list)
496
+ entries: list[CodexFilesystemEntry] = Field(default_factory=list)
497
+
498
+
499
+ class CodexThreadCreateRequest(BaseModel):
500
+ project_path: str | None = None
501
+ host_id: str = "local"
502
+
503
+ @field_validator("project_path")
504
+ @classmethod
505
+ def strip_project_path(cls, value: str | None) -> str | None:
506
+ if value is None:
507
+ return None
508
+ stripped = value.strip()
509
+ return stripped or None
510
+
511
+ @field_validator("host_id")
512
+ @classmethod
513
+ def strip_host_id(cls, value: str) -> str:
514
+ return value.strip() or "local"
515
+
516
+
517
+ class CodexThreadCreateResponse(BaseModel):
518
+ thread_id: str
519
+ host_id: str = "local"
520
+ state: dict[str, Any] | None = None
521
+
522
+
523
+ class CodexThreadStateResponse(BaseModel):
524
+ thread_id: str
525
+ state: dict[str, Any] | None = None
526
+
527
+
528
+ class CodexThreadNameRequest(BaseModel):
529
+ name: str
530
+
531
+ @field_validator("name")
532
+ @classmethod
533
+ def strip_name(cls, value: str) -> str:
534
+ return value.strip()
@@ -0,0 +1 @@
1
+ import{x as y,q as f,h as D,u as H,y as Q,j as X,z as Y,o as Z,c as ee,a as w,A as te,p as s,t as O,b as re,s as J}from"./index-CjVNk6ja.js";import{u as ae,_ as oe,a as se,c as ie}from"./useCodexWorkspace-D1rCOO1x.js";import"./api-CeihACIV.js";const ne=["yier:codex-start","yier:codex-resume","yier:codex-send-prompt","yier:codex-steer-prompt","yier:codex-enqueue-followup","yier:codex-remove-followup","yier:codex-interrupt-turn","yier:codex-compact-thread","yier:codex-set-mode","yier:codex-set-goal","yier:codex-update-goal-status","yier:codex-clear-goal","yier:codex-submit-user-input","yier:codex-rename-thread","yier:codex-archive-thread","yier:codex-fork-thread"],de=new Set(ne),ue=new Set(["active","paused","blocked","usageLimited","budgetLimited","complete"]);function ce(o){return!B(o)||typeof o.type!="string"||!de.has(o.type)?null:o}function le(o,e){return JSON.parse(JSON.stringify({type:o,...e}))}function c(...o){for(const e of o)if(typeof e=="string"&&e.trim())return e.trim();return""}function pe(...o){for(const e of o)if(typeof e=="number"&&Number.isFinite(e)&&e>0)return Math.floor(e);return null}function N(o){return B(o)?o:null}function W(o){const e=c(o).toLowerCase();return e?e==="build"||e==="plan"?{mode:e,error:""}:{mode:null,error:"mode must be build or plan."}:{mode:null,error:""}}function he(o){const e=c(o);return e?ue.has(e)?{status:e,error:""}:{status:null,error:`unsupported goal status: ${e}.`}:{status:null,error:"goal status is required."}}function fe(o){const e=Array.isArray(o.attachments)?o.attachments.filter(B):[];return{prompt:c(o.prompt),model:R(o.model),reasoningEffort:R(o.reasoningEffort,o.reasoning_effort),attachments:e,approvalPolicy:R(o.approvalPolicy,o.approval_policy),approvalsReviewer:R(o.approvalsReviewer,o.approvals_reviewer),sandboxPolicy:N(o.sandboxPolicy??o.sandbox_policy)}}function U(o){const e=Array.isArray(o)&&o.length?o[o.length-1]:null;return e?{turnId:c(e.turnId,e.id),status:c(e.status)||"idle",turnStartedAtMs:C(e.turnStartedAtMs),firstTurnWorkItemStartedAtMs:C(e.firstTurnWorkItemStartedAtMs),finalAssistantStartedAtMs:C(e.finalAssistantStartedAtMs),durationMs:C(e.durationMs),error:e.error??null}:null}function R(...o){return c(...o)||null}function C(o){return typeof o=="number"&&Number.isFinite(o)?o:null}function B(o){return typeof o=="object"&&o!==null&&!Array.isArray(o)}function ve(o){const{source:e,hasStarted:q,isInitializing:t,displayError:l,postMessage:p}=o,m=f(""),g=f(""),T=f(""),x=f(""),M=f(""),h=f("");function k(){return l.value?"failed":e.activeUserInputRequest?"awaiting_approval":t.value||e.isCommandBusy||["inProgress","active","working"].includes(e.activeStatus)?"running":["completed","complete","succeeded","success"].includes(e.activeStatus)?"done":["error","failed"].includes(e.activeStatus)?"failed":e.activeStatus==="planning"?"planning":"idle"}function I(){if(!q.value&&!e.activeThreadId&&!l.value)return;const n=k(),u=e.activeUserInputRequest,v=U(e.activeThreadState?.turns),b=JSON.stringify({status:n,threadId:e.activeThreadId,turnId:v?.turnId??"",rawStatus:e.activeStatus,mode:e.activeMode,requestId:u?.id??"",requestMethod:u?.method??"",error:l.value});b!==m.value&&(m.value=b,p("yier:codex-status",{status:n,threadId:e.activeThreadId,turnId:v?.turnId??"",mode:e.activeMode,codexStatus:e.activeStatus,requestId:u?.id??"",requestMethod:u?.method??"",message:l.value}))}function A(){if(!e.activeThreadId)return;const n=U(e.activeThreadState?.turns),u=JSON.stringify({threadId:e.activeThreadId,turn:n});u!==g.value&&(g.value=u,p("yier:codex-turn-state",{threadId:e.activeThreadId,mode:e.activeMode,turn:n}))}function E(){if(!e.activeThreadId)return;const n=e.activeThreadState?.threadGoal??null,u=e.activeThreadState?.completedThreadGoal??null,v=JSON.stringify({threadId:e.activeThreadId,goal:n,completedGoal:u});v!==T.value&&(T.value=v,p("yier:codex-goal-state",{threadId:e.activeThreadId,goal:n,completedGoal:u}))}function G(){if(!e.activeThreadId)return;const n=`${e.activeThreadId}:${e.activeMode}`;n!==x.value&&(x.value=n,p("yier:codex-mode-changed",{threadId:e.activeThreadId,mode:e.activeMode}))}function F(){if(!e.activeThreadId)return;const n=e.activeUserInputRequest??null,u=JSON.stringify({threadId:e.activeThreadId,request:n});u!==M.value&&(M.value=u,p("yier:codex-user-input-request",{threadId:e.activeThreadId,request:n}))}function d(){if(!e.activeThreadId)return;const n=JSON.stringify({threadId:e.activeThreadId,followups:e.queuedFollowups});n!==h.value&&(h.value=n,p("yier:codex-followups-changed",{threadId:e.activeThreadId,followups:e.queuedFollowups}))}y(()=>[e.activeThreadId,e.activeStatus,e.activeMode,e.activeUserInputRequest?.id??"",e.activeUserInputRequest?.method??"",e.isCommandBusy,t.value,l.value],I),y(()=>JSON.stringify({threadId:e.activeThreadId,turn:U(e.activeThreadState?.turns)}),A),y(()=>JSON.stringify({threadId:e.activeThreadId,goal:e.activeThreadState?.threadGoal??null,completedGoal:e.activeThreadState?.completedThreadGoal??null}),E),y(()=>[e.activeThreadId,e.activeMode],G),y(()=>JSON.stringify({threadId:e.activeThreadId,request:e.activeUserInputRequest??null}),F),y(()=>JSON.stringify({threadId:e.activeThreadId,followups:e.queuedFollowups}),d)}const me={class:"flex h-dvh min-h-0 flex-col overflow-hidden bg-[color:var(--app-bg)]"},ye={class:"grid grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)_minmax(0,1fr)] items-center gap-3 border-b border-[color:var(--app-border)] bg-[color:var(--app-panel-strong)] px-4 py-2 pt-[calc(0.5rem+env(safe-area-inset-top))] max-sm:px-3"},we={class:"flex min-w-0 items-center gap-2 justify-self-start"},ge={class:"truncate text-sm font-semibold text-[color:var(--app-text)]"},Te=["title"],Se=D({__name:"CodexEmbedView",setup(o){const e=H(),q=M("embed_token"),t=Q(ae({autoConnect:!1,persistActiveThread:!1,selectInitialThread:!1,socketUrl:ie(`/api/codex/ws?embed_token=${encodeURIComponent(q)}`)})),l=f(""),p=f(!1),m=f(!1),g=f(!1),T=J(()=>l.value||t.errorMessage),x=J(()=>se(t.activeThreadState)||"Codex embed");function M(r){const a=e.query[r];return Array.isArray(a)?(a[0]??"").trim():typeof a=="string"?a.trim():""}function h(r,a={}){typeof window>"u"||window.parent.postMessage(le(r,a),"*")}ve({source:t,hasStarted:m,isInitializing:p,displayError:T,postMessage:h});function k(r){return{command:r.type,commandId:c(r.commandId)}}function I(r,a,i={}){h("yier:codex-command-result",{...k(r),ok:a,threadId:t.activeThreadId,mode:t.activeMode,...i})}function A(r,a){l.value=a,h("yier:codex-error",{...k(r),message:a}),I(r,!1,{message:a})}function E(){return q?!0:(l.value="embed_token is required.",h("yier:codex-error",{message:l.value}),!1)}async function G(){if(!g.value){if(await t.connect(),t.errorMessage)throw new Error(t.errorMessage);g.value=!0}}function F(){if(!t.activeThreadId)throw new Error("An active thread is required.");return t.activeThreadId}async function d(r){if(l.value="",t.errorMessage="",await r(),t.errorMessage)throw new Error(t.errorMessage)}function n(r){const a=N(r.goal),i=c(typeof r.goal=="string"?r.goal:null,a?.objective,r.objective),S=pe(a?.tokenBudget,a?.token_budget,r.tokenBudget,r.token_budget);return{objective:i,tokenBudget:S}}async function u(r){r&&await d(()=>t.setMode(r))}async function v(r){const{objective:a,tokenBudget:i}=n(r);if(!a){if(r.goal!==void 0||r.objective!==void 0||r.tokenBudget!==void 0||r.token_budget!==void 0)throw new Error("goal objective is required.");return}await d(()=>t.setThreadGoal(a,i))}async function b(r){const a=fe(r);return!a.prompt&&!a.attachments?.length?!1:(await d(()=>t.sendPrompt(a)),h("yier:codex-prompt-sent",{threadId:t.activeThreadId,cwd:t.activeThreadState?.cwd??"",mode:t.activeMode}),!0)}async function $(r){const a=r.type==="yier:codex-start",i=a?c(r.cwd):"",S=a?"":c(r.threadId,r.thread_id),_=W(r.mode);if(_.error)throw new Error(_.error);if(a&&!i||!a&&!S)throw new Error(a?"cwd is required.":"thread_id is required.");m.value=!0,p.value=!0;try{if(await G(),a){const P=await t.startEmbedThread(i);if(!P?.thread_id)throw new Error(t.errorMessage||"Codex thread could not be created.");await u(_.mode),await v(r),h("yier:codex-thread-created",{threadId:P.thread_id,cwd:t.activeThreadState?.cwd??i,mode:t.activeMode})}else{if(!await t.resumeEmbedThread(S))throw new Error(t.errorMessage||"Codex thread could not be resumed.");await u(_.mode),await v(r),h("yier:codex-thread-resumed",{threadId:S,cwd:t.activeThreadState?.cwd??"",mode:t.activeMode})}await b(r)}finally{p.value=!1}}async function z(r){if(!E()){I(r,!1,{message:l.value});return}l.value="",t.errorMessage="";const a=t.activeThreadId;try{r.type==="yier:codex-start"||r.type==="yier:codex-resume"?await $(r):(F(),await K(r)),I(r,!0,{threadId:t.activeThreadId||a})}catch(i){A(r,i instanceof Error?i.message:String(i))}}async function K(r){switch(r.type){case"yier:codex-send-prompt":if(!await b(r))throw new Error("prompt or attachments are required.");return;case"yier:codex-steer-prompt":{const a=c(r.prompt);if(!a)throw new Error("prompt is required.");await d(()=>t.steerPrompt(a));return}case"yier:codex-enqueue-followup":{const a=c(r.prompt);if(!a)throw new Error("prompt is required.");await d(()=>t.enqueueFollowup(a));return}case"yier:codex-remove-followup":{const a=c(r.messageId,r.message_id);if(!a)throw new Error("message_id is required.");await d(()=>t.removeFollowup(a));return}case"yier:codex-interrupt-turn":await d(()=>t.interruptTurn());return;case"yier:codex-compact-thread":await d(()=>t.compactThread());return;case"yier:codex-set-mode":{const a=W(r.mode);if(a.error||!a.mode)throw new Error(a.error||"mode is required.");await u(a.mode);return}case"yier:codex-set-goal":{const{objective:a,tokenBudget:i}=n(r);if(!a)throw new Error("goal objective is required.");await d(()=>t.setThreadGoal(a,i));return}case"yier:codex-update-goal-status":{const a=he(r.status);if(a.error||!a.status)throw new Error(a.error);const i=a.status;await d(()=>t.updateThreadGoalStatus(i));return}case"yier:codex-clear-goal":await d(()=>t.clearThreadGoal());return;case"yier:codex-submit-user-input":{const a=c(r.requestId,r.request_id),i=N(r.response);if(!a)throw new Error("request_id is required.");if(!i)throw new Error("response must be an object.");await d(()=>t.submitUserInputResponse(a,i));return}case"yier:codex-rename-thread":{const a=c(r.name);if(!a)throw new Error("name is required.");await d(()=>t.renameThread(a));return}case"yier:codex-archive-thread":await d(()=>t.archiveThread());return;case"yier:codex-fork-thread":await d(()=>t.forkThread());return;default:throw new Error(`Unsupported iframe command: ${r.type}.`)}}function j(r){if(r.source&&r.source!==window.parent)return;const a=ce(r.data);a&&z(a)}function L(r,a){t.submitUserInputResponse(r,a)}function V(r){l.value=r,h("yier:codex-error",{message:r})}return X(()=>{window.addEventListener("message",j),E()&&h("yier:codex-ready")}),Y(()=>{window.removeEventListener("message",j)}),(r,a)=>(Z(),ee("main",me,[w("div",ye,[w("div",we,[w("span",{class:te(["h-2.5 w-2.5 shrink-0 rounded-full",s(t).status==="open"?"bg-emerald-500":s(t).status==="connecting"?"bg-amber-500":"bg-red-500"])},null,2),w("span",ge," Codex "+O(p.value?"starting":s(t).status),1)]),w("h1",{class:"m-0 min-w-0 truncate text-center text-base font-semibold text-[color:var(--app-text)] max-sm:text-sm",title:x.value},O(x.value),9,Te),a[1]||(a[1]=w("div",{"aria-hidden":"true"},null,-1))]),re(oe,{"active-thread-id":s(t).activeThreadId,"active-thread-state":s(t).activeThreadState,"active-user-input-request":s(t).activeUserInputRequest,"active-status":s(t).activeStatus,"active-mode":s(t).activeMode,"queued-followups":s(t).queuedFollowups,"socket-status":s(t).status,"error-message":T.value,"success-message":s(t).successMessage,"is-command-busy":s(t).isCommandBusy||p.value,"is-renaming":s(t).isRenaming,"is-archiving":s(t).isArchiving,"is-thread-loading":s(t).isActiveThreadLoading,"is-active-turn-in-progress":s(t).isActiveTurnInProgress,"list-skills":s(t).listSkills,"empty-eyebrow":"Codex embed","empty-title":p.value?"Starting thread":m.value?"Waiting for thread":"Waiting for host message",onRenameThread:s(t).renameThread,onArchiveThread:a[0]||(a[0]=i=>s(t).archiveThread()),onCompactThread:s(t).compactThread,onInterruptTurn:s(t).interruptTurn,onSetMode:s(t).setMode,onSetThreadGoal:s(t).setThreadGoal,onUpdateThreadGoalStatus:s(t).updateThreadGoalStatus,onClearThreadGoal:s(t).clearThreadGoal,onRefresh:s(t).refreshWorkspace,onSubmitUserInputResponse:L,onSendPrompt:s(t).sendPrompt,onSteerPrompt:s(t).steerPrompt,onEnqueueFollowup:s(t).enqueueFollowup,onRemoveFollowup:s(t).removeFollowup,onForkThread:s(t).forkThread,onCopyError:V},null,8,["active-thread-id","active-thread-state","active-user-input-request","active-status","active-mode","queued-followups","socket-status","error-message","success-message","is-command-busy","is-renaming","is-archiving","is-thread-loading","is-active-turn-in-progress","list-skills","empty-title","onRenameThread","onCompactThread","onInterruptTurn","onSetMode","onSetThreadGoal","onUpdateThreadGoalStatus","onClearThreadGoal","onRefresh","onSendPrompt","onSteerPrompt","onEnqueueFollowup","onRemoveFollowup","onForkThread"])]))}});export{Se as default};