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.
Files changed (173) hide show
  1. codeagent/__init__.py +10 -0
  2. codeagent/__main__.py +13 -0
  3. codeagent/cli/__init__.py +13 -0
  4. codeagent/cli/entrypoint.py +161 -0
  5. codeagent/cli/main.py +979 -0
  6. codeagent/core/__init__.py +2 -0
  7. codeagent/core/attachments.py +342 -0
  8. codeagent/core/audio_models.py +50 -0
  9. codeagent/core/bootstrap.py +178 -0
  10. codeagent/core/default_presets.py +159 -0
  11. codeagent/core/env.py +106 -0
  12. codeagent/core/folder_picker.py +151 -0
  13. codeagent/core/image_understanding.py +90 -0
  14. codeagent/core/models.py +6 -0
  15. codeagent/core/paths.py +238 -0
  16. codeagent/core/pricing.py +107 -0
  17. codeagent/core/process_ports.py +258 -0
  18. codeagent/core/seed_bridge.py +21 -0
  19. codeagent/core/settings.py +98 -0
  20. codeagent/core/speech_synth.py +330 -0
  21. codeagent/core/team_config.py +247 -0
  22. codeagent/core/team_manager.py +245 -0
  23. codeagent/core/token_counter.py +41 -0
  24. codeagent/core/tts_voices.py +103 -0
  25. codeagent/core/usage_billing.py +72 -0
  26. codeagent/core/vision_models.py +93 -0
  27. codeagent/memory/diary.py +144 -0
  28. codeagent/persona_defaults/README.md +12 -0
  29. codeagent/persona_defaults/agent.md +352 -0
  30. codeagent/persona_defaults/identity.md +29 -0
  31. codeagent/persona_defaults/memory.md +4 -0
  32. codeagent/persona_defaults/skills.md +325 -0
  33. codeagent/persona_defaults/soul.md +38 -0
  34. codeagent/persona_defaults/tools.md +41 -0
  35. codeagent/persona_defaults/user.md +18 -0
  36. codeagent/runtime/__init__.py +11 -0
  37. codeagent/runtime/agent_context.py +6 -0
  38. codeagent/runtime/agent_runtime.py +21 -0
  39. codeagent/runtime/compact_prompt.py +9 -0
  40. codeagent/runtime/compact_state.py +40 -0
  41. codeagent/runtime/llm_worker.py +132 -0
  42. codeagent/runtime/orchestrator.py +161 -0
  43. codeagent/runtime/prompt_enrichment.py +324 -0
  44. codeagent/runtime/session_manager.py +30 -0
  45. codeagent/runtime/task_split.py +38 -0
  46. codeagent/runtime/turn_loop.py +13 -0
  47. codeagent/runtime/worker.py +48 -0
  48. codeagent/server/__init__.py +505 -0
  49. codeagent/server/app_factory.py +1292 -0
  50. codeagent/server/attachment_api.py +261 -0
  51. codeagent/server/hub.py +175 -0
  52. codeagent/server/icon.png +0 -0
  53. codeagent/server/self_healing.py +143 -0
  54. codeagent/server/team_engine.py +243 -0
  55. codeagent/server/tts_api.py +51 -0
  56. codeagent/server/webui_api_app.py +3180 -0
  57. codeagent/skills/__init__.py +2 -0
  58. codeagent/skills/select.py +144 -0
  59. codeagent/tools/__init__.py +6 -0
  60. codeagent/tools/agent_tools.py +10 -0
  61. codeagent/web/__init__.py +1 -0
  62. codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
  63. codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
  64. codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
  65. codeagent/web/auth_impl.py +326 -0
  66. codeagent/web/static/00-marked.min.js +6 -0
  67. codeagent/web/static/00-mermaid.min.js +2314 -0
  68. codeagent/web/static/00-purify.min.js +3 -0
  69. codeagent/web/static/00-storage.js +83 -0
  70. codeagent/web/static/00-utils.js +51 -0
  71. codeagent/web/static/01-theme.css +96 -0
  72. codeagent/web/static/01a-scroll.js +86 -0
  73. codeagent/web/static/01b-think.js +62 -0
  74. codeagent/web/static/01c-session-identity.js +81 -0
  75. codeagent/web/static/01c-session-thinking.js +61 -0
  76. codeagent/web/static/01c-session-tree-menus.js +349 -0
  77. codeagent/web/static/01c-session-tree.js +531 -0
  78. codeagent/web/static/01c-session-utilities.js +83 -0
  79. codeagent/web/static/01c-vision-model.js +132 -0
  80. codeagent/web/static/01d-image-gen-model.js +84 -0
  81. codeagent/web/static/01e-audio-model.js +97 -0
  82. codeagent/web/static/01f-compose-ui.js +88 -0
  83. codeagent/web/static/01g-tts-settings.js +128 -0
  84. codeagent/web/static/01h-music-model.js +85 -0
  85. codeagent/web/static/01i-model-stack.js +291 -0
  86. codeagent/web/static/01j-video-gen-model.js +85 -0
  87. codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
  88. codeagent/web/static/01k-model-stack-state.js +209 -0
  89. codeagent/web/static/01q-inflight.js +195 -0
  90. codeagent/web/static/02-reset.css +22 -0
  91. codeagent/web/static/02-session-ui.js +201 -0
  92. codeagent/web/static/03-layout-p1.css +2 -0
  93. codeagent/web/static/03-layout-p2.css +2 -0
  94. codeagent/web/static/03a-links-stream.js +88 -0
  95. codeagent/web/static/03b-stream-text.js +119 -0
  96. codeagent/web/static/03c-render-bubble.js +520 -0
  97. codeagent/web/static/03d-agent-bubble.js +126 -0
  98. codeagent/web/static/03e-tool-trace.js +182 -0
  99. codeagent/web/static/03f-timeline.js +54 -0
  100. codeagent/web/static/03g-bubble-tts.js +292 -0
  101. codeagent/web/static/04-components-p1.css +1 -0
  102. codeagent/web/static/04-components-p2b.css +1 -0
  103. codeagent/web/static/04-components-p3.css +2 -0
  104. codeagent/web/static/04-components-p4.css +1 -0
  105. codeagent/web/static/04-components-p5.css +1 -0
  106. codeagent/web/static/04-components-p6.css +58 -0
  107. codeagent/web/static/04-components-p7.css +4 -0
  108. codeagent/web/static/04-components-p8.css +1 -0
  109. codeagent/web/static/04-components-p9.css +1 -0
  110. codeagent/web/static/04-ws-connect.js +351 -0
  111. codeagent/web/static/04-ws-helpers.js +116 -0
  112. codeagent/web/static/05-chat-p1.css +1058 -0
  113. codeagent/web/static/05-chat-p2.css +1 -0
  114. codeagent/web/static/05-chat-p3.css +295 -0
  115. codeagent/web/static/05a-session-history.js +176 -0
  116. codeagent/web/static/05b-session-list.js +114 -0
  117. codeagent/web/static/05c-dialog.js +123 -0
  118. codeagent/web/static/05d-project-create.js +172 -0
  119. codeagent/web/static/05e-project-remote.js +143 -0
  120. codeagent/web/static/06-chat.js +552 -0
  121. codeagent/web/static/06-utilities.css +181 -0
  122. codeagent/web/static/06b-attachments.js +304 -0
  123. codeagent/web/static/06c-camera.js +488 -0
  124. codeagent/web/static/07-activity-bar.css +127 -0
  125. codeagent/web/static/09-git-p1.css +1 -0
  126. codeagent/web/static/09-git-p2.css +1 -0
  127. codeagent/web/static/09a-init-bind.js +110 -0
  128. codeagent/web/static/09b-panel-toggle.js +80 -0
  129. codeagent/web/static/09c-api-refresh.js +100 -0
  130. codeagent/web/static/09d-status-log.js +80 -0
  131. codeagent/web/static/09e-commit.js +80 -0
  132. codeagent/web/static/09f-remote.js +93 -0
  133. codeagent/web/static/11a-cron-parse.js +117 -0
  134. codeagent/web/static/11b-cron-panel.js +100 -0
  135. codeagent/web/static/11c-cron-card.js +133 -0
  136. codeagent/web/static/11d-cron-form.js +105 -0
  137. codeagent/web/static/11e-env-config.js +168 -0
  138. codeagent/web/static/11f-env-llm-a-state.js +136 -0
  139. codeagent/web/static/11f-env-llm-b-form.js +409 -0
  140. codeagent/web/static/11f-env-llm-c-board.js +322 -0
  141. codeagent/web/static/11g-env-paths-git.js +973 -0
  142. codeagent/web/static/11h-env-mcp-a-state.js +161 -0
  143. codeagent/web/static/11h-env-mcp-b-form.js +229 -0
  144. codeagent/web/static/11h-env-mcp-c-board.js +565 -0
  145. codeagent/web/static/12-plugins.js +127 -0
  146. codeagent/web/static/13a-skills.js +156 -0
  147. codeagent/web/static/13a-todo-utils.js +140 -0
  148. codeagent/web/static/13b-todo-fetch.js +106 -0
  149. codeagent/web/static/13b-tools.js +127 -0
  150. codeagent/web/static/13c-todo-crud.js +92 -0
  151. codeagent/web/static/14-activity-bar.js +177 -0
  152. codeagent/web/static/15-plans.css +194 -0
  153. codeagent/web/static/15-plans.js +219 -0
  154. codeagent/web/static/16-mobile.css +612 -0
  155. codeagent/web/static/16-mobile.js +178 -0
  156. codeagent/web/static/17-preset-ux.css +287 -0
  157. codeagent/web/static/18-agent-mgr.css +616 -0
  158. codeagent/web/static/18-agent-mgr.js +204 -0
  159. codeagent/web/static/24-health-dashboard.js +68 -0
  160. codeagent/web/static/99-brand-polish.css +152 -0
  161. codeagent/web/static/body.html +824 -0
  162. codeagent/web/static/setup/setup_form.html +171 -0
  163. codeagent/web/static/setup/setup_head.html +100 -0
  164. codeagent/web/static/setup/setup_tail.html +264 -0
  165. codeagent/web/static/setup/setup_test.html +38 -0
  166. codeagent/web/web_login.html +265 -0
  167. codeagent/web/webui.html +35 -0
  168. tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
  169. tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
  170. tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
  171. tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
  172. tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
  173. tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,245 @@
1
+ """Team manager — singleton that manages team lifecycle.
2
+
3
+ Usage::
4
+
5
+ from codeagent.core.team_manager import TeamManager
6
+ tm = TeamManager()
7
+ tm.init_from_config(".codeagent/config/team.json")
8
+
9
+ # Later, when runtime is available:
10
+ tm.register_members(session_factory, llm_executor)
11
+
12
+ # Lookup:
13
+ lead = tm.get_lead()
14
+ members = tm.get_members()
15
+ agent = tm.get_agent("frontend_dev")
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from pathlib import Path
22
+ from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING
23
+
24
+ from seed.core.agent_registry import AgentHandle, AgentRegistry
25
+
26
+ if TYPE_CHECKING:
27
+ pass
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class TeamManager:
33
+ """Singleton that manages team configuration and agent lifecycle."""
34
+
35
+ _instance: Optional["TeamManager"] = None
36
+
37
+ def __new__(cls) -> "TeamManager":
38
+ if cls._instance is None:
39
+ cls._instance = super().__new__(cls)
40
+ cls._instance._reset_state()
41
+ return cls._instance
42
+
43
+ def _reset_state(self) -> None:
44
+ """Reset internal state (called from __new__ and _reset)."""
45
+ self._initialized = False
46
+ self._config = None
47
+ self._lead_handle = None
48
+ self._member_handles: Dict[str, AgentHandle] = {}
49
+
50
+ @classmethod
51
+ def _reset(cls) -> None:
52
+ """Reset singleton for testing. Clears instance and registry."""
53
+ from seed.core.agent_registry import AgentRegistry
54
+ AgentRegistry.clear()
55
+ cls._instance = None
56
+
57
+ def init_from_config(self, config_path: Path | str) -> bool:
58
+ """Load and parse team.json config.
59
+
60
+ Args:
61
+ config_path: Path to team.json file.
62
+
63
+ Returns:
64
+ True if loaded successfully, False if file not found or invalid.
65
+ """
66
+ from codeagent.core.team_config import TeamConfig
67
+
68
+ path = Path(config_path)
69
+ if not path.is_file():
70
+ logger.info(f"No team config found at {config_path} — running in single-agent mode")
71
+ return False
72
+
73
+ try:
74
+ self._config = TeamConfig.from_file(path)
75
+ self._initialized = True
76
+ logger.info(
77
+ f"Loaded team config: Lead='{self._config.lead.name}', "
78
+ f"{len(self._config.members)} members"
79
+ )
80
+ return True
81
+ except Exception as e:
82
+ logger.warning(f"Failed to load team config: {e} — running in single-agent mode")
83
+ return False
84
+
85
+ @property
86
+ def is_team_mode(self) -> bool:
87
+ """True if team config was loaded successfully."""
88
+ return self._initialized and self._config is not None
89
+
90
+ @property
91
+ def config(self):
92
+ """The loaded TeamConfig (None if not in team mode)."""
93
+ return self._config
94
+
95
+ def get_lead(self) -> Optional[AgentHandle]:
96
+ """Return the registered Lead agent handle."""
97
+ if self._lead_handle:
98
+ return self._lead_handle
99
+ # Fall back to registry lookup
100
+ if self._config and self._config.lead:
101
+ return AgentRegistry.get(self._config.lead.id)
102
+ return None
103
+
104
+ def get_members(self) -> List[AgentHandle]:
105
+ """Return list of all registered member agent handles."""
106
+ return list(self._member_handles.values())
107
+
108
+ def get_agent(self, agent_id: str) -> Optional[AgentHandle]:
109
+ """Look up a registered agent by id.
110
+
111
+ Checks members first, then lead.
112
+ """
113
+ handle = self._member_handles.get(agent_id)
114
+ if handle:
115
+ return handle
116
+ return AgentRegistry.get(agent_id)
117
+
118
+ def register_members(
119
+ self,
120
+ create_agent: Callable[[str, str, List[str]], Any],
121
+ ) -> int:
122
+ """Register all member agents with AgentRegistry.
123
+
124
+ This should be called once the runtime is available to create agents.
125
+
126
+ Args:
127
+ create_agent: Factory function(agent_id, persona_path, tools) -> object
128
+ The returned object must be suitable for AgentHandle construction.
129
+ Typically an AutonomousAgent instance or any callable that can
130
+ be passed to AgentHandle(agent=...).
131
+
132
+ Returns:
133
+ Number of agents registered.
134
+ """
135
+ if not self._config:
136
+ logger.warning("Cannot register members: no team config loaded")
137
+ return 0
138
+
139
+ count = 0
140
+ # Register lead first
141
+ if self._config.lead:
142
+ try:
143
+ agent_instance = create_agent(
144
+ self._config.lead.id,
145
+ self._config.lead.persona,
146
+ self._config.lead.tools,
147
+ )
148
+ handle = AgentHandle(
149
+ agent=agent_instance,
150
+ metadata={"role": "lead", "name": self._config.lead.name},
151
+ )
152
+ AgentRegistry.register(self._config.lead.id, handle)
153
+ self._lead_handle = handle
154
+ count += 1
155
+ logger.info(f"Registered Lead agent: {self._config.lead.id}")
156
+ except Exception as e:
157
+ logger.error(f"Failed to register Lead agent '{self._config.lead.id}': {e}")
158
+
159
+ # Register members
160
+ for member in self._config.members:
161
+ if member.id in self._member_handles:
162
+ logger.warning(f"Member '{member.id}' already registered, skipping")
163
+ continue
164
+ try:
165
+ agent_instance = create_agent(
166
+ member.id,
167
+ member.persona,
168
+ member.tools,
169
+ )
170
+ handle = AgentHandle(
171
+ agent=agent_instance,
172
+ metadata={"role": "member", "name": member.name},
173
+ )
174
+ AgentRegistry.register(member.id, handle)
175
+ self._member_handles[member.id] = handle
176
+ count += 1
177
+ logger.info(f"Registered member agent: {member.id} ({member.name})")
178
+ except Exception as e:
179
+ logger.error(f"Failed to register member '{member.id}': {e}")
180
+
181
+ logger.info(f"Team registration complete: {count} agents registered")
182
+ return count
183
+
184
+ def unregister_all(self) -> None:
185
+ """Remove all registered agents from the registry."""
186
+ if self._lead_handle:
187
+ AgentRegistry.unregister(self._config.lead.id if self._config else "")
188
+ self._lead_handle = None
189
+ for agent_id in list(self._member_handles.keys()):
190
+ AgentRegistry.unregister(agent_id)
191
+ self._member_handles.clear()
192
+ logger.info("All team agents unregistered")
193
+
194
+ def get_team_status(self) -> dict:
195
+ """Return a summary of team status for UI display."""
196
+ if not self.is_team_mode:
197
+ return {"mode": "single", "agents": []}
198
+
199
+ agents = []
200
+ if self._config and self._config.lead:
201
+ agents.append({
202
+ "id": self._config.lead.id,
203
+ "name": self._config.lead.name,
204
+ "role": "lead",
205
+ "tools": self._config.lead.tools,
206
+ "registered": self._lead_handle is not None,
207
+ })
208
+ if self._config:
209
+ for m in self._config.members:
210
+ agents.append({
211
+ "id": m.id,
212
+ "name": m.name,
213
+ "role": "member",
214
+ "tools": m.tools,
215
+ "registered": m.id in self._member_handles,
216
+ })
217
+
218
+ return {
219
+ "mode": "team",
220
+ "version": self._config.version if self._config else "1.0",
221
+ "total_agents": len(agents),
222
+ "registered_count": sum(1 for a in agents if a["registered"]),
223
+ "agents": agents,
224
+ }
225
+
226
+
227
+ def team_config_path() -> Path:
228
+ """Return the default team config path (.codeagent/config/team.json in project root)."""
229
+ from seed.core.config_plane import project_root
230
+
231
+ root = project_root()
232
+ return root / "config" / "team.json"
233
+
234
+
235
+ def is_team_configured() -> bool:
236
+ """Check if team.json exists and is valid."""
237
+ path = team_config_path()
238
+ if not path.is_file():
239
+ return False
240
+ try:
241
+ from codeagent.core.team_config import TeamConfig
242
+ TeamConfig.from_file(path)
243
+ return True
244
+ except Exception:
245
+ return False
@@ -0,0 +1,41 @@
1
+ """
2
+ Token 计数适配层 — 基于 seed_model_providers.token_counter,增加 attachment tag 剥离。
3
+
4
+ 如需使用原始 DeepSeek tokenizer 计数,直接 from seed_model_providers.token_counter import count_tokens.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from seed_model_providers.token_counter import count_tokens # noqa: F401 — re-export
12
+
13
+ from codeagent.core.attachments import ATTACHMENT_TAG_RE, IMAGE_DIR_TAG_RE
14
+
15
+
16
+ def count_messages(messages: list[dict[str, Any]]) -> dict[str, int]:
17
+ """计算一组消息的总 token 用量(剥离 attachment/image_dir 标记后再计)。"""
18
+ from seed_model_providers.token_counter import count_tokens as _cnt_tok
19
+
20
+ content_tokens = 0
21
+ for msg in messages:
22
+ role = str(msg.get("role") or "")
23
+ content = msg.get("content")
24
+ if role == "tool":
25
+ content_tokens += _cnt_tok(str(content or ""))
26
+ elif isinstance(content, list):
27
+ for part in content:
28
+ if isinstance(part, dict) and part.get("type") == "text":
29
+ content_tokens += _cnt_tok(part.get("text", ""))
30
+ else:
31
+ text = str(content or "")
32
+ text = ATTACHMENT_TAG_RE.sub("", text)
33
+ text = IMAGE_DIR_TAG_RE.sub("", text)
34
+ content_tokens += _cnt_tok(text)
35
+ content_tokens += 4
36
+
37
+ return {
38
+ "total_tokens": content_tokens,
39
+ "content_tokens": content_tokens,
40
+ "message_count": len(messages),
41
+ }
@@ -0,0 +1,103 @@
1
+ """MiniMax system TTS voices and models (curated from platform docs)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+ from seed_model_providers import list_models_for_provider
9
+
10
+ # https://platform.minimaxi.com/docs/faq/system-voice-id
11
+ TTS_SYSTEM_VOICES: list[dict[str, str]] = [
12
+ {"id": "male-qn-qingse", "name": "青涩青年", "lang": "中文"},
13
+ {"id": "male-qn-jingying", "name": "精英青年", "lang": "中文"},
14
+ {"id": "male-qn-badao", "name": "霸道青年", "lang": "中文"},
15
+ {"id": "male-qn-daxuesheng", "name": "青年大学生", "lang": "中文"},
16
+ {"id": "female-shaonv", "name": "少女", "lang": "中文"},
17
+ {"id": "female-yujie", "name": "御姐", "lang": "中文"},
18
+ {"id": "female-chengshu", "name": "成熟女性", "lang": "中文"},
19
+ {"id": "female-tianmei", "name": "甜美女性", "lang": "中文"},
20
+ {"id": "Chinese (Mandarin)_Lyrical_Voice", "name": "抒情男声", "lang": "中文"},
21
+ {"id": "Chinese (Mandarin)_Gentleman", "name": "温润男声", "lang": "中文"},
22
+ {"id": "Chinese (Mandarin)_Sweet_Lady", "name": "甜美女声", "lang": "中文"},
23
+ {"id": "Chinese (Mandarin)_News_Anchor", "name": "新闻女声", "lang": "中文"},
24
+ {"id": "Chinese (Mandarin)_Male_Announcer", "name": "播报男声", "lang": "中文"},
25
+ {"id": "Chinese (Mandarin)_Radio_Host", "name": "电台男主播", "lang": "中文"},
26
+ {"id": "Chinese (Mandarin)_Reliable_Executive", "name": "沉稳高管", "lang": "中文"},
27
+ {"id": "Chinese (Mandarin)_Warm_Bestie", "name": "温暖闺蜜", "lang": "中文"},
28
+ {"id": "Chinese (Mandarin)_HK_Flight_Attendant", "name": "港普空姐", "lang": "中文"},
29
+ {"id": "Chinese (Mandarin)_Humorous_Elder", "name": "搞笑大爷", "lang": "中文"},
30
+ {"id": "Chinese (Mandarin)_Crisp_Girl", "name": "清脆少女", "lang": "中文"},
31
+ {"id": "Chinese (Mandarin)_Soft_Girl", "name": "柔和少女", "lang": "中文"},
32
+ {"id": "Cantonese_ProfessionalHost(F)", "name": "粤语女主持", "lang": "粤语"},
33
+ {"id": "Cantonese_GentleLady", "name": "粤语温柔女声", "lang": "粤语"},
34
+ {"id": "Cantonese_ProfessionalHost(M)", "name": "粤语男主持", "lang": "粤语"},
35
+ {"id": "English_Graceful_Lady", "name": "Graceful Lady", "lang": "英文"},
36
+ {"id": "English_Trustworthy_Man", "name": "Trustworthy Man", "lang": "英文"},
37
+ {"id": "English_Diligent_Man", "name": "Diligent Man", "lang": "英文"},
38
+ {"id": "English_Gentle-voiced_man", "name": "Gentle-voiced man", "lang": "英文"},
39
+ {"id": "English_Whispering_girl", "name": "Whispering girl", "lang": "英文"},
40
+ {"id": "Japanese_GentleButler", "name": "Gentle Butler", "lang": "日文"},
41
+ {"id": "Japanese_KindLady", "name": "Kind Lady", "lang": "日文"},
42
+ {"id": "Korean_SweetGirl", "name": "Sweet Girl", "lang": "韩文"},
43
+ ]
44
+
45
+ DEFAULT_TTS_VOICE_ID = "male-qn-qingse"
46
+ DEFAULT_TTS_MODEL = "speech-2.8-turbo"
47
+
48
+
49
+ def _speech_models_from_catalog() -> list[dict[str, str]]:
50
+ out: list[dict[str, str]] = []
51
+ for m in list_models_for_provider("minimax", "speech"):
52
+ mid = str(m.get("id") or "").strip()
53
+ if not mid:
54
+ continue
55
+ label = str(m.get("label") or mid).strip() or mid
56
+ out.append({"id": mid, "name": label})
57
+ return out
58
+
59
+
60
+ def _allowed_tts_models() -> set[str]:
61
+ return {m["id"] for m in _speech_models_from_catalog()}
62
+
63
+
64
+ def default_tts_voice_id() -> str:
65
+ v = os.environ.get("CODEAGENT_TTS_VOICE_ID", DEFAULT_TTS_VOICE_ID).strip()
66
+ return v or DEFAULT_TTS_VOICE_ID
67
+
68
+
69
+ def default_tts_model() -> str:
70
+ m = os.environ.get("CODEAGENT_TTS_MODEL", DEFAULT_TTS_MODEL).strip()
71
+ return m or DEFAULT_TTS_MODEL
72
+
73
+
74
+ def normalize_tts_voice_id(voice_id: str | None) -> str:
75
+ v = (voice_id or "").strip()
76
+ if not v:
77
+ return default_tts_voice_id()
78
+ if len(v) > 128:
79
+ return default_tts_voice_id()
80
+ return v
81
+
82
+
83
+ def normalize_tts_model(model: str | None) -> str:
84
+ m = (model or "").strip()
85
+ allowed = _allowed_tts_models()
86
+ if m in allowed:
87
+ return m
88
+ if not m:
89
+ return default_tts_model()
90
+ env_default = default_tts_model()
91
+ return env_default if env_default in allowed else DEFAULT_TTS_MODEL
92
+
93
+
94
+ def tts_options_payload(*, configured: bool) -> dict[str, Any]:
95
+ models = _speech_models_from_catalog()
96
+ return {
97
+ "configured": configured,
98
+ "default_voice_id": default_tts_voice_id(),
99
+ "default_model": default_tts_model(),
100
+ "voices": TTS_SYSTEM_VOICES,
101
+ "models": models,
102
+ "docs_url": "https://platform.minimaxi.com/docs/faq/system-voice-id",
103
+ }
@@ -0,0 +1,72 @@
1
+ """Session-level token usage accumulation with per-model cost (billing v2)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Tuple
6
+
7
+ from codeagent.core.pricing import calculate_cost
8
+
9
+ USAGE_KEYS = (
10
+ "prompt_tokens",
11
+ "completion_tokens",
12
+ "total_tokens",
13
+ "prompt_cache_hit_tokens",
14
+ "prompt_cache_miss_tokens",
15
+ )
16
+
17
+
18
+ def _add_usage_keys(target: Dict[str, Any], source: Dict[str, Any]) -> None:
19
+ for key in USAGE_KEYS:
20
+ value = source.get(key, 0)
21
+ if not isinstance(value, (int, float)):
22
+ value = 0
23
+ target[key] = int(target.get(key, 0) or 0) + int(value)
24
+
25
+
26
+ def merge_accumulated_usage(
27
+ prev: Dict[str, Any] | None,
28
+ model_name: str,
29
+ api_usage: Dict[str, Any] | None,
30
+ ) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:
31
+ """Merge one LLM round into session ``accumulated_usage``.
32
+
33
+ Returns:
34
+ ``(accumulated_usage, round_cost, accumulated_cost_summary)``
35
+ """
36
+ prev = prev or {}
37
+ api_usage = dict(api_usage or {})
38
+
39
+ acc: Dict[str, Any] = {}
40
+ _add_usage_keys(acc, prev)
41
+ _add_usage_keys(acc, api_usage)
42
+
43
+ per_model: Dict[str, Dict[str, Any]] = {
44
+ name: dict(data) for name, data in (prev.get("per_model") or {}).items()
45
+ }
46
+ model_usage = dict(per_model.get(model_name) or {})
47
+ _add_usage_keys(model_usage, api_usage)
48
+ per_model[model_name] = model_usage
49
+
50
+ total_cost_val = 0.0
51
+ for mname, mdata in per_model.items():
52
+ mcost = calculate_cost(mname, mdata)
53
+ cost_val = float(mcost.get("total_cost", 0) or 0)
54
+ mdata["cost"] = round(cost_val, 6)
55
+ total_cost_val += cost_val
56
+
57
+ acc["per_model"] = per_model
58
+ acc["total_cost"] = round(total_cost_val, 6)
59
+ acc["currency"] = "CNY"
60
+
61
+ round_cost = calculate_cost(model_name, api_usage)
62
+ acc["last_request"] = {
63
+ "model": model_name,
64
+ "usage": api_usage,
65
+ "cost": round_cost,
66
+ }
67
+
68
+ accumulated_cost = {
69
+ "total_cost": acc["total_cost"],
70
+ "currency": "CNY",
71
+ }
72
+ return acc, round_cost, accumulated_cost
@@ -0,0 +1,93 @@
1
+ """Vision preset resolution for vision_analyze tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any, Optional
7
+
8
+ from codeagent.core import env as ca_env
9
+
10
+
11
+ def list_vision_presets() -> list[dict[str, Any]]:
12
+ from seed.core.llm_presets import load_presets
13
+
14
+ return [p for p in load_presets() if preset_supports_vision(p)]
15
+
16
+
17
+ def preset_supports_vision(preset: dict[str, Any] | None) -> bool:
18
+ if not isinstance(preset, dict):
19
+ return False
20
+ if preset.get("supports_vision") is True:
21
+ return True
22
+ return False
23
+
24
+
25
+ def preset_supports_vision_id(preset_id: str) -> bool:
26
+ from seed.core.llm_presets import load_presets
27
+
28
+ pid = (preset_id or "").strip()
29
+ for p in load_presets():
30
+ if str(p.get("id") or "").strip() == pid:
31
+ return preset_supports_vision(p)
32
+ return False
33
+
34
+
35
+ def resolve_preset_id(preset_id: Optional[str]) -> Optional[dict[str, Any]]:
36
+ from seed.core.llm_presets import load_presets, resolve_preset
37
+
38
+ pid = (preset_id or "").strip()
39
+ if pid:
40
+ for p in load_presets():
41
+ if str(p.get("id") or "").strip() == pid:
42
+ return p
43
+ return None
44
+ return resolve_preset(None)
45
+
46
+
47
+ def get_vision_executor(vision_llm_id: Optional[str] = None):
48
+ from seed.core.llm_exec import get_llm_executor
49
+ from seed.core.llm_presets import llm_executor_from_resolved
50
+
51
+ pid = (vision_llm_id or "").strip()
52
+ if not pid:
53
+ pid = ca_env.pick_default("", "CODEAGENT_VISION_PRESET_ID").strip()
54
+ preset = resolve_preset_id(pid) if pid else None
55
+ if preset and preset_supports_vision(preset):
56
+ return llm_executor_from_resolved(preset)
57
+ if pid:
58
+ raise ValueError(f"vision preset not found or not supports_vision: {pid}")
59
+ raise ValueError("vision_llm_id required (no CODEAGENT_VISION_PRESET_ID fallback)")
60
+
61
+
62
+ def resolve_main_llm(llm_id: Optional[str] = None):
63
+ from seed.core.llm_presets import llm_executor_from_resolved, resolve_preset
64
+
65
+ pid = (llm_id or "").strip()
66
+ if pid:
67
+ from seed.core.llm_presets import load_presets
68
+
69
+ for p in load_presets():
70
+ if str(p.get("id") or "").strip() == pid:
71
+ preset = p
72
+ break
73
+ else:
74
+ preset = resolve_preset(None)
75
+ else:
76
+ preset = resolve_preset(None)
77
+
78
+ cfg = dict(preset)
79
+
80
+ # 会话级 max_tokens 覆盖(CODEAGENT_CHAT_MAX_TOKENS / SEED_LLM_MAX_TOKENS)
81
+ from codeagent.core import env as ca_env
82
+ from seed.core import env_access as _ea
83
+
84
+ mt_override = _ea.pick_nonempty(ca_env.CHAT_MAX_TOKENS)
85
+ if not mt_override:
86
+ mt_override = _ea.pick_nonempty(*_ea.LLM_MAX_TOKENS)
87
+ if mt_override:
88
+ try:
89
+ cfg["max_tokens"] = int(mt_override)
90
+ except (ValueError, TypeError):
91
+ pass
92
+
93
+ return llm_executor_from_resolved(cfg)