yee88 0.1.0__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.
- takopi/__init__.py +1 -0
- takopi/api.py +116 -0
- takopi/backends.py +25 -0
- takopi/backends_helpers.py +14 -0
- takopi/cli/__init__.py +228 -0
- takopi/cli/config.py +320 -0
- takopi/cli/doctor.py +173 -0
- takopi/cli/init.py +113 -0
- takopi/cli/onboarding_cmd.py +126 -0
- takopi/cli/plugins.py +196 -0
- takopi/cli/run.py +419 -0
- takopi/cli/topic.py +355 -0
- takopi/commands.py +134 -0
- takopi/config.py +142 -0
- takopi/config_migrations.py +124 -0
- takopi/config_watch.py +146 -0
- takopi/context.py +9 -0
- takopi/directives.py +146 -0
- takopi/engines.py +53 -0
- takopi/events.py +170 -0
- takopi/ids.py +17 -0
- takopi/lockfile.py +158 -0
- takopi/logging.py +283 -0
- takopi/markdown.py +298 -0
- takopi/model.py +77 -0
- takopi/plugins.py +312 -0
- takopi/presenter.py +25 -0
- takopi/progress.py +99 -0
- takopi/router.py +113 -0
- takopi/runner.py +712 -0
- takopi/runner_bridge.py +619 -0
- takopi/runners/__init__.py +1 -0
- takopi/runners/claude.py +483 -0
- takopi/runners/codex.py +656 -0
- takopi/runners/mock.py +221 -0
- takopi/runners/opencode.py +505 -0
- takopi/runners/pi.py +523 -0
- takopi/runners/run_options.py +39 -0
- takopi/runners/tool_actions.py +90 -0
- takopi/runtime_loader.py +207 -0
- takopi/scheduler.py +159 -0
- takopi/schemas/__init__.py +1 -0
- takopi/schemas/claude.py +238 -0
- takopi/schemas/codex.py +169 -0
- takopi/schemas/opencode.py +51 -0
- takopi/schemas/pi.py +117 -0
- takopi/settings.py +360 -0
- takopi/telegram/__init__.py +20 -0
- takopi/telegram/api_models.py +37 -0
- takopi/telegram/api_schemas.py +152 -0
- takopi/telegram/backend.py +163 -0
- takopi/telegram/bridge.py +425 -0
- takopi/telegram/chat_prefs.py +242 -0
- takopi/telegram/chat_sessions.py +112 -0
- takopi/telegram/client.py +409 -0
- takopi/telegram/client_api.py +539 -0
- takopi/telegram/commands/__init__.py +12 -0
- takopi/telegram/commands/agent.py +196 -0
- takopi/telegram/commands/cancel.py +116 -0
- takopi/telegram/commands/dispatch.py +111 -0
- takopi/telegram/commands/executor.py +449 -0
- takopi/telegram/commands/file_transfer.py +586 -0
- takopi/telegram/commands/handlers.py +45 -0
- takopi/telegram/commands/media.py +143 -0
- takopi/telegram/commands/menu.py +139 -0
- takopi/telegram/commands/model.py +215 -0
- takopi/telegram/commands/overrides.py +159 -0
- takopi/telegram/commands/parse.py +30 -0
- takopi/telegram/commands/plan.py +16 -0
- takopi/telegram/commands/reasoning.py +234 -0
- takopi/telegram/commands/reply.py +23 -0
- takopi/telegram/commands/topics.py +332 -0
- takopi/telegram/commands/trigger.py +143 -0
- takopi/telegram/context.py +140 -0
- takopi/telegram/engine_defaults.py +86 -0
- takopi/telegram/engine_overrides.py +105 -0
- takopi/telegram/files.py +178 -0
- takopi/telegram/loop.py +1822 -0
- takopi/telegram/onboarding.py +1088 -0
- takopi/telegram/outbox.py +177 -0
- takopi/telegram/parsing.py +239 -0
- takopi/telegram/render.py +198 -0
- takopi/telegram/state_store.py +88 -0
- takopi/telegram/topic_state.py +334 -0
- takopi/telegram/topics.py +256 -0
- takopi/telegram/trigger_mode.py +68 -0
- takopi/telegram/types.py +63 -0
- takopi/telegram/voice.py +110 -0
- takopi/transport.py +53 -0
- takopi/transport_runtime.py +323 -0
- takopi/transports.py +76 -0
- takopi/utils/__init__.py +1 -0
- takopi/utils/git.py +87 -0
- takopi/utils/json_state.py +21 -0
- takopi/utils/paths.py +47 -0
- takopi/utils/streams.py +44 -0
- takopi/utils/subprocess.py +86 -0
- takopi/worktrees.py +135 -0
- yee88-0.1.0.dist-info/METADATA +116 -0
- yee88-0.1.0.dist-info/RECORD +103 -0
- yee88-0.1.0.dist-info/WHEEL +4 -0
- yee88-0.1.0.dist-info/entry_points.txt +11 -0
- yee88-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .api_schemas import (
|
|
4
|
+
CallbackQuery,
|
|
5
|
+
CallbackQueryMessage,
|
|
6
|
+
Chat,
|
|
7
|
+
ChatMember,
|
|
8
|
+
Document,
|
|
9
|
+
File,
|
|
10
|
+
ForumTopic,
|
|
11
|
+
Message,
|
|
12
|
+
MessageReply,
|
|
13
|
+
PhotoSize,
|
|
14
|
+
Sticker,
|
|
15
|
+
Update,
|
|
16
|
+
User,
|
|
17
|
+
Video,
|
|
18
|
+
Voice,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"CallbackQuery",
|
|
23
|
+
"CallbackQueryMessage",
|
|
24
|
+
"Chat",
|
|
25
|
+
"ChatMember",
|
|
26
|
+
"Document",
|
|
27
|
+
"File",
|
|
28
|
+
"ForumTopic",
|
|
29
|
+
"Message",
|
|
30
|
+
"MessageReply",
|
|
31
|
+
"PhotoSize",
|
|
32
|
+
"Sticker",
|
|
33
|
+
"Update",
|
|
34
|
+
"User",
|
|
35
|
+
"Video",
|
|
36
|
+
"Voice",
|
|
37
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Msgspec models for Telegram Bot API payloads (subset used by takopi).
|
|
2
|
+
|
|
3
|
+
Derived from telegram-api.html in the repository.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import msgspec
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CallbackQuery",
|
|
12
|
+
"CallbackQueryMessage",
|
|
13
|
+
"Chat",
|
|
14
|
+
"ChatMember",
|
|
15
|
+
"Document",
|
|
16
|
+
"File",
|
|
17
|
+
"ForumTopic",
|
|
18
|
+
"Message",
|
|
19
|
+
"MessageReply",
|
|
20
|
+
"PhotoSize",
|
|
21
|
+
"Sticker",
|
|
22
|
+
"Update",
|
|
23
|
+
"User",
|
|
24
|
+
"Video",
|
|
25
|
+
"Voice",
|
|
26
|
+
"decode_update",
|
|
27
|
+
"decode_updates",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class User(msgspec.Struct, forbid_unknown_fields=False):
|
|
32
|
+
id: int
|
|
33
|
+
is_bot: bool | None = None
|
|
34
|
+
username: str | None = None
|
|
35
|
+
first_name: str | None = None
|
|
36
|
+
last_name: str | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Chat(msgspec.Struct, forbid_unknown_fields=False):
|
|
40
|
+
id: int
|
|
41
|
+
type: str
|
|
42
|
+
title: str | None = None
|
|
43
|
+
username: str | None = None
|
|
44
|
+
first_name: str | None = None
|
|
45
|
+
last_name: str | None = None
|
|
46
|
+
is_forum: bool | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PhotoSize(msgspec.Struct, forbid_unknown_fields=False):
|
|
50
|
+
file_id: str
|
|
51
|
+
width: int
|
|
52
|
+
height: int
|
|
53
|
+
file_size: int | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Document(msgspec.Struct, forbid_unknown_fields=False):
|
|
57
|
+
file_id: str
|
|
58
|
+
file_name: str | None = None
|
|
59
|
+
mime_type: str | None = None
|
|
60
|
+
file_size: int | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Video(msgspec.Struct, forbid_unknown_fields=False):
|
|
64
|
+
file_id: str
|
|
65
|
+
file_name: str | None = None
|
|
66
|
+
mime_type: str | None = None
|
|
67
|
+
file_size: int | None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Voice(msgspec.Struct, forbid_unknown_fields=False):
|
|
71
|
+
file_id: str
|
|
72
|
+
duration: int | None = None
|
|
73
|
+
mime_type: str | None = None
|
|
74
|
+
file_size: int | None = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Sticker(msgspec.Struct, forbid_unknown_fields=False):
|
|
78
|
+
file_id: str
|
|
79
|
+
file_size: int | None = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class MessageReply(msgspec.Struct, forbid_unknown_fields=False):
|
|
83
|
+
message_id: int
|
|
84
|
+
text: str | None = None
|
|
85
|
+
from_: User | None = msgspec.field(default=None, name="from")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Message(msgspec.Struct, forbid_unknown_fields=False):
|
|
89
|
+
message_id: int
|
|
90
|
+
chat: Chat
|
|
91
|
+
message_thread_id: int | None = None
|
|
92
|
+
from_: User | None = msgspec.field(default=None, name="from")
|
|
93
|
+
text: str | None = None
|
|
94
|
+
caption: str | None = None
|
|
95
|
+
reply_to_message: MessageReply | None = None
|
|
96
|
+
forward_from: User | None = None
|
|
97
|
+
forward_from_chat: Chat | None = None
|
|
98
|
+
forward_from_message_id: int | None = None
|
|
99
|
+
forward_sender_name: str | None = None
|
|
100
|
+
forward_signature: str | None = None
|
|
101
|
+
forward_date: int | None = None
|
|
102
|
+
media_group_id: str | None = None
|
|
103
|
+
is_automatic_forward: bool | None = None
|
|
104
|
+
is_topic_message: bool | None = None
|
|
105
|
+
voice: Voice | None = None
|
|
106
|
+
document: Document | None = None
|
|
107
|
+
video: Video | None = None
|
|
108
|
+
photo: list[PhotoSize] | None = None
|
|
109
|
+
sticker: Sticker | None = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CallbackQueryMessage(msgspec.Struct, forbid_unknown_fields=False):
|
|
113
|
+
message_id: int
|
|
114
|
+
chat: Chat
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class CallbackQuery(msgspec.Struct, forbid_unknown_fields=False):
|
|
118
|
+
id: str
|
|
119
|
+
from_: User = msgspec.field(name="from")
|
|
120
|
+
message: CallbackQueryMessage | None = None
|
|
121
|
+
data: str | None = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Update(msgspec.Struct, forbid_unknown_fields=False):
|
|
125
|
+
update_id: int
|
|
126
|
+
message: Message | None = None
|
|
127
|
+
callback_query: CallbackQuery | None = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class File(msgspec.Struct, forbid_unknown_fields=False):
|
|
131
|
+
file_path: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ChatMember(msgspec.Struct, forbid_unknown_fields=False):
|
|
135
|
+
status: str
|
|
136
|
+
can_manage_topics: bool | None = None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ForumTopic(msgspec.Struct, forbid_unknown_fields=False):
|
|
140
|
+
message_thread_id: int
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
_UPDATE_DECODER = msgspec.json.Decoder(Update)
|
|
144
|
+
_UPDATES_DECODER = msgspec.json.Decoder(list[Update])
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def decode_update(payload: str | bytes) -> Update:
|
|
148
|
+
return _UPDATE_DECODER.decode(payload)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def decode_updates(payload: str | bytes) -> list[Update]:
|
|
152
|
+
return _UPDATES_DECODER.decode(payload)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
import anyio
|
|
8
|
+
|
|
9
|
+
from ..backends import EngineBackend
|
|
10
|
+
from ..logging import get_logger
|
|
11
|
+
from ..runner_bridge import ExecBridgeConfig
|
|
12
|
+
from ..settings import TelegramTopicsSettings, TelegramTransportSettings, load_settings_if_exists
|
|
13
|
+
from ..transport_runtime import TransportRuntime
|
|
14
|
+
from ..transports import SetupResult, TransportBackend
|
|
15
|
+
from .bridge import (
|
|
16
|
+
TelegramBridgeConfig,
|
|
17
|
+
TelegramPresenter,
|
|
18
|
+
TelegramTransport,
|
|
19
|
+
run_main_loop,
|
|
20
|
+
)
|
|
21
|
+
from .client import TelegramClient
|
|
22
|
+
from .onboarding import check_setup, interactive_setup
|
|
23
|
+
from .topics import _resolve_topics_scope_raw
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _expect_transport_settings(transport_config: object) -> TelegramTransportSettings:
|
|
29
|
+
if isinstance(transport_config, TelegramTransportSettings):
|
|
30
|
+
return transport_config
|
|
31
|
+
raise TypeError("transport_config must be TelegramTransportSettings")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _build_startup_message(
|
|
35
|
+
runtime: TransportRuntime,
|
|
36
|
+
*,
|
|
37
|
+
startup_pwd: str,
|
|
38
|
+
chat_id: int,
|
|
39
|
+
session_mode: Literal["stateless", "chat"],
|
|
40
|
+
show_resume_line: bool,
|
|
41
|
+
topics: TelegramTopicsSettings,
|
|
42
|
+
) -> str:
|
|
43
|
+
available_engines = list(runtime.available_engine_ids())
|
|
44
|
+
missing_engines = list(runtime.missing_engine_ids())
|
|
45
|
+
misconfigured_engines = list(runtime.engine_ids_with_status("bad_config"))
|
|
46
|
+
failed_engines = list(runtime.engine_ids_with_status("load_error"))
|
|
47
|
+
|
|
48
|
+
engine_list = ", ".join(available_engines) if available_engines else "none"
|
|
49
|
+
|
|
50
|
+
notes: list[str] = []
|
|
51
|
+
if missing_engines:
|
|
52
|
+
notes.append(f"not installed: {', '.join(missing_engines)}")
|
|
53
|
+
if misconfigured_engines:
|
|
54
|
+
notes.append(f"misconfigured: {', '.join(misconfigured_engines)}")
|
|
55
|
+
if failed_engines:
|
|
56
|
+
notes.append(f"failed to load: {', '.join(failed_engines)}")
|
|
57
|
+
if notes:
|
|
58
|
+
engine_list = f"{engine_list} ({'; '.join(notes)})"
|
|
59
|
+
project_aliases = sorted(set(runtime.project_aliases()), key=str.lower)
|
|
60
|
+
project_list = ", ".join(project_aliases) if project_aliases else "none"
|
|
61
|
+
resume_label = "shown" if show_resume_line else "hidden"
|
|
62
|
+
topics_label = "disabled"
|
|
63
|
+
if topics.enabled:
|
|
64
|
+
resolved_scope, _ = _resolve_topics_scope_raw(
|
|
65
|
+
topics.scope, chat_id, runtime.project_chat_ids()
|
|
66
|
+
)
|
|
67
|
+
scope_label = (
|
|
68
|
+
f"auto ({resolved_scope})" if topics.scope == "auto" else resolved_scope
|
|
69
|
+
)
|
|
70
|
+
topics_label = f"enabled (scope={scope_label})"
|
|
71
|
+
return (
|
|
72
|
+
f"\N{OCTOPUS} **takopi is ready**\n\n"
|
|
73
|
+
f"default: `{runtime.default_engine}` \n"
|
|
74
|
+
f"engines: `{engine_list}` \n"
|
|
75
|
+
f"projects: `{project_list}` \n"
|
|
76
|
+
f"mode: `{session_mode}` \n"
|
|
77
|
+
f"topics: `{topics_label}` \n"
|
|
78
|
+
f"resume lines: `{resume_label}` \n"
|
|
79
|
+
f"working in: `{startup_pwd}`"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TelegramBackend(TransportBackend):
|
|
84
|
+
id = "telegram"
|
|
85
|
+
description = "Telegram bot"
|
|
86
|
+
|
|
87
|
+
def check_setup(
|
|
88
|
+
self,
|
|
89
|
+
engine_backend: EngineBackend,
|
|
90
|
+
*,
|
|
91
|
+
transport_override: str | None = None,
|
|
92
|
+
) -> SetupResult:
|
|
93
|
+
return check_setup(engine_backend, transport_override=transport_override)
|
|
94
|
+
|
|
95
|
+
async def interactive_setup(self, *, force: bool) -> bool:
|
|
96
|
+
return await interactive_setup(force=force)
|
|
97
|
+
|
|
98
|
+
def lock_token(self, *, transport_config: object, _config_path: Path) -> str | None:
|
|
99
|
+
settings = _expect_transport_settings(transport_config)
|
|
100
|
+
return settings.bot_token
|
|
101
|
+
|
|
102
|
+
def build_and_run(
|
|
103
|
+
self,
|
|
104
|
+
*,
|
|
105
|
+
transport_config: object,
|
|
106
|
+
config_path: Path,
|
|
107
|
+
runtime: TransportRuntime,
|
|
108
|
+
final_notify: bool,
|
|
109
|
+
default_engine_override: str | None,
|
|
110
|
+
) -> None:
|
|
111
|
+
settings = _expect_transport_settings(transport_config)
|
|
112
|
+
token = settings.bot_token
|
|
113
|
+
chat_id = settings.chat_id
|
|
114
|
+
startup_msg = _build_startup_message(
|
|
115
|
+
runtime,
|
|
116
|
+
startup_pwd=os.getcwd(),
|
|
117
|
+
chat_id=chat_id,
|
|
118
|
+
session_mode=settings.session_mode,
|
|
119
|
+
show_resume_line=settings.show_resume_line,
|
|
120
|
+
topics=settings.topics,
|
|
121
|
+
)
|
|
122
|
+
bot = TelegramClient(token)
|
|
123
|
+
transport = TelegramTransport(bot)
|
|
124
|
+
presenter = TelegramPresenter(message_overflow=settings.message_overflow)
|
|
125
|
+
exec_cfg = ExecBridgeConfig(
|
|
126
|
+
transport=transport,
|
|
127
|
+
presenter=presenter,
|
|
128
|
+
final_notify=final_notify,
|
|
129
|
+
)
|
|
130
|
+
cfg = TelegramBridgeConfig(
|
|
131
|
+
bot=bot,
|
|
132
|
+
runtime=runtime,
|
|
133
|
+
chat_id=chat_id,
|
|
134
|
+
startup_msg=startup_msg,
|
|
135
|
+
exec_cfg=exec_cfg,
|
|
136
|
+
session_mode=settings.session_mode,
|
|
137
|
+
show_resume_line=settings.show_resume_line,
|
|
138
|
+
voice_transcription=settings.voice_transcription,
|
|
139
|
+
voice_max_bytes=int(settings.voice_max_bytes),
|
|
140
|
+
voice_transcription_model=settings.voice_transcription_model,
|
|
141
|
+
voice_transcription_base_url=settings.voice_transcription_base_url,
|
|
142
|
+
voice_transcription_api_key=settings.voice_transcription_api_key,
|
|
143
|
+
forward_coalesce_s=settings.forward_coalesce_s,
|
|
144
|
+
media_group_debounce_s=settings.media_group_debounce_s,
|
|
145
|
+
allowed_user_ids=tuple(settings.allowed_user_ids),
|
|
146
|
+
topics=settings.topics,
|
|
147
|
+
files=settings.files,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
async def run_loop() -> None:
|
|
151
|
+
await run_main_loop(
|
|
152
|
+
cfg,
|
|
153
|
+
watch_config=runtime.watch_config,
|
|
154
|
+
default_engine_override=default_engine_override,
|
|
155
|
+
transport_id=self.id,
|
|
156
|
+
transport_config=settings,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
anyio.run(run_loop)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
telegram_backend = TelegramBackend()
|
|
163
|
+
BACKEND = telegram_backend
|