calvyn-code 0.14.10 → 0.14.11
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/cli.py +23 -16
- package/hermes_cli/model_switch.py +17 -1
- package/hermes_cli/models.py +8 -5
- package/package.json +1 -1
- package/plugins/platforms/vk/__init__.py +3 -0
- package/plugins/platforms/vk/adapter.py +105 -0
- package/plugins/platforms/vk/plugin.yaml +25 -0
- package/pyproject.toml +1 -1
- package/tools/lazy_deps.py +7 -4
package/cli.py
CHANGED
|
@@ -2679,7 +2679,7 @@ class HermesCLI:
|
|
|
2679
2679
|
# env vars would stomp each other.
|
|
2680
2680
|
_model_config = CLI_CONFIG.get("model", {})
|
|
2681
2681
|
_config_model = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
|
|
2682
|
-
_DEFAULT_CONFIG_MODEL = ""
|
|
2682
|
+
_DEFAULT_CONFIG_MODEL = "gpt-5.3-codex"
|
|
2683
2683
|
self.model = model or _config_model or _DEFAULT_CONFIG_MODEL
|
|
2684
2684
|
# Auto-detect model from local server if still on default
|
|
2685
2685
|
if self.model == _DEFAULT_CONFIG_MODEL:
|
|
@@ -4618,7 +4618,10 @@ class HermesCLI:
|
|
|
4618
4618
|
self._console_print()
|
|
4619
4619
|
self._console_print()
|
|
4620
4620
|
term_width = shutil.get_terminal_size().columns
|
|
4621
|
-
|
|
4621
|
+
_early_banner_shown = os.getenv("CALVYN_EARLY_BANNER_SHOWN", "").strip().lower() in {
|
|
4622
|
+
"1", "true", "yes", "on",
|
|
4623
|
+
}
|
|
4624
|
+
if term_width >= 95 and not _early_banner_shown:
|
|
4622
4625
|
self._console_print(_logo)
|
|
4623
4626
|
self._console_print()
|
|
4624
4627
|
self._console_print(_build_compact_banner())
|
|
@@ -6901,13 +6904,15 @@ class HermesCLI:
|
|
|
6901
6904
|
_cprint(" Prompt caching: enabled")
|
|
6902
6905
|
if result.warning_message:
|
|
6903
6906
|
_cprint(f" ⚠ {result.warning_message}")
|
|
6904
|
-
if persist_global:
|
|
6905
|
-
save_config_value("model.default", result.new_model)
|
|
6906
|
-
if result.provider_changed:
|
|
6907
|
-
save_config_value("model.provider", result.target_provider)
|
|
6908
|
-
_cprint(" Saved to config.yaml (--global)")
|
|
6909
|
-
else:
|
|
6910
|
-
|
|
6907
|
+
if persist_global:
|
|
6908
|
+
save_config_value("model.default", result.new_model)
|
|
6909
|
+
if result.provider_changed:
|
|
6910
|
+
save_config_value("model.provider", result.target_provider)
|
|
6911
|
+
_cprint(" Saved to config.yaml (--global)")
|
|
6912
|
+
else:
|
|
6913
|
+
save_config_value("model.default", result.new_model)
|
|
6914
|
+
save_config_value("model.provider", result.target_provider)
|
|
6915
|
+
_cprint(" Saved to config.yaml")
|
|
6911
6916
|
|
|
6912
6917
|
def _handle_model_picker_selection(self, persist_global: bool = False) -> None:
|
|
6913
6918
|
state = self._model_picker_state
|
|
@@ -7144,13 +7149,15 @@ class HermesCLI:
|
|
|
7144
7149
|
_cprint(f" ⚠ {result.warning_message}")
|
|
7145
7150
|
|
|
7146
7151
|
# Persistence
|
|
7147
|
-
if persist_global:
|
|
7148
|
-
save_config_value("model.default", result.new_model)
|
|
7149
|
-
if result.provider_changed:
|
|
7150
|
-
save_config_value("model.provider", result.target_provider)
|
|
7151
|
-
_cprint(" Saved to config.yaml (--global)")
|
|
7152
|
-
else:
|
|
7153
|
-
|
|
7152
|
+
if persist_global:
|
|
7153
|
+
save_config_value("model.default", result.new_model)
|
|
7154
|
+
if result.provider_changed:
|
|
7155
|
+
save_config_value("model.provider", result.target_provider)
|
|
7156
|
+
_cprint(" Saved to config.yaml (--global)")
|
|
7157
|
+
else:
|
|
7158
|
+
save_config_value("model.default", result.new_model)
|
|
7159
|
+
save_config_value("model.provider", result.target_provider)
|
|
7160
|
+
_cprint(" Saved to config.yaml")
|
|
7154
7161
|
|
|
7155
7162
|
def _handle_codex_runtime(self, cmd_original: str) -> None:
|
|
7156
7163
|
"""Handle /codex-runtime — toggle the codex app-server runtime opt-in.
|
|
@@ -170,7 +170,23 @@ class DirectAlias(NamedTuple):
|
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
# Built-in direct aliases (can be extended via config.yaml model_aliases:)
|
|
173
|
-
_BUILTIN_DIRECT_ALIASES: dict[str, DirectAlias] = {
|
|
173
|
+
_BUILTIN_DIRECT_ALIASES: dict[str, DirectAlias] = {
|
|
174
|
+
"freemodel/gpt": DirectAlias(
|
|
175
|
+
model="gpt-5.3-codex",
|
|
176
|
+
provider="freemodel",
|
|
177
|
+
base_url="https://freemodel.dev/v1",
|
|
178
|
+
),
|
|
179
|
+
"freemodel/gpt-5.3-codex": DirectAlias(
|
|
180
|
+
model="gpt-5.3-codex",
|
|
181
|
+
provider="freemodel",
|
|
182
|
+
base_url="https://freemodel.dev/v1",
|
|
183
|
+
),
|
|
184
|
+
"openai/gpt": DirectAlias(
|
|
185
|
+
model="gpt-5.4",
|
|
186
|
+
provider="openai",
|
|
187
|
+
base_url="",
|
|
188
|
+
),
|
|
189
|
+
}
|
|
174
190
|
|
|
175
191
|
# Merged dict (builtins + user config); populated by _load_direct_aliases()
|
|
176
192
|
DIRECT_ALIASES: dict[str, DirectAlias] = {}
|
package/hermes_cli/models.py
CHANGED
|
@@ -189,11 +189,12 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
|
|
|
189
189
|
],
|
|
190
190
|
# Native OpenAI Chat Completions (api.openai.com). Used by /model counts and
|
|
191
191
|
# provider_model_ids fallback when /v1/models is unavailable.
|
|
192
|
-
"openai": [
|
|
193
|
-
"gpt
|
|
194
|
-
"gpt-5.4
|
|
195
|
-
"gpt-5-mini",
|
|
196
|
-
"gpt-5
|
|
192
|
+
"openai": [
|
|
193
|
+
"openai/gpt",
|
|
194
|
+
"gpt-5.4",
|
|
195
|
+
"gpt-5.4-mini",
|
|
196
|
+
"gpt-5-mini",
|
|
197
|
+
"gpt-5.3-codex",
|
|
197
198
|
"gpt-5.2-codex",
|
|
198
199
|
"gpt-4.1",
|
|
199
200
|
"gpt-4o",
|
|
@@ -340,6 +341,8 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
|
|
|
340
341
|
"openai/gpt-5.4",
|
|
341
342
|
],
|
|
342
343
|
"freemodel": [
|
|
344
|
+
"freemodel/gpt",
|
|
345
|
+
"freemodel/gpt-5.3-codex",
|
|
343
346
|
"gpt-5.5",
|
|
344
347
|
"gpt-5.4",
|
|
345
348
|
"gpt-5.4-mini",
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from vkbottle.bot import Bot
|
|
10
|
+
from vkbottle import Message
|
|
11
|
+
VKBOTTLE_AVAILABLE = True
|
|
12
|
+
except Exception:
|
|
13
|
+
Bot = Any
|
|
14
|
+
Message = Any
|
|
15
|
+
VKBOTTLE_AVAILABLE = False
|
|
16
|
+
|
|
17
|
+
from gateway.config import Platform, PlatformConfig
|
|
18
|
+
from gateway.platforms.base import BasePlatformAdapter, MessageEvent, MessageType, SendResult
|
|
19
|
+
from gateway.session import SessionSource
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_requirements() -> bool:
|
|
23
|
+
if VKBOTTLE_AVAILABLE:
|
|
24
|
+
return True
|
|
25
|
+
try:
|
|
26
|
+
from tools.lazy_deps import ensure as _ensure
|
|
27
|
+
_ensure("platform.vk", prompt=False)
|
|
28
|
+
except Exception:
|
|
29
|
+
return False
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class VKAdapter(BasePlatformAdapter):
|
|
34
|
+
def __init__(self, config):
|
|
35
|
+
super().__init__(config=config, platform=Platform("vk"))
|
|
36
|
+
extra = getattr(config, "extra", {}) or {}
|
|
37
|
+
self.token = os.getenv("VK_BOT_TOKEN") or getattr(config, "token", "") or extra.get("token", "")
|
|
38
|
+
self.allowed_users = {str(x).strip() for x in (os.getenv("VK_ALLOWED_USERS", "") or extra.get("allowed_users", "") or "").split(",") if str(x).strip()}
|
|
39
|
+
self.allow_all = str(os.getenv("VK_ALLOW_ALL_USERS", "") or extra.get("allow_all_users", "") or "").lower() in {"1","true","yes","on"}
|
|
40
|
+
self.home_channel = os.getenv("VK_HOME_CHANNEL") or extra.get("home_channel", "")
|
|
41
|
+
self._bot = None
|
|
42
|
+
self._task = None
|
|
43
|
+
|
|
44
|
+
async def connect(self) -> bool:
|
|
45
|
+
if not self.token:
|
|
46
|
+
self._set_fatal_error("missing_token", "VK_BOT_TOKEN is required", retryable=False)
|
|
47
|
+
return False
|
|
48
|
+
self._bot = Bot(token=self.token)
|
|
49
|
+
|
|
50
|
+
@self._bot.on.message()
|
|
51
|
+
async def _on_message(message: Message):
|
|
52
|
+
if getattr(message, "out", False):
|
|
53
|
+
return
|
|
54
|
+
user_id = str(getattr(message, "from_id", "") or "")
|
|
55
|
+
if not self.allow_all and self.allowed_users and user_id not in self.allowed_users:
|
|
56
|
+
return
|
|
57
|
+
text = (getattr(message, "text", "") or "").strip()
|
|
58
|
+
if not text:
|
|
59
|
+
return
|
|
60
|
+
peer_id = str(getattr(message, "peer_id", "") or "")
|
|
61
|
+
source = SessionSource(platform=Platform("vk"), chat_id=peer_id, user_id=user_id, chat_type="dm" if peer_id and peer_id.isdigit() and int(peer_id) < 2_000_000_000 else "group")
|
|
62
|
+
event = MessageEvent(text=text, message_type=MessageType.TEXT, source=source, raw_message=message, message_id=str(getattr(message, "conversation_message_id", "") or ""))
|
|
63
|
+
await self.handle_message(event)
|
|
64
|
+
|
|
65
|
+
self._task = asyncio.create_task(self._bot.run_forever())
|
|
66
|
+
self._mark_connected()
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
async def disconnect(self) -> None:
|
|
70
|
+
self._mark_disconnected()
|
|
71
|
+
if self._task and not self._task.done():
|
|
72
|
+
self._task.cancel()
|
|
73
|
+
self._task = None
|
|
74
|
+
|
|
75
|
+
async def send(self, chat_id: str, content: str, reply_to: Optional[str] = None, metadata: Optional[dict] = None, **kwargs) -> SendResult:
|
|
76
|
+
if not self._bot:
|
|
77
|
+
return SendResult(success=False, error="VK bot not connected")
|
|
78
|
+
try:
|
|
79
|
+
await self._bot.api.messages.send(peer_id=int(chat_id), random_id=0, message=content)
|
|
80
|
+
return SendResult(success=True, message_id="vk")
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
return SendResult(success=False, error=str(exc))
|
|
83
|
+
|
|
84
|
+
async def send_typing(self, chat_id: str) -> None:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
async def get_chat_info(self, chat_id: str) -> dict:
|
|
88
|
+
return {"name": chat_id, "type": "dm", "chat_id": chat_id}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def register(ctx):
|
|
92
|
+
ctx.register_platform(
|
|
93
|
+
name="vk",
|
|
94
|
+
label="VK",
|
|
95
|
+
adapter_factory=lambda cfg: VKAdapter(cfg),
|
|
96
|
+
check_fn=check_requirements,
|
|
97
|
+
required_env=["VK_BOT_TOKEN"],
|
|
98
|
+
env_enablement_fn=lambda: {"token": os.getenv("VK_BOT_TOKEN", "")} if os.getenv("VK_BOT_TOKEN") else None,
|
|
99
|
+
cron_deliver_env_var="VK_HOME_CHANNEL",
|
|
100
|
+
allowed_users_env="VK_ALLOWED_USERS",
|
|
101
|
+
allow_all_env="VK_ALLOW_ALL_USERS",
|
|
102
|
+
emoji="💬",
|
|
103
|
+
pii_safe=False,
|
|
104
|
+
platform_hint="You are chatting in VK. Keep replies concise and plain-text friendly.",
|
|
105
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: vk-platform
|
|
2
|
+
label: VK
|
|
3
|
+
kind: platform
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: >
|
|
6
|
+
VK gateway adapter for Calvyn Code.
|
|
7
|
+
author: Zanderrr
|
|
8
|
+
requires_env:
|
|
9
|
+
- name: VK_BOT_TOKEN
|
|
10
|
+
description: "VK community token for the bot"
|
|
11
|
+
prompt: "VK token"
|
|
12
|
+
password: true
|
|
13
|
+
optional_env:
|
|
14
|
+
- name: VK_ALLOWED_USERS
|
|
15
|
+
description: "Comma-separated VK user IDs allowed to talk to the bot"
|
|
16
|
+
prompt: "Allowed users"
|
|
17
|
+
password: false
|
|
18
|
+
- name: VK_ALLOW_ALL_USERS
|
|
19
|
+
description: "Allow any VK user to talk to the bot"
|
|
20
|
+
prompt: "Allow all users? (true/false)"
|
|
21
|
+
password: false
|
|
22
|
+
- name: VK_HOME_CHANNEL
|
|
23
|
+
description: "Chat ID used for cron delivery"
|
|
24
|
+
prompt: "VK home channel"
|
|
25
|
+
password: false
|
package/pyproject.toml
CHANGED
|
@@ -81,7 +81,7 @@ daytona = ["daytona==0.155.0"]
|
|
|
81
81
|
vercel = ["vercel==0.5.7"]
|
|
82
82
|
hindsight = ["hindsight-client==0.6.1"]
|
|
83
83
|
dev = ["debugpy==1.8.20", "pytest==9.0.2", "pytest-asyncio==1.3.0", "pytest-xdist==3.8.0", "pytest-split==0.11.0", "mcp==1.26.0", "ty==0.0.21", "ruff==0.15.10"]
|
|
84
|
-
messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.13.3", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2"]
|
|
84
|
+
messaging = ["python-telegram-bot[webhooks]==22.6", "discord.py[voice]==2.7.1", "aiohttp==3.13.3", "brotlicffi==1.2.0.1", "slack-bolt==1.27.0", "slack-sdk==3.40.1", "qrcode==7.4.2", "vkbottle==4.6.2"]
|
|
85
85
|
cron = [] # croniter is now a core dependency; this extra kept for back-compat
|
|
86
86
|
slack = ["slack-bolt==1.27.0", "slack-sdk==3.40.1", "aiohttp==3.13.3"]
|
|
87
87
|
matrix = ["mautrix[encryption]==0.21.0", "Markdown==3.10.2", "aiosqlite==0.22.1", "asyncpg==0.31.0", "aiohttp-socks==0.11.0"]
|
package/tools/lazy_deps.py
CHANGED
|
@@ -139,10 +139,13 @@ LAZY_DEPS: dict[str, tuple[str, ...]] = {
|
|
|
139
139
|
"alibabacloud-dingtalk==2.2.42",
|
|
140
140
|
"qrcode==7.4.2",
|
|
141
141
|
),
|
|
142
|
-
"platform.feishu": (
|
|
143
|
-
"lark-oapi==1.5.3",
|
|
144
|
-
"qrcode==7.4.2",
|
|
145
|
-
),
|
|
142
|
+
"platform.feishu": (
|
|
143
|
+
"lark-oapi==1.5.3",
|
|
144
|
+
"qrcode==7.4.2",
|
|
145
|
+
),
|
|
146
|
+
"platform.vk": (
|
|
147
|
+
"vkbottle==4.6.2",
|
|
148
|
+
),
|
|
146
149
|
|
|
147
150
|
# ─── Terminal backends ─────────────────────────────────────────────────
|
|
148
151
|
"terminal.modal": ("modal==1.3.4",),
|