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.
- open_codex_ui-0.1.4.dist-info/METADATA +249 -0
- open_codex_ui-0.1.4.dist-info/RECORD +40 -0
- open_codex_ui-0.1.4.dist-info/WHEEL +5 -0
- open_codex_ui-0.1.4.dist-info/entry_points.txt +7 -0
- open_codex_ui-0.1.4.dist-info/top_level.txt +1 -0
- yier_web/__init__.py +6 -0
- yier_web/app.py +223 -0
- yier_web/auth.py +269 -0
- yier_web/cli.py +172 -0
- yier_web/codex/__init__.py +3 -0
- yier_web/codex/ipc_manager.py +1761 -0
- yier_web/codex/session_events.py +110 -0
- yier_web/codex/ws_commands.py +299 -0
- yier_web/config.py +651 -0
- yier_web/directory_picker.py +93 -0
- yier_web/event_stream.py +29 -0
- yier_web/frontend.py +204 -0
- yier_web/routes/__init__.py +17 -0
- yier_web/routes/codex.py +573 -0
- yier_web/routes/core.py +281 -0
- yier_web/schemas.py +534 -0
- yier_web/static/assets/CodexEmbedView-CN_-Mhe2.js +1 -0
- yier_web/static/assets/CodexView-wpI61iXa.js +606 -0
- yier_web/static/assets/LoginView-CELCom2O.js +101 -0
- yier_web/static/assets/api-CeihACIV.js +1099 -0
- yier_web/static/assets/index-BdFqJ-Kl.css +1 -0
- yier_web/static/assets/index-CjVNk6ja.js +183 -0
- yier_web/static/assets/index-mSBvq1p8.js +79 -0
- yier_web/static/assets/open-codex-ui-icon-DKJ1ZKj4.svg +99 -0
- yier_web/static/assets/primeicons-C6QP2o4f.woff2 +0 -0
- yier_web/static/assets/primeicons-DMOk5skT.eot +0 -0
- yier_web/static/assets/primeicons-Dr5RGzOO.svg +345 -0
- yier_web/static/assets/primeicons-MpK4pl85.ttf +0 -0
- yier_web/static/assets/primeicons-WjwUDZjB.woff +0 -0
- yier_web/static/assets/useCodexWorkspace-6QenDzvb.css +1 -0
- yier_web/static/assets/useCodexWorkspace-D1rCOO1x.js +1128 -0
- yier_web/static/brand/open-codex-ui-logo.svg +85 -0
- yier_web/static/favicon.ico +0 -0
- yier_web/static/favicon.svg +99 -0
- 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};
|