tagword-codeagent 1.1.6__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.
- codeagent/__init__.py +10 -0
- codeagent/__main__.py +13 -0
- codeagent/cli/__init__.py +13 -0
- codeagent/cli/entrypoint.py +161 -0
- codeagent/cli/main.py +979 -0
- codeagent/core/__init__.py +2 -0
- codeagent/core/attachments.py +342 -0
- codeagent/core/audio_models.py +50 -0
- codeagent/core/bootstrap.py +178 -0
- codeagent/core/default_presets.py +159 -0
- codeagent/core/env.py +106 -0
- codeagent/core/folder_picker.py +151 -0
- codeagent/core/image_understanding.py +90 -0
- codeagent/core/models.py +6 -0
- codeagent/core/paths.py +238 -0
- codeagent/core/pricing.py +107 -0
- codeagent/core/process_ports.py +258 -0
- codeagent/core/seed_bridge.py +21 -0
- codeagent/core/settings.py +98 -0
- codeagent/core/speech_synth.py +330 -0
- codeagent/core/team_config.py +247 -0
- codeagent/core/team_manager.py +245 -0
- codeagent/core/token_counter.py +41 -0
- codeagent/core/tts_voices.py +103 -0
- codeagent/core/usage_billing.py +72 -0
- codeagent/core/vision_models.py +93 -0
- codeagent/memory/diary.py +144 -0
- codeagent/persona_defaults/README.md +12 -0
- codeagent/persona_defaults/agent.md +352 -0
- codeagent/persona_defaults/identity.md +29 -0
- codeagent/persona_defaults/memory.md +4 -0
- codeagent/persona_defaults/skills.md +325 -0
- codeagent/persona_defaults/soul.md +38 -0
- codeagent/persona_defaults/tools.md +41 -0
- codeagent/persona_defaults/user.md +18 -0
- codeagent/runtime/__init__.py +11 -0
- codeagent/runtime/agent_context.py +6 -0
- codeagent/runtime/agent_runtime.py +21 -0
- codeagent/runtime/compact_prompt.py +9 -0
- codeagent/runtime/compact_state.py +40 -0
- codeagent/runtime/llm_worker.py +132 -0
- codeagent/runtime/orchestrator.py +161 -0
- codeagent/runtime/prompt_enrichment.py +324 -0
- codeagent/runtime/session_manager.py +30 -0
- codeagent/runtime/task_split.py +38 -0
- codeagent/runtime/turn_loop.py +13 -0
- codeagent/runtime/worker.py +48 -0
- codeagent/server/__init__.py +505 -0
- codeagent/server/app_factory.py +1292 -0
- codeagent/server/attachment_api.py +261 -0
- codeagent/server/hub.py +175 -0
- codeagent/server/icon.png +0 -0
- codeagent/server/self_healing.py +143 -0
- codeagent/server/team_engine.py +243 -0
- codeagent/server/tts_api.py +51 -0
- codeagent/server/webui_api_app.py +3180 -0
- codeagent/skills/__init__.py +2 -0
- codeagent/skills/select.py +144 -0
- codeagent/tools/__init__.py +6 -0
- codeagent/tools/agent_tools.py +10 -0
- codeagent/web/__init__.py +1 -0
- codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
- codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
- codeagent/web/auth_impl.py +326 -0
- codeagent/web/static/00-marked.min.js +6 -0
- codeagent/web/static/00-mermaid.min.js +2314 -0
- codeagent/web/static/00-purify.min.js +3 -0
- codeagent/web/static/00-storage.js +83 -0
- codeagent/web/static/00-utils.js +51 -0
- codeagent/web/static/01-theme.css +96 -0
- codeagent/web/static/01a-scroll.js +86 -0
- codeagent/web/static/01b-think.js +62 -0
- codeagent/web/static/01c-session-identity.js +81 -0
- codeagent/web/static/01c-session-thinking.js +61 -0
- codeagent/web/static/01c-session-tree-menus.js +349 -0
- codeagent/web/static/01c-session-tree.js +531 -0
- codeagent/web/static/01c-session-utilities.js +83 -0
- codeagent/web/static/01c-vision-model.js +132 -0
- codeagent/web/static/01d-image-gen-model.js +84 -0
- codeagent/web/static/01e-audio-model.js +97 -0
- codeagent/web/static/01f-compose-ui.js +88 -0
- codeagent/web/static/01g-tts-settings.js +128 -0
- codeagent/web/static/01h-music-model.js +85 -0
- codeagent/web/static/01i-model-stack.js +291 -0
- codeagent/web/static/01j-video-gen-model.js +85 -0
- codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
- codeagent/web/static/01k-model-stack-state.js +209 -0
- codeagent/web/static/01q-inflight.js +195 -0
- codeagent/web/static/02-reset.css +22 -0
- codeagent/web/static/02-session-ui.js +201 -0
- codeagent/web/static/03-layout-p1.css +2 -0
- codeagent/web/static/03-layout-p2.css +2 -0
- codeagent/web/static/03a-links-stream.js +88 -0
- codeagent/web/static/03b-stream-text.js +119 -0
- codeagent/web/static/03c-render-bubble.js +520 -0
- codeagent/web/static/03d-agent-bubble.js +126 -0
- codeagent/web/static/03e-tool-trace.js +182 -0
- codeagent/web/static/03f-timeline.js +54 -0
- codeagent/web/static/03g-bubble-tts.js +292 -0
- codeagent/web/static/04-components-p1.css +1 -0
- codeagent/web/static/04-components-p2b.css +1 -0
- codeagent/web/static/04-components-p3.css +2 -0
- codeagent/web/static/04-components-p4.css +1 -0
- codeagent/web/static/04-components-p5.css +1 -0
- codeagent/web/static/04-components-p6.css +58 -0
- codeagent/web/static/04-components-p7.css +4 -0
- codeagent/web/static/04-components-p8.css +1 -0
- codeagent/web/static/04-components-p9.css +1 -0
- codeagent/web/static/04-ws-connect.js +351 -0
- codeagent/web/static/04-ws-helpers.js +116 -0
- codeagent/web/static/05-chat-p1.css +1058 -0
- codeagent/web/static/05-chat-p2.css +1 -0
- codeagent/web/static/05-chat-p3.css +295 -0
- codeagent/web/static/05a-session-history.js +176 -0
- codeagent/web/static/05b-session-list.js +114 -0
- codeagent/web/static/05c-dialog.js +123 -0
- codeagent/web/static/05d-project-create.js +172 -0
- codeagent/web/static/05e-project-remote.js +143 -0
- codeagent/web/static/06-chat.js +552 -0
- codeagent/web/static/06-utilities.css +181 -0
- codeagent/web/static/06b-attachments.js +304 -0
- codeagent/web/static/06c-camera.js +488 -0
- codeagent/web/static/07-activity-bar.css +127 -0
- codeagent/web/static/09-git-p1.css +1 -0
- codeagent/web/static/09-git-p2.css +1 -0
- codeagent/web/static/09a-init-bind.js +110 -0
- codeagent/web/static/09b-panel-toggle.js +80 -0
- codeagent/web/static/09c-api-refresh.js +100 -0
- codeagent/web/static/09d-status-log.js +80 -0
- codeagent/web/static/09e-commit.js +80 -0
- codeagent/web/static/09f-remote.js +93 -0
- codeagent/web/static/11a-cron-parse.js +117 -0
- codeagent/web/static/11b-cron-panel.js +100 -0
- codeagent/web/static/11c-cron-card.js +133 -0
- codeagent/web/static/11d-cron-form.js +105 -0
- codeagent/web/static/11e-env-config.js +168 -0
- codeagent/web/static/11f-env-llm-a-state.js +136 -0
- codeagent/web/static/11f-env-llm-b-form.js +409 -0
- codeagent/web/static/11f-env-llm-c-board.js +322 -0
- codeagent/web/static/11g-env-paths-git.js +973 -0
- codeagent/web/static/11h-env-mcp-a-state.js +161 -0
- codeagent/web/static/11h-env-mcp-b-form.js +229 -0
- codeagent/web/static/11h-env-mcp-c-board.js +565 -0
- codeagent/web/static/12-plugins.js +127 -0
- codeagent/web/static/13a-skills.js +156 -0
- codeagent/web/static/13a-todo-utils.js +140 -0
- codeagent/web/static/13b-todo-fetch.js +106 -0
- codeagent/web/static/13b-tools.js +127 -0
- codeagent/web/static/13c-todo-crud.js +92 -0
- codeagent/web/static/14-activity-bar.js +177 -0
- codeagent/web/static/15-plans.css +194 -0
- codeagent/web/static/15-plans.js +219 -0
- codeagent/web/static/16-mobile.css +612 -0
- codeagent/web/static/16-mobile.js +178 -0
- codeagent/web/static/17-preset-ux.css +287 -0
- codeagent/web/static/18-agent-mgr.css +616 -0
- codeagent/web/static/18-agent-mgr.js +204 -0
- codeagent/web/static/24-health-dashboard.js +68 -0
- codeagent/web/static/99-brand-polish.css +152 -0
- codeagent/web/static/body.html +824 -0
- codeagent/web/static/setup/setup_form.html +171 -0
- codeagent/web/static/setup/setup_head.html +100 -0
- codeagent/web/static/setup/setup_tail.html +264 -0
- codeagent/web/static/setup/setup_test.html +38 -0
- codeagent/web/web_login.html +265 -0
- codeagent/web/webui.html +35 -0
- tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
- tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
- tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
- tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
- tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
- tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Per-use-type default preset IDs.
|
|
2
|
+
|
|
3
|
+
Extends seed's `get_default_preset_id()` (which is a single chat-only default)
|
|
4
|
+
with a multi-slot default map: ``{chat: 'preset-abc', vision: 'preset-xyz', ...}``.
|
|
5
|
+
|
|
6
|
+
Storage:
|
|
7
|
+
- Primary: ``<config_dir>/default_presets.json`` (a JSON object)
|
|
8
|
+
- Legacy fallback: ``<config_dir>/codeagent.default_preset_ids.json``
|
|
9
|
+
- Ultimate fallback: seed's ``seed.default_model`` (chat only)
|
|
10
|
+
|
|
11
|
+
Slots: ``chat``, ``vision``, ``image_gen``, ``audio``, ``music``, ``video_gen``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Ordered list of supported slots. "chat" must come first for legacy compat.
|
|
24
|
+
SLOTS: tuple[str, ...] = (
|
|
25
|
+
"chat",
|
|
26
|
+
"vision",
|
|
27
|
+
"image_gen",
|
|
28
|
+
"audio",
|
|
29
|
+
"music",
|
|
30
|
+
"video_gen",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
DEFAULT_PRESET_IDS_FILENAME = "default_presets.json"
|
|
34
|
+
LEGACY_PRESET_IDS_FILENAME = "codeagent.default_preset_ids.json"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _config_dir() -> Path:
|
|
38
|
+
"""Resolve the same config dir seed.core.llm_presets uses."""
|
|
39
|
+
try:
|
|
40
|
+
from seed.core.llm_presets import _config_dir as _seed_config_dir # type: ignore
|
|
41
|
+
return _seed_config_dir()
|
|
42
|
+
except Exception:
|
|
43
|
+
# Fallback (shouldn't normally trigger): SEED_PROJECT_ROOT/config
|
|
44
|
+
import os
|
|
45
|
+
root = os.environ.get("SEED_PROJECT_ROOT", "").strip() or "."
|
|
46
|
+
return Path(root) / "config"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _new_path() -> Path:
|
|
50
|
+
return _config_dir() / DEFAULT_PRESET_IDS_FILENAME
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _legacy_path() -> Path | None:
|
|
54
|
+
"""Path of the legacy single-id file from seed, if any."""
|
|
55
|
+
try:
|
|
56
|
+
from seed.core.llm_presets import _default_id_read_path # type: ignore
|
|
57
|
+
p = _default_id_read_path()
|
|
58
|
+
return p if p is not None else None
|
|
59
|
+
except Exception:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _read_legacy_single_id() -> str:
|
|
64
|
+
p = _legacy_path()
|
|
65
|
+
if p is None or not p.is_file():
|
|
66
|
+
return ""
|
|
67
|
+
try:
|
|
68
|
+
return p.read_text(encoding="utf-8").strip()
|
|
69
|
+
except OSError:
|
|
70
|
+
return ""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_default_preset_ids() -> Dict[str, str]:
|
|
74
|
+
"""Return a mapping ``{slot: preset_id}`` for every slot that has a default.
|
|
75
|
+
|
|
76
|
+
Reads the new JSON map first; falls back to ``codeagent.default_preset_ids.json``
|
|
77
|
+
(legacy), then the seed single-id file (assigned to ``chat``).
|
|
78
|
+
"""
|
|
79
|
+
p = _new_path()
|
|
80
|
+
if p.is_file():
|
|
81
|
+
try:
|
|
82
|
+
data = json.loads(p.read_text(encoding="utf-8") or "{}")
|
|
83
|
+
except (json.JSONDecodeError, OSError):
|
|
84
|
+
data = {}
|
|
85
|
+
if isinstance(data, dict):
|
|
86
|
+
out: Dict[str, str] = {}
|
|
87
|
+
for slot in SLOTS:
|
|
88
|
+
v = data.get(slot)
|
|
89
|
+
if isinstance(v, str) and v.strip():
|
|
90
|
+
out[slot] = v.strip()
|
|
91
|
+
return out
|
|
92
|
+
# Legacy fallback 1: old codeagent.default_preset_ids.json
|
|
93
|
+
leg = _config_dir() / LEGACY_PRESET_IDS_FILENAME
|
|
94
|
+
if leg.is_file():
|
|
95
|
+
try:
|
|
96
|
+
data = json.loads(leg.read_text(encoding="utf-8") or "{}")
|
|
97
|
+
except (json.JSONDecodeError, OSError):
|
|
98
|
+
data = {}
|
|
99
|
+
if isinstance(data, dict):
|
|
100
|
+
out: Dict[str, str] = {}
|
|
101
|
+
for slot in SLOTS:
|
|
102
|
+
v = data.get(slot)
|
|
103
|
+
if isinstance(v, str) and v.strip():
|
|
104
|
+
out[slot] = v.strip()
|
|
105
|
+
if out:
|
|
106
|
+
return out
|
|
107
|
+
# Legacy fallback 2: single id assigned to "chat"
|
|
108
|
+
legacy = _read_legacy_single_id()
|
|
109
|
+
if legacy:
|
|
110
|
+
return {"chat": legacy}
|
|
111
|
+
return {}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def set_default_preset_id_for_slot(slot: str, preset_id: str) -> None:
|
|
115
|
+
"""Set/clear the default for a single slot. Empty string clears."""
|
|
116
|
+
if slot not in SLOTS:
|
|
117
|
+
raise ValueError(f"unknown slot: {slot!r}")
|
|
118
|
+
cur = get_default_preset_ids()
|
|
119
|
+
pid = (preset_id or "").strip()
|
|
120
|
+
if pid:
|
|
121
|
+
cur[slot] = pid
|
|
122
|
+
else:
|
|
123
|
+
cur.pop(slot, None)
|
|
124
|
+
_write_default_preset_ids(cur)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def set_default_preset_ids(mapping: Dict[str, str]) -> None:
|
|
128
|
+
"""Bulk-replace the entire default map (ignoring unknown slots)."""
|
|
129
|
+
out: Dict[str, str] = {}
|
|
130
|
+
for slot in SLOTS:
|
|
131
|
+
if slot in mapping:
|
|
132
|
+
v = mapping[slot]
|
|
133
|
+
if isinstance(v, str) and v.strip():
|
|
134
|
+
out[slot] = v.strip()
|
|
135
|
+
_write_default_preset_ids(out)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _write_default_preset_ids(mapping: Dict[str, str]) -> None:
|
|
139
|
+
p = _new_path()
|
|
140
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
try:
|
|
142
|
+
p.write_text(json.dumps(mapping, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
143
|
+
except OSError as e:
|
|
144
|
+
logger.error("Failed to save default preset ids: %s", e)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# Backward-compat shim — keep `get_default_preset_id` and `set_default_preset_id`
|
|
149
|
+
# behaving as before for any in-process callers that still import them.
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
def get_default_preset_id() -> str:
|
|
153
|
+
"""Legacy single-id shim → returns the 'chat' default (or '' if none)."""
|
|
154
|
+
return get_default_preset_ids().get("chat", "")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def set_default_preset_id(preset_id: str) -> None:
|
|
158
|
+
"""Legacy single-id shim → sets the 'chat' default."""
|
|
159
|
+
set_default_preset_id_for_slot("chat", preset_id)
|
codeagent/core/env.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Code Agent product environment variables (``CODEAGENT_*`` only)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# --- Product data root (Code Agent home, not Seed generic ``~/.seed``) ---
|
|
9
|
+
|
|
10
|
+
HOME = "CODEAGENT_HOME"
|
|
11
|
+
PROJECT_ROOT = "CODEAGENT_PROJECT_ROOT"
|
|
12
|
+
|
|
13
|
+
# --- Product keys (not read by Seed) ---
|
|
14
|
+
|
|
15
|
+
AGENT_ID = "CODEAGENT_AGENT_ID"
|
|
16
|
+
LOG_LEVEL = "CODEAGENT_LOG_LEVEL"
|
|
17
|
+
SKILLS_AUTO = "CODEAGENT_SKILLS_AUTO"
|
|
18
|
+
SKILLS_TOP_K = "CODEAGENT_SKILLS_TOP_K"
|
|
19
|
+
DIARY = "CODEAGENT_DIARY"
|
|
20
|
+
DIARY_KEEP_DAYS = "CODEAGENT_DIARY_KEEP_DAYS"
|
|
21
|
+
CHAT_USER_ROUNDS = "CODEAGENT_CHAT_USER_ROUNDS"
|
|
22
|
+
MAX_TOOL_ROUNDS = "CODEAGENT_MAX_TOOL_ROUNDS"
|
|
23
|
+
CHAT_AUTO_CONTINUE_ON_LIMIT = "CODEAGENT_CHAT_AUTO_CONTINUE_ON_LIMIT"
|
|
24
|
+
CHAT_AUTO_CONTINUE_MAX_SEGMENTS = "CODEAGENT_CHAT_AUTO_CONTINUE_MAX_SEGMENTS"
|
|
25
|
+
CHAT_MAX_TOOL_ROUNDS_DEFAULT = "CODEAGENT_CHAT_MAX_TOOL_ROUNDS_DEFAULT"
|
|
26
|
+
CONTEXT_COMPACT = "CODEAGENT_CONTEXT_COMPACT"
|
|
27
|
+
CONTEXT_COMPACT_MIN_TOKENS = "CODEAGENT_CONTEXT_COMPACT_MIN_TOKENS"
|
|
28
|
+
CONTEXT_COMPACT_KEEP_USER_ROUNDS = "CODEAGENT_CONTEXT_COMPACT_KEEP_USER_ROUNDS"
|
|
29
|
+
CONTEXT_COMPACT_MID_LOOP = "CODEAGENT_CONTEXT_COMPACT_MID_LOOP"
|
|
30
|
+
CONTEXT_COMPACT_ADAPTIVE_KEEP = "CODEAGENT_CONTEXT_COMPACT_ADAPTIVE_KEEP"
|
|
31
|
+
CONTEXT_COMPACT_SUMMARIZER_PROMPT_FILE = "CODEAGENT_CONTEXT_COMPACT_SUMMARIZER_PROMPT_FILE"
|
|
32
|
+
CONTEXT_COMPACT_SUMMARIZER_BASEURL = "CODEAGENT_CONTEXT_COMPACT_SUMMARIZER_BASEURL"
|
|
33
|
+
CONTEXT_COMPACT_SUMMARIZER_MODEL = "CODEAGENT_CONTEXT_COMPACT_SUMMARIZER_MODEL"
|
|
34
|
+
CONTEXT_COMPACT_SUMMARIZER_MAX_TOKENS = "CODEAGENT_CONTEXT_COMPACT_SUMMARIZER_MAX_TOKENS"
|
|
35
|
+
CHAT_MAX_TOKENS = "CODEAGENT_CHAT_MAX_TOKENS"
|
|
36
|
+
LLM_CONTEXT_SIZE = "CODEAGENT_LLM_CONTEXT_SIZE"
|
|
37
|
+
WEBUI_SESSION_HISTORY_MAX_CHARS = "CODEAGENT_WEBUI_SESSION_HISTORY_MAX_CHARS"
|
|
38
|
+
WEBUI_SESSION_HISTORY_USER_BLOCKS = "CODEAGENT_WEBUI_SESSION_HISTORY_USER_BLOCKS"
|
|
39
|
+
WEBUI_SESSION_HISTORY_MAX_MESSAGES = "CODEAGENT_WEBUI_SESSION_HISTORY_MAX_MESSAGES"
|
|
40
|
+
WEBUI_SESSION_HISTORY_REASONING_MAX_CHARS = "CODEAGENT_WEBUI_SESSION_HISTORY_REASONING_MAX_CHARS"
|
|
41
|
+
WEBUI_TOKEN = "CODEAGENT_WEBUI_TOKEN"
|
|
42
|
+
SKIP_FOLDER_PICKER = "CODEAGENT_SKIP_FOLDER_PICKER"
|
|
43
|
+
AGENT_TOOLS_NO_CACHE = "CODEAGENT_AGENT_TOOLS_NO_CACHE"
|
|
44
|
+
AGENT_TOOLS_MODE = "CODEAGENT_AGENT_TOOLS_MODE"
|
|
45
|
+
ORCHESTRATOR_AUTO_SPLIT = "CODEAGENT_ORCHESTRATOR_AUTO_SPLIT"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def pick_default(default: str, key: str) -> str:
|
|
49
|
+
if key in os.environ:
|
|
50
|
+
return os.environ[key]
|
|
51
|
+
return default
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def pick_nonempty(key: str) -> str:
|
|
55
|
+
raw = os.environ.get(key)
|
|
56
|
+
if raw is not None and str(raw).strip():
|
|
57
|
+
return str(raw).strip()
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def pick_int(default: int, key: str) -> int:
|
|
62
|
+
raw = pick_default(str(default), key).strip()
|
|
63
|
+
try:
|
|
64
|
+
return int(raw)
|
|
65
|
+
except (TypeError, ValueError):
|
|
66
|
+
return default
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def env_truthy(key: str, default: str = "0") -> bool:
|
|
72
|
+
val = pick_default(default, key).strip().lower()
|
|
73
|
+
return val in ("1", "true", "yes", "on")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def env_falsy(key: str, default: str = "1") -> bool:
|
|
77
|
+
val = pick_default(default, key).strip().lower()
|
|
78
|
+
return val in ("0", "false", "no", "off")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def default_agent_id() -> str:
|
|
82
|
+
return pick_default("default", AGENT_ID).strip() or "default"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def product_home() -> Path:
|
|
86
|
+
"""Default ``~/.codeagent``; override with ``CODEAGENT_HOME`` or ``CODEAGENT_PROJECT_ROOT``."""
|
|
87
|
+
raw = pick_nonempty(PROJECT_ROOT) or pick_nonempty(HOME)
|
|
88
|
+
if raw:
|
|
89
|
+
return Path(raw).expanduser().resolve()
|
|
90
|
+
return (Path.home() / ".codeagent").resolve()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def apply_default_product_home() -> Path:
|
|
94
|
+
"""
|
|
95
|
+
Ensure product home exists and set ``SEED_PROJECT_ROOT`` for the Seed kernel.
|
|
96
|
+
|
|
97
|
+
Other products (e.g. designagent) should use their own ``~/.<product>`` and set
|
|
98
|
+
``SEED_PROJECT_ROOT`` the same way in their bootstrap.
|
|
99
|
+
"""
|
|
100
|
+
home = product_home()
|
|
101
|
+
os.environ.setdefault(HOME, str(home))
|
|
102
|
+
if "SEED_PROJECT_ROOT" not in os.environ:
|
|
103
|
+
os.environ["SEED_PROJECT_ROOT"] = str(home)
|
|
104
|
+
home.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
(home / "config").mkdir(parents=True, exist_ok=True)
|
|
106
|
+
return home
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Native folder picker (macOS / Windows / Linux desktop)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Tuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def has_graphical_session() -> bool:
|
|
13
|
+
if sys.platform == "win32":
|
|
14
|
+
return True
|
|
15
|
+
if sys.platform == "darwin":
|
|
16
|
+
return True
|
|
17
|
+
return bool(
|
|
18
|
+
os.environ.get("DISPLAY", "").strip()
|
|
19
|
+
or os.environ.get("WAYLAND_DISPLAY", "").strip()
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pick_directory_sync() -> Tuple[str, bool, str]:
|
|
24
|
+
"""
|
|
25
|
+
Blocking folder picker.
|
|
26
|
+
|
|
27
|
+
Returns ``(path, skipped, hint)``:
|
|
28
|
+
- *path*: chosen directory or ``""``
|
|
29
|
+
- *skipped*: env/disabled/no GUI tool
|
|
30
|
+
- *hint*: user-facing reason when *path* is empty
|
|
31
|
+
"""
|
|
32
|
+
if sys.platform == "darwin":
|
|
33
|
+
return _pick_darwin()
|
|
34
|
+
if sys.platform == "win32":
|
|
35
|
+
return _pick_windows()
|
|
36
|
+
return _pick_linux()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _pick_darwin() -> Tuple[str, bool, str]:
|
|
40
|
+
script = (
|
|
41
|
+
'POSIX path of (choose folder with prompt '
|
|
42
|
+
'"选择目录" default location (path to desktop folder))'
|
|
43
|
+
)
|
|
44
|
+
try:
|
|
45
|
+
cp = subprocess.run(
|
|
46
|
+
["osascript", "-e", script],
|
|
47
|
+
check=False,
|
|
48
|
+
capture_output=True,
|
|
49
|
+
text=True,
|
|
50
|
+
encoding="utf-8",
|
|
51
|
+
errors="replace",
|
|
52
|
+
timeout=120,
|
|
53
|
+
)
|
|
54
|
+
except FileNotFoundError:
|
|
55
|
+
return "", True, "osascript not found"
|
|
56
|
+
except subprocess.TimeoutExpired:
|
|
57
|
+
return "", False, "folder picker timed out"
|
|
58
|
+
cand = (cp.stdout or "").strip()
|
|
59
|
+
if cand and Path(cand).is_dir():
|
|
60
|
+
return cand, False, ""
|
|
61
|
+
if cp.returncode != 0:
|
|
62
|
+
return "", False, "folder picker cancelled"
|
|
63
|
+
return "", False, ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _pick_windows() -> Tuple[str, bool, str]:
|
|
67
|
+
ps = r"""
|
|
68
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
69
|
+
$top = New-Object System.Windows.Forms.Form
|
|
70
|
+
$top.TopMost = $true
|
|
71
|
+
$top.WindowState = [System.Windows.Forms.FormWindowState]::Minimized
|
|
72
|
+
$top.ShowInTaskbar = $false
|
|
73
|
+
$top.Size = New-Object System.Drawing.Size(0, 0)
|
|
74
|
+
$top.StartPosition = "Manual"
|
|
75
|
+
$top.Location = New-Object System.Drawing.Point(-32000, -32000)
|
|
76
|
+
$null = $top.Show()
|
|
77
|
+
[System.Windows.Forms.Application]::DoEvents()
|
|
78
|
+
try {
|
|
79
|
+
$d = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
80
|
+
$d.Description = "选择项目目录"
|
|
81
|
+
$d.ShowNewFolderButton = $true
|
|
82
|
+
if ($d.ShowDialog($top) -eq "OK") { Write-Output $d.SelectedPath }
|
|
83
|
+
} finally { $top.Close(); $top.Dispose() }
|
|
84
|
+
"""
|
|
85
|
+
for exe in ("powershell", "pwsh"):
|
|
86
|
+
if not _which(exe):
|
|
87
|
+
continue
|
|
88
|
+
try:
|
|
89
|
+
cp = subprocess.run(
|
|
90
|
+
[exe, "-NoProfile", "-Command", ps],
|
|
91
|
+
check=False,
|
|
92
|
+
capture_output=True,
|
|
93
|
+
text=True,
|
|
94
|
+
encoding="utf-8",
|
|
95
|
+
errors="replace",
|
|
96
|
+
timeout=120,
|
|
97
|
+
)
|
|
98
|
+
except subprocess.TimeoutExpired:
|
|
99
|
+
return "", False, "folder picker timed out"
|
|
100
|
+
cand = (cp.stdout or "").strip()
|
|
101
|
+
if cand and Path(cand).is_dir():
|
|
102
|
+
return cand, False, ""
|
|
103
|
+
return "", True, "PowerShell not found for folder picker"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _pick_linux() -> Tuple[str, bool, str]:
|
|
107
|
+
if not has_graphical_session():
|
|
108
|
+
return (
|
|
109
|
+
"",
|
|
110
|
+
True,
|
|
111
|
+
"no DISPLAY/WAYLAND_DISPLAY; set project path manually or install zenity on a desktop session",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
tools = (
|
|
115
|
+
(["zenity", "--file-selection", "--directory"], "zenity"),
|
|
116
|
+
(["kdialog", "--getexistingdirectory"], "kdialog"),
|
|
117
|
+
(["yad", "--file", "--directory"], "yad"),
|
|
118
|
+
)
|
|
119
|
+
missing: list[str] = []
|
|
120
|
+
for cmd, name in tools:
|
|
121
|
+
if not _which(cmd[0]):
|
|
122
|
+
missing.append(name)
|
|
123
|
+
continue
|
|
124
|
+
try:
|
|
125
|
+
cp = subprocess.run(
|
|
126
|
+
cmd,
|
|
127
|
+
check=False,
|
|
128
|
+
capture_output=True,
|
|
129
|
+
text=True,
|
|
130
|
+
encoding="utf-8",
|
|
131
|
+
errors="replace",
|
|
132
|
+
timeout=120,
|
|
133
|
+
)
|
|
134
|
+
except subprocess.TimeoutExpired:
|
|
135
|
+
return "", False, "folder picker timed out"
|
|
136
|
+
cand = (cp.stdout or "").strip()
|
|
137
|
+
if cand and Path(cand).is_dir():
|
|
138
|
+
return cand, False, ""
|
|
139
|
+
if cp.returncode != 0 and cp.returncode != 1:
|
|
140
|
+
continue
|
|
141
|
+
hint = (
|
|
142
|
+
"install zenity, kdialog, or yad for GUI folder selection "
|
|
143
|
+
f"(missing: {', '.join(missing) or 'unknown'})"
|
|
144
|
+
)
|
|
145
|
+
return "", True, hint
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _which(name: str) -> Optional[str]:
|
|
149
|
+
from shutil import which as _w
|
|
150
|
+
|
|
151
|
+
return _w(name)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Image understanding: vision LLM presets or MiniMax Token Plan MCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from seed.integrations.mcp_config import MINIMAX_MCP_SERVER_ID, get_server_config
|
|
9
|
+
|
|
10
|
+
MCP_VISION_SENTINEL = "__mcp_minimax__"
|
|
11
|
+
MINIMAX_UNDERSTAND_IMAGE_TOOL = "understand_image"
|
|
12
|
+
MCP_QUALIFIED_UNDERSTAND_IMAGE = f"mcp__{MINIMAX_MCP_SERVER_ID}__{MINIMAX_UNDERSTAND_IMAGE_TOOL}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def minimax_mcp_configured(base: Optional[Path] = None) -> bool:
|
|
16
|
+
"""MiniMax MCP server enabled in config with a non-empty API key."""
|
|
17
|
+
from seed.integrations.mcp_client import mcp_globally_enabled
|
|
18
|
+
|
|
19
|
+
if not mcp_globally_enabled():
|
|
20
|
+
return False
|
|
21
|
+
cfg = get_server_config(MINIMAX_MCP_SERVER_ID, base)
|
|
22
|
+
if not cfg or not cfg.enabled or not cfg.command.strip():
|
|
23
|
+
return False
|
|
24
|
+
key = (cfg.env or {}).get("MINIMAX_API_KEY") or ""
|
|
25
|
+
return bool(str(key).strip())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def minimax_mcp_understand_image_ready(
|
|
29
|
+
base: Optional[Path] = None,
|
|
30
|
+
*,
|
|
31
|
+
probe: bool = False,
|
|
32
|
+
servers_status: Optional[list[dict[str, Any]]] = None,
|
|
33
|
+
) -> bool:
|
|
34
|
+
"""True when MiniMax MCP can run ``understand_image`` (configured; optionally probed)."""
|
|
35
|
+
if not minimax_mcp_configured(base):
|
|
36
|
+
return False
|
|
37
|
+
if not probe:
|
|
38
|
+
return True
|
|
39
|
+
rows = servers_status
|
|
40
|
+
if rows is None:
|
|
41
|
+
from seed.integrations.mcp_client import get_mcp_manager
|
|
42
|
+
|
|
43
|
+
rows = get_mcp_manager().list_servers_status(probe=True)
|
|
44
|
+
for row in rows or []:
|
|
45
|
+
if str(row.get("id") or "") != MINIMAX_MCP_SERVER_ID:
|
|
46
|
+
continue
|
|
47
|
+
if not row.get("connected"):
|
|
48
|
+
return False
|
|
49
|
+
tools = row.get("tools") or []
|
|
50
|
+
return MINIMAX_UNDERSTAND_IMAGE_TOOL in tools
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def image_attachment_allowed(vision_llm_id: str, base: Optional[Path] = None) -> bool:
|
|
55
|
+
"""User may send image attachments when a vision preset or MiniMax MCP is available."""
|
|
56
|
+
vid = (vision_llm_id or "").strip()
|
|
57
|
+
if vid == MCP_VISION_SENTINEL:
|
|
58
|
+
return minimax_mcp_configured(base)
|
|
59
|
+
if vid:
|
|
60
|
+
from codeagent.core.vision_models import preset_supports_vision_id
|
|
61
|
+
|
|
62
|
+
return preset_supports_vision_id(vid)
|
|
63
|
+
return minimax_mcp_configured(base)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def video_attachment_allowed(vision_llm_id: str) -> bool:
|
|
67
|
+
"""Video analysis still requires a vision-capable LLM preset."""
|
|
68
|
+
from codeagent.core.vision_models import preset_supports_vision_id
|
|
69
|
+
|
|
70
|
+
vid = (vision_llm_id or "").strip()
|
|
71
|
+
return bool(vid) and preset_supports_vision_id(vid)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def image_understanding_status(
|
|
75
|
+
base: Optional[Path] = None,
|
|
76
|
+
*,
|
|
77
|
+
probe: bool = False,
|
|
78
|
+
servers_status: Optional[list[dict[str, Any]]] = None,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
configured = minimax_mcp_configured(base)
|
|
81
|
+
ready = minimax_mcp_understand_image_ready(
|
|
82
|
+
base, probe=probe, servers_status=servers_status
|
|
83
|
+
)
|
|
84
|
+
return {
|
|
85
|
+
"configured": configured,
|
|
86
|
+
"ready": ready,
|
|
87
|
+
"sentinel": MCP_VISION_SENTINEL,
|
|
88
|
+
"mcp_tool": MCP_QUALIFIED_UNDERSTAND_IMAGE,
|
|
89
|
+
"minimax_server_id": MINIMAX_MCP_SERVER_ID,
|
|
90
|
+
}
|