@researai/deepscientist 1.5.15 → 1.5.17
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.
- package/README.md +385 -104
- package/bin/ds.js +1241 -110
- package/docs/en/00_QUICK_START.md +100 -19
- package/docs/en/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +25 -8
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
- package/docs/en/91_DEVELOPMENT.md +237 -0
- package/docs/en/README.md +24 -2
- package/docs/zh/00_QUICK_START.md +89 -19
- package/docs/zh/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +26 -9
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
- package/docs/zh/README.md +24 -2
- package/install.sh +46 -4
- package/package.json +2 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/service.py +647 -22
- package/src/deepscientist/bash_exec/service.py +234 -9
- package/src/deepscientist/bridges/connectors.py +8 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +367 -22
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +183 -13
- package/src/deepscientist/daemon/api/handlers.py +255 -31
- package/src/deepscientist/daemon/api/router.py +9 -0
- package/src/deepscientist/daemon/app.py +1146 -105
- package/src/deepscientist/diagnostics/__init__.py +6 -0
- package/src/deepscientist/diagnostics/runner_failures.py +130 -0
- package/src/deepscientist/doctor.py +207 -3
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +129 -0
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +39 -0
- package/src/deepscientist/prompts/builder.py +275 -34
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +707 -55
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +143 -43
- package/src/deepscientist/shared.py +19 -0
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +14 -2
- package/src/prompts/system.md +23 -5
- package/src/prompts/system_copilot.md +56 -0
- package/src/skills/analysis-campaign/SKILL.md +1 -0
- package/src/skills/baseline/SKILL.md +8 -0
- package/src/skills/decision/SKILL.md +8 -0
- package/src/skills/experiment/SKILL.md +8 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +1 -0
- package/src/skills/idea/SKILL.md +1 -0
- package/src/skills/intake-audit/SKILL.md +8 -0
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1 -0
- package/src/skills/rebuttal/SKILL.md +1 -0
- package/src/skills/review/SKILL.md +1 -0
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +1 -0
- package/src/tui/dist/app/AppContainer.js +19 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-Bv-Z8YpU.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-BCKAfjba.js +1 -0
- package/src/ui/dist/assets/CliPlugin-BCKcpc35.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-DbOfSJ8K.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-CbaFRrUU.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DAjLVeQD.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-CIUqbUDO.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-CQACjoAA.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-0r4nLPke.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-nBOmI2v_.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-BHxOxF4z.js +14 -0
- package/src/ui/dist/assets/LabPlugin-BKoZGs95.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-ZwtV8pIp.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DKqVfKyW.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BwxStZ9D.js +13 -0
- package/src/ui/dist/assets/NotebookEditor-BEQhaQbt.js +81 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-DB9N_T9q.js +361 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfLoader-eWBONbQP.js +16 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-D22YOZL3.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-c-RK9DLM.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CxF9ytAx.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-C5xqeeUH.js +54 -0
- package/src/ui/dist/assets/VNCViewer-BoLGLnHz.js +11 -0
- package/src/ui/dist/assets/bot-DREQOxzP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-C9Qpx4DE.js +6 -0
- package/src/ui/dist/assets/code-WlFHE7z_.js +6 -0
- package/src/ui/dist/assets/file-content-BZMz3RYp.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-CQhw0jS2.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-CfQPKQKj.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-DxZ8DCZh.js +6 -0
- package/src/ui/dist/assets/image-Bgl4VIyx.js +6 -0
- package/src/ui/dist/assets/index-BpV6lusQ.css +33 -0
- package/src/ui/dist/assets/index-CBNVuWcP.js +2496 -0
- package/src/ui/dist/assets/index-CwNu1aH4.js +11 -0
- package/src/ui/dist/assets/index-DrUnlf6K.js +1 -0
- package/src/ui/dist/assets/index-NW-h8VzN.js +1 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-J8OnM0jE.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-CLc0pPP8.js +1 -0
- package/src/ui/dist/assets/project-sync-C9IdzdZW.js +1 -0
- package/src/ui/dist/assets/select-Cs2PmzwL.js +11 -0
- package/src/ui/dist/assets/sigma-ClKcHAXm.js +6 -0
- package/src/ui/dist/assets/trash-DwpbFr3w.js +11 -0
- package/src/ui/dist/assets/useCliAccess-NQ8m0Let.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-BC-Hltpd.js +11 -0
- package/src/ui/dist/assets/zoom-out-E_gaeAxL.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
- package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
- package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
- package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
- package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
- package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
- package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
- package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
- package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
- package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
- package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
- package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
- package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
- package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
- package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
- package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
- package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import ipaddress
|
|
3
4
|
import json
|
|
4
5
|
import re
|
|
6
|
+
import shutil
|
|
5
7
|
import subprocess
|
|
6
8
|
import tomllib
|
|
7
9
|
from functools import lru_cache
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
from .shared import ensure_dir, read_text, write_text
|
|
8
14
|
|
|
9
15
|
_MIN_XHIGH_SUPPORTED_VERSION = (0, 63, 0)
|
|
16
|
+
_CHAT_WIRE_COMPAT_VERSION = (0, 57, 0)
|
|
10
17
|
_CODEX_VERSION_PATTERN = re.compile(r"codex-cli\s+(\d+)\.(\d+)\.(\d+)", re.IGNORECASE)
|
|
18
|
+
_CODEX_HOME_SYNCED_FILES = ("config.toml", "auth.json")
|
|
19
|
+
_CODEX_HOME_SYNCED_DIRS = ("skills", "agents", "prompts")
|
|
20
|
+
_CODEX_HOME_QUEST_OVERLAY_DIRS = ("skills", "prompts")
|
|
21
|
+
_ROOT_TABLE_SECTION_PATTERN = re.compile(r"^\s*\[")
|
|
22
|
+
_ROOT_MODEL_ASSIGNMENT_PATTERN = re.compile(r"^\s*(model_provider|model)\s*=")
|
|
23
|
+
_COMPAT_BEGIN_MARKER = "# BEGIN DEEPSCIENTIST PROFILE COMPAT"
|
|
24
|
+
_COMPAT_END_MARKER = "# END DEEPSCIENTIST PROFILE COMPAT"
|
|
25
|
+
_MISSING_ENV_PATTERN = re.compile(r"Missing environment variable:\s*[`'\"]?([^`'\"\s]+)", re.IGNORECASE)
|
|
26
|
+
_LOCAL_PROVIDER_HOST_ALIASES = {"localhost", "host.docker.internal"}
|
|
11
27
|
|
|
12
28
|
|
|
13
29
|
def parse_codex_cli_version(text: str) -> tuple[int, int, int] | None:
|
|
@@ -41,6 +57,50 @@ def format_codex_cli_version(version: tuple[int, int, int] | None) -> str:
|
|
|
41
57
|
return ".".join(str(part) for part in version)
|
|
42
58
|
|
|
43
59
|
|
|
60
|
+
def chat_wire_compatible_codex_version() -> tuple[int, int, int]:
|
|
61
|
+
return _CHAT_WIRE_COMPAT_VERSION
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _split_root_table_lines(config_text: str) -> tuple[list[str], list[str]]:
|
|
65
|
+
lines = str(config_text or "").splitlines()
|
|
66
|
+
for index, line in enumerate(lines):
|
|
67
|
+
if _ROOT_TABLE_SECTION_PATTERN.match(line):
|
|
68
|
+
return lines[:index], lines[index:]
|
|
69
|
+
return lines, []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _strip_root_model_assignments(lines: list[str]) -> list[str]:
|
|
73
|
+
filtered: list[str] = []
|
|
74
|
+
skipping_compat_block = False
|
|
75
|
+
for line in lines:
|
|
76
|
+
stripped = line.strip()
|
|
77
|
+
if stripped == _COMPAT_BEGIN_MARKER:
|
|
78
|
+
skipping_compat_block = True
|
|
79
|
+
continue
|
|
80
|
+
if skipping_compat_block:
|
|
81
|
+
if stripped == _COMPAT_END_MARKER:
|
|
82
|
+
skipping_compat_block = False
|
|
83
|
+
continue
|
|
84
|
+
if _ROOT_MODEL_ASSIGNMENT_PATTERN.match(line):
|
|
85
|
+
continue
|
|
86
|
+
filtered.append(line)
|
|
87
|
+
while filtered and not filtered[0].strip():
|
|
88
|
+
filtered.pop(0)
|
|
89
|
+
while filtered and not filtered[-1].strip():
|
|
90
|
+
filtered.pop()
|
|
91
|
+
return filtered
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _join_field_names(fields: list[str]) -> str:
|
|
95
|
+
if not fields:
|
|
96
|
+
return ""
|
|
97
|
+
if len(fields) == 1:
|
|
98
|
+
return fields[0]
|
|
99
|
+
if len(fields) == 2:
|
|
100
|
+
return f"{fields[0]} and {fields[1]}"
|
|
101
|
+
return ", ".join(fields[:-1]) + f", and {fields[-1]}"
|
|
102
|
+
|
|
103
|
+
|
|
44
104
|
def normalize_codex_reasoning_effort(
|
|
45
105
|
reasoning_effort: str | None,
|
|
46
106
|
*,
|
|
@@ -86,32 +146,317 @@ def adapt_profile_only_provider_config(
|
|
|
86
146
|
if not isinstance(profile_payload, dict):
|
|
87
147
|
return config_text, None
|
|
88
148
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
149
|
+
profile_model_provider = str(profile_payload.get("model_provider") or "").strip()
|
|
150
|
+
profile_model = str(profile_payload.get("model") or "").strip()
|
|
151
|
+
top_level_model_provider = str(parsed.get("model_provider") or "").strip()
|
|
152
|
+
top_level_model = str(parsed.get("model") or "").strip()
|
|
153
|
+
|
|
154
|
+
root_lines: list[str] = []
|
|
155
|
+
changed_fields: list[str] = []
|
|
156
|
+
conflicted_fields: list[str] = []
|
|
157
|
+
if profile_model_provider and top_level_model_provider != profile_model_provider:
|
|
158
|
+
root_lines.append(f"model_provider = {json.dumps(profile_model_provider, ensure_ascii=False)}")
|
|
159
|
+
changed_fields.append("model_provider")
|
|
160
|
+
if top_level_model_provider:
|
|
161
|
+
conflicted_fields.append("model_provider")
|
|
162
|
+
elif profile_model_provider:
|
|
163
|
+
root_lines.append(f"model_provider = {json.dumps(profile_model_provider, ensure_ascii=False)}")
|
|
164
|
+
if profile_model and top_level_model != profile_model:
|
|
165
|
+
root_lines.append(f"model = {json.dumps(profile_model, ensure_ascii=False)}")
|
|
166
|
+
changed_fields.append("model")
|
|
167
|
+
if top_level_model:
|
|
168
|
+
conflicted_fields.append("model")
|
|
169
|
+
elif profile_model:
|
|
170
|
+
root_lines.append(f"model = {json.dumps(profile_model, ensure_ascii=False)}")
|
|
171
|
+
|
|
172
|
+
if not changed_fields:
|
|
103
173
|
return config_text, None
|
|
104
174
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
175
|
+
root_prefix, body_lines = _split_root_table_lines(config_text)
|
|
176
|
+
cleaned_root = _strip_root_model_assignments(root_prefix)
|
|
177
|
+
adapted_lines: list[str] = [
|
|
178
|
+
_COMPAT_BEGIN_MARKER,
|
|
179
|
+
*root_lines,
|
|
180
|
+
_COMPAT_END_MARKER,
|
|
181
|
+
]
|
|
182
|
+
if cleaned_root:
|
|
183
|
+
adapted_lines.append("")
|
|
184
|
+
adapted_lines.extend(cleaned_root)
|
|
185
|
+
if body_lines:
|
|
186
|
+
adapted_lines.append("")
|
|
187
|
+
adapted_lines.extend(body_lines)
|
|
188
|
+
adapted = "\n".join(adapted_lines).rstrip() + "\n"
|
|
189
|
+
field_text = _join_field_names(changed_fields)
|
|
111
190
|
return (
|
|
112
191
|
adapted,
|
|
113
192
|
(
|
|
114
|
-
f"DeepScientist
|
|
115
|
-
f"{
|
|
193
|
+
f"DeepScientist overrode conflicting top-level {field_text} with values from profile "
|
|
194
|
+
f"`{normalized_profile}` for Codex compatibility."
|
|
195
|
+
if conflicted_fields
|
|
196
|
+
else f"DeepScientist promoted `{normalized_profile}` profile {field_text} to the top level for Codex compatibility."
|
|
116
197
|
),
|
|
117
198
|
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _remove_tree_path(path: Path) -> None:
|
|
202
|
+
if not path.exists() and not path.is_symlink():
|
|
203
|
+
return
|
|
204
|
+
if path.is_symlink() or path.is_file():
|
|
205
|
+
path.unlink()
|
|
206
|
+
return
|
|
207
|
+
shutil.rmtree(path)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _overlay_file_sources(*roots: Path) -> dict[Path, Path]:
|
|
211
|
+
merged: dict[Path, Path] = {}
|
|
212
|
+
for root in roots:
|
|
213
|
+
if not root.exists() or not root.is_dir():
|
|
214
|
+
continue
|
|
215
|
+
for source_path in sorted(root.rglob("*")):
|
|
216
|
+
if not source_path.is_file():
|
|
217
|
+
continue
|
|
218
|
+
merged[source_path.relative_to(root)] = source_path
|
|
219
|
+
return merged
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _sync_overlay_directory(target_dir: Path, *source_dirs: Path) -> None:
|
|
223
|
+
desired_files = _overlay_file_sources(*source_dirs)
|
|
224
|
+
if not desired_files:
|
|
225
|
+
_remove_tree_path(target_dir)
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
desired_dirs: set[Path] = {Path(".")}
|
|
229
|
+
for relative in desired_files:
|
|
230
|
+
parent = relative.parent
|
|
231
|
+
while True:
|
|
232
|
+
desired_dirs.add(parent)
|
|
233
|
+
if parent == Path("."):
|
|
234
|
+
break
|
|
235
|
+
parent = parent.parent
|
|
236
|
+
|
|
237
|
+
if target_dir.exists() or target_dir.is_symlink():
|
|
238
|
+
for existing_path in sorted(target_dir.rglob("*"), reverse=True):
|
|
239
|
+
relative = existing_path.relative_to(target_dir)
|
|
240
|
+
if existing_path.is_dir() and not existing_path.is_symlink():
|
|
241
|
+
if relative not in desired_dirs:
|
|
242
|
+
shutil.rmtree(existing_path)
|
|
243
|
+
continue
|
|
244
|
+
if relative not in desired_files:
|
|
245
|
+
existing_path.unlink()
|
|
246
|
+
|
|
247
|
+
ensure_dir(target_dir)
|
|
248
|
+
for relative in sorted(desired_dirs, key=lambda item: (len(item.parts), item.as_posix())):
|
|
249
|
+
if relative == Path("."):
|
|
250
|
+
continue
|
|
251
|
+
current = target_dir / relative
|
|
252
|
+
if current.exists() and (current.is_symlink() or current.is_file()):
|
|
253
|
+
current.unlink()
|
|
254
|
+
ensure_dir(current)
|
|
255
|
+
|
|
256
|
+
for relative, source_path in desired_files.items():
|
|
257
|
+
target_path = target_dir / relative
|
|
258
|
+
if target_path.exists() and target_path.is_dir() and not target_path.is_symlink():
|
|
259
|
+
shutil.rmtree(target_path)
|
|
260
|
+
ensure_dir(target_path.parent)
|
|
261
|
+
try:
|
|
262
|
+
same_path = source_path.resolve() == target_path.resolve()
|
|
263
|
+
except FileNotFoundError:
|
|
264
|
+
same_path = False
|
|
265
|
+
if same_path:
|
|
266
|
+
continue
|
|
267
|
+
shutil.copy2(source_path, target_path)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def materialize_codex_runtime_home(
|
|
271
|
+
*,
|
|
272
|
+
source_home: str | Path,
|
|
273
|
+
target_home: str | Path,
|
|
274
|
+
profile: str = "",
|
|
275
|
+
quest_codex_root: str | Path | None = None,
|
|
276
|
+
) -> str | None:
|
|
277
|
+
source_root = Path(source_home).expanduser()
|
|
278
|
+
target_root = ensure_dir(Path(target_home))
|
|
279
|
+
|
|
280
|
+
for filename in _CODEX_HOME_SYNCED_FILES:
|
|
281
|
+
source_path = source_root / filename
|
|
282
|
+
target_path = target_root / filename
|
|
283
|
+
if not source_path.exists():
|
|
284
|
+
_remove_tree_path(target_path)
|
|
285
|
+
continue
|
|
286
|
+
if target_path.exists() and target_path.is_dir() and not target_path.is_symlink():
|
|
287
|
+
shutil.rmtree(target_path)
|
|
288
|
+
ensure_dir(target_path.parent)
|
|
289
|
+
try:
|
|
290
|
+
same_path = source_path.resolve() == target_path.resolve()
|
|
291
|
+
except FileNotFoundError:
|
|
292
|
+
same_path = False
|
|
293
|
+
if not same_path:
|
|
294
|
+
shutil.copy2(source_path, target_path)
|
|
295
|
+
|
|
296
|
+
overlay_root = Path(quest_codex_root) if quest_codex_root is not None else None
|
|
297
|
+
for dirname in _CODEX_HOME_SYNCED_DIRS:
|
|
298
|
+
overlay_dir = overlay_root / dirname if overlay_root is not None and dirname in _CODEX_HOME_QUEST_OVERLAY_DIRS else None
|
|
299
|
+
source_dirs: list[Path] = [source_root / dirname]
|
|
300
|
+
if overlay_dir is not None:
|
|
301
|
+
source_dirs.append(overlay_dir)
|
|
302
|
+
_sync_overlay_directory(target_root / dirname, *source_dirs)
|
|
303
|
+
|
|
304
|
+
warning: str | None = None
|
|
305
|
+
config_path = target_root / "config.toml"
|
|
306
|
+
if profile and config_path.exists():
|
|
307
|
+
adapted_text, warning = adapt_profile_only_provider_config(read_text(config_path), profile=profile)
|
|
308
|
+
write_text(config_path, adapted_text)
|
|
309
|
+
return warning
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _empty_provider_metadata() -> dict[str, str | bool | None]:
|
|
313
|
+
return {
|
|
314
|
+
"provider": None,
|
|
315
|
+
"model": None,
|
|
316
|
+
"env_key": None,
|
|
317
|
+
"base_url": None,
|
|
318
|
+
"wire_api": None,
|
|
319
|
+
"requires_openai_auth": None,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def active_provider_metadata(
|
|
324
|
+
config_text: str,
|
|
325
|
+
*,
|
|
326
|
+
profile: str | None = None,
|
|
327
|
+
) -> dict[str, str | bool | None]:
|
|
328
|
+
normalized_profile = str(profile or "").strip()
|
|
329
|
+
if not str(config_text or "").strip():
|
|
330
|
+
return _empty_provider_metadata()
|
|
331
|
+
try:
|
|
332
|
+
parsed = tomllib.loads(config_text)
|
|
333
|
+
except tomllib.TOMLDecodeError:
|
|
334
|
+
return _empty_provider_metadata()
|
|
335
|
+
|
|
336
|
+
profile_payload: dict | None = None
|
|
337
|
+
if normalized_profile:
|
|
338
|
+
profiles = parsed.get("profiles")
|
|
339
|
+
if not isinstance(profiles, dict):
|
|
340
|
+
return _empty_provider_metadata()
|
|
341
|
+
candidate_profile = profiles.get(normalized_profile)
|
|
342
|
+
if not isinstance(candidate_profile, dict):
|
|
343
|
+
return _empty_provider_metadata()
|
|
344
|
+
profile_payload = candidate_profile
|
|
345
|
+
|
|
346
|
+
model_provider = str(
|
|
347
|
+
(profile_payload or {}).get("model_provider")
|
|
348
|
+
or parsed.get("model_provider")
|
|
349
|
+
or ""
|
|
350
|
+
).strip() or None
|
|
351
|
+
model = str(
|
|
352
|
+
(profile_payload or {}).get("model")
|
|
353
|
+
or parsed.get("model")
|
|
354
|
+
or ""
|
|
355
|
+
).strip() or None
|
|
356
|
+
provider_payload = None
|
|
357
|
+
model_providers = parsed.get("model_providers")
|
|
358
|
+
if model_provider and isinstance(model_providers, dict):
|
|
359
|
+
candidate = model_providers.get(model_provider)
|
|
360
|
+
if isinstance(candidate, dict):
|
|
361
|
+
provider_payload = candidate
|
|
362
|
+
|
|
363
|
+
env_key = (
|
|
364
|
+
str(provider_payload.get("env_key") or "").strip()
|
|
365
|
+
if isinstance(provider_payload, dict)
|
|
366
|
+
else None
|
|
367
|
+
) or None
|
|
368
|
+
base_url = (
|
|
369
|
+
str(provider_payload.get("base_url") or "").strip()
|
|
370
|
+
if isinstance(provider_payload, dict)
|
|
371
|
+
else None
|
|
372
|
+
) or None
|
|
373
|
+
wire_api = (
|
|
374
|
+
str(provider_payload.get("wire_api") or "").strip()
|
|
375
|
+
if isinstance(provider_payload, dict)
|
|
376
|
+
else None
|
|
377
|
+
) or None
|
|
378
|
+
requires_openai_auth = (
|
|
379
|
+
bool(provider_payload.get("requires_openai_auth"))
|
|
380
|
+
if isinstance(provider_payload, dict) and "requires_openai_auth" in provider_payload
|
|
381
|
+
else None
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
"provider": model_provider,
|
|
386
|
+
"model": model,
|
|
387
|
+
"env_key": env_key,
|
|
388
|
+
"base_url": base_url,
|
|
389
|
+
"wire_api": wire_api,
|
|
390
|
+
"requires_openai_auth": requires_openai_auth,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def provider_profile_metadata(
|
|
395
|
+
config_text: str,
|
|
396
|
+
*,
|
|
397
|
+
profile: str,
|
|
398
|
+
) -> dict[str, str | bool | None]:
|
|
399
|
+
normalized_profile = str(profile or "").strip()
|
|
400
|
+
if not normalized_profile:
|
|
401
|
+
return _empty_provider_metadata()
|
|
402
|
+
return active_provider_metadata(config_text, profile=normalized_profile)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def provider_profile_metadata_from_home(
|
|
406
|
+
config_home: str | Path,
|
|
407
|
+
*,
|
|
408
|
+
profile: str,
|
|
409
|
+
) -> dict[str, str | bool | None]:
|
|
410
|
+
config_path = Path(config_home).expanduser() / "config.toml"
|
|
411
|
+
if not config_path.exists():
|
|
412
|
+
return _empty_provider_metadata()
|
|
413
|
+
return provider_profile_metadata(config_path.read_text(encoding="utf-8"), profile=profile)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def provider_base_url_looks_local(base_url: str | None) -> bool:
|
|
417
|
+
normalized = str(base_url or "").strip()
|
|
418
|
+
if not normalized:
|
|
419
|
+
return False
|
|
420
|
+
parsed = urlparse(normalized)
|
|
421
|
+
hostname = str(parsed.hostname or "").strip().lower()
|
|
422
|
+
if not hostname:
|
|
423
|
+
return False
|
|
424
|
+
if hostname in _LOCAL_PROVIDER_HOST_ALIASES or hostname.endswith(".local"):
|
|
425
|
+
return True
|
|
426
|
+
try:
|
|
427
|
+
ip = ipaddress.ip_address(hostname)
|
|
428
|
+
except ValueError:
|
|
429
|
+
return False
|
|
430
|
+
return ip.is_loopback or ip.is_private or ip.is_link_local or ip.is_unspecified
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def missing_provider_env_key(
|
|
434
|
+
metadata: dict[str, str | bool | None],
|
|
435
|
+
env: dict[str, str] | None,
|
|
436
|
+
) -> str | None:
|
|
437
|
+
env_key = str((metadata or {}).get("env_key") or "").strip()
|
|
438
|
+
if not env_key:
|
|
439
|
+
return None
|
|
440
|
+
env_value = str((env or {}).get(env_key) or "").strip()
|
|
441
|
+
if env_value:
|
|
442
|
+
return None
|
|
443
|
+
return env_key
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def missing_provider_env_key_from_text(*texts: str) -> str | None:
|
|
447
|
+
for text in texts:
|
|
448
|
+
match = _MISSING_ENV_PATTERN.search(str(text or ""))
|
|
449
|
+
if match:
|
|
450
|
+
return str(match.group(1) or "").strip() or None
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def active_provider_metadata_from_home(
|
|
455
|
+
config_home: str | Path,
|
|
456
|
+
*,
|
|
457
|
+
profile: str | None = None,
|
|
458
|
+
) -> dict[str, str | bool | None]:
|
|
459
|
+
config_path = Path(config_home).expanduser() / "config.toml"
|
|
460
|
+
if not config_path.exists():
|
|
461
|
+
return _empty_provider_metadata()
|
|
462
|
+
return active_provider_metadata(config_path.read_text(encoding="utf-8"), profile=profile)
|
|
@@ -38,6 +38,7 @@ def default_config(home: Path) -> dict:
|
|
|
38
38
|
"ui": {
|
|
39
39
|
"host": "0.0.0.0",
|
|
40
40
|
"port": 20999,
|
|
41
|
+
"auth_enabled": False,
|
|
41
42
|
"auto_open_browser": True,
|
|
42
43
|
"default_mode": "web",
|
|
43
44
|
},
|
|
@@ -96,7 +97,7 @@ def default_runners() -> dict:
|
|
|
96
97
|
"binary": "codex",
|
|
97
98
|
"config_dir": "~/.codex",
|
|
98
99
|
"profile": "",
|
|
99
|
-
"model": "
|
|
100
|
+
"model": "inherit",
|
|
100
101
|
"model_reasoning_effort": "xhigh",
|
|
101
102
|
"approval_policy": "never",
|
|
102
103
|
"sandbox_mode": "danger-full-access",
|