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.
Files changed (103) hide show
  1. takopi/__init__.py +1 -0
  2. takopi/api.py +116 -0
  3. takopi/backends.py +25 -0
  4. takopi/backends_helpers.py +14 -0
  5. takopi/cli/__init__.py +228 -0
  6. takopi/cli/config.py +320 -0
  7. takopi/cli/doctor.py +173 -0
  8. takopi/cli/init.py +113 -0
  9. takopi/cli/onboarding_cmd.py +126 -0
  10. takopi/cli/plugins.py +196 -0
  11. takopi/cli/run.py +419 -0
  12. takopi/cli/topic.py +355 -0
  13. takopi/commands.py +134 -0
  14. takopi/config.py +142 -0
  15. takopi/config_migrations.py +124 -0
  16. takopi/config_watch.py +146 -0
  17. takopi/context.py +9 -0
  18. takopi/directives.py +146 -0
  19. takopi/engines.py +53 -0
  20. takopi/events.py +170 -0
  21. takopi/ids.py +17 -0
  22. takopi/lockfile.py +158 -0
  23. takopi/logging.py +283 -0
  24. takopi/markdown.py +298 -0
  25. takopi/model.py +77 -0
  26. takopi/plugins.py +312 -0
  27. takopi/presenter.py +25 -0
  28. takopi/progress.py +99 -0
  29. takopi/router.py +113 -0
  30. takopi/runner.py +712 -0
  31. takopi/runner_bridge.py +619 -0
  32. takopi/runners/__init__.py +1 -0
  33. takopi/runners/claude.py +483 -0
  34. takopi/runners/codex.py +656 -0
  35. takopi/runners/mock.py +221 -0
  36. takopi/runners/opencode.py +505 -0
  37. takopi/runners/pi.py +523 -0
  38. takopi/runners/run_options.py +39 -0
  39. takopi/runners/tool_actions.py +90 -0
  40. takopi/runtime_loader.py +207 -0
  41. takopi/scheduler.py +159 -0
  42. takopi/schemas/__init__.py +1 -0
  43. takopi/schemas/claude.py +238 -0
  44. takopi/schemas/codex.py +169 -0
  45. takopi/schemas/opencode.py +51 -0
  46. takopi/schemas/pi.py +117 -0
  47. takopi/settings.py +360 -0
  48. takopi/telegram/__init__.py +20 -0
  49. takopi/telegram/api_models.py +37 -0
  50. takopi/telegram/api_schemas.py +152 -0
  51. takopi/telegram/backend.py +163 -0
  52. takopi/telegram/bridge.py +425 -0
  53. takopi/telegram/chat_prefs.py +242 -0
  54. takopi/telegram/chat_sessions.py +112 -0
  55. takopi/telegram/client.py +409 -0
  56. takopi/telegram/client_api.py +539 -0
  57. takopi/telegram/commands/__init__.py +12 -0
  58. takopi/telegram/commands/agent.py +196 -0
  59. takopi/telegram/commands/cancel.py +116 -0
  60. takopi/telegram/commands/dispatch.py +111 -0
  61. takopi/telegram/commands/executor.py +449 -0
  62. takopi/telegram/commands/file_transfer.py +586 -0
  63. takopi/telegram/commands/handlers.py +45 -0
  64. takopi/telegram/commands/media.py +143 -0
  65. takopi/telegram/commands/menu.py +139 -0
  66. takopi/telegram/commands/model.py +215 -0
  67. takopi/telegram/commands/overrides.py +159 -0
  68. takopi/telegram/commands/parse.py +30 -0
  69. takopi/telegram/commands/plan.py +16 -0
  70. takopi/telegram/commands/reasoning.py +234 -0
  71. takopi/telegram/commands/reply.py +23 -0
  72. takopi/telegram/commands/topics.py +332 -0
  73. takopi/telegram/commands/trigger.py +143 -0
  74. takopi/telegram/context.py +140 -0
  75. takopi/telegram/engine_defaults.py +86 -0
  76. takopi/telegram/engine_overrides.py +105 -0
  77. takopi/telegram/files.py +178 -0
  78. takopi/telegram/loop.py +1822 -0
  79. takopi/telegram/onboarding.py +1088 -0
  80. takopi/telegram/outbox.py +177 -0
  81. takopi/telegram/parsing.py +239 -0
  82. takopi/telegram/render.py +198 -0
  83. takopi/telegram/state_store.py +88 -0
  84. takopi/telegram/topic_state.py +334 -0
  85. takopi/telegram/topics.py +256 -0
  86. takopi/telegram/trigger_mode.py +68 -0
  87. takopi/telegram/types.py +63 -0
  88. takopi/telegram/voice.py +110 -0
  89. takopi/transport.py +53 -0
  90. takopi/transport_runtime.py +323 -0
  91. takopi/transports.py +76 -0
  92. takopi/utils/__init__.py +1 -0
  93. takopi/utils/git.py +87 -0
  94. takopi/utils/json_state.py +21 -0
  95. takopi/utils/paths.py +47 -0
  96. takopi/utils/streams.py +44 -0
  97. takopi/utils/subprocess.py +86 -0
  98. takopi/worktrees.py +135 -0
  99. yee88-0.1.0.dist-info/METADATA +116 -0
  100. yee88-0.1.0.dist-info/RECORD +103 -0
  101. yee88-0.1.0.dist-info/WHEEL +4 -0
  102. yee88-0.1.0.dist-info/entry_points.txt +11 -0
  103. 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