kimi-agent-module-api 1.0.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.
- kimi_agent_module_api/__init__.py +175 -0
- kimi_agent_module_api/contracts.py +1127 -0
- kimi_agent_module_api/events.py +140 -0
- kimi_agent_module_api/images.py +34 -0
- kimi_agent_module_api/py.typed +1 -0
- kimi_agent_module_api/settings.py +33 -0
- kimi_agent_module_api/testing.py +1036 -0
- kimi_agent_module_api/tools.py +63 -0
- kimi_agent_module_api/trust.py +36 -0
- kimi_agent_module_api-1.0.0.dist-info/METADATA +51 -0
- kimi_agent_module_api-1.0.0.dist-info/RECORD +14 -0
- kimi_agent_module_api-1.0.0.dist-info/WHEEL +5 -0
- kimi_agent_module_api-1.0.0.dist-info/licenses/LICENSE +21 -0
- kimi_agent_module_api-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Normalized core Discord events published on the module event bus.
|
|
2
|
+
|
|
3
|
+
Each dataclass carries what a subscriber cannot re-fetch after the fact:
|
|
4
|
+
deleted or pre-edit content, removed members' roles, role and timeout deltas,
|
|
5
|
+
audit-log changes. Anything still live is fetched through a declared Discord
|
|
6
|
+
action instead. Payloads never contain Discord SDK objects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Literal
|
|
14
|
+
|
|
15
|
+
from kimi_agent_module_api.contracts import (
|
|
16
|
+
CORE_TOPIC_PREFIX,
|
|
17
|
+
AttachmentSnapshot,
|
|
18
|
+
InviteSnapshot,
|
|
19
|
+
MemberSnapshot,
|
|
20
|
+
MessageRef,
|
|
21
|
+
MessageSnapshot,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
TOPIC_MESSAGE = f"{CORE_TOPIC_PREFIX}.message"
|
|
25
|
+
TOPIC_MESSAGE_EDIT = f"{CORE_TOPIC_PREFIX}.message_edit"
|
|
26
|
+
TOPIC_MESSAGE_DELETE = f"{CORE_TOPIC_PREFIX}.message_delete"
|
|
27
|
+
TOPIC_MESSAGE_BULK_DELETE = f"{CORE_TOPIC_PREFIX}.message_bulk_delete"
|
|
28
|
+
TOPIC_INVITE_CREATE = f"{CORE_TOPIC_PREFIX}.invite_create"
|
|
29
|
+
TOPIC_INVITE_DELETE = f"{CORE_TOPIC_PREFIX}.invite_delete"
|
|
30
|
+
TOPIC_MEMBER_JOIN = f"{CORE_TOPIC_PREFIX}.member_join"
|
|
31
|
+
TOPIC_MEMBER_REMOVE = f"{CORE_TOPIC_PREFIX}.member_remove"
|
|
32
|
+
TOPIC_MEMBER_UPDATE = f"{CORE_TOPIC_PREFIX}.member_update"
|
|
33
|
+
TOPIC_AUDIT_LOG_ENTRY = f"{CORE_TOPIC_PREFIX}.audit_log_entry"
|
|
34
|
+
|
|
35
|
+
CORE_TOPICS: frozenset[str] = frozenset(
|
|
36
|
+
{
|
|
37
|
+
TOPIC_MESSAGE,
|
|
38
|
+
TOPIC_MESSAGE_EDIT,
|
|
39
|
+
TOPIC_MESSAGE_DELETE,
|
|
40
|
+
TOPIC_MESSAGE_BULK_DELETE,
|
|
41
|
+
TOPIC_INVITE_CREATE,
|
|
42
|
+
TOPIC_INVITE_DELETE,
|
|
43
|
+
TOPIC_MEMBER_JOIN,
|
|
44
|
+
TOPIC_MEMBER_REMOVE,
|
|
45
|
+
TOPIC_MEMBER_UPDATE,
|
|
46
|
+
TOPIC_AUDIT_LOG_ENTRY,
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class MessageEvent:
|
|
53
|
+
message: MessageSnapshot
|
|
54
|
+
author_is_bot: bool
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True)
|
|
58
|
+
class MessageEditEvent:
|
|
59
|
+
ref: MessageRef
|
|
60
|
+
author_id: int | None
|
|
61
|
+
before_content: str | None
|
|
62
|
+
after_content: str | None
|
|
63
|
+
edited_at: float
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True, slots=True)
|
|
67
|
+
class MessageDeleteEvent:
|
|
68
|
+
ref: MessageRef
|
|
69
|
+
author_id: int | None
|
|
70
|
+
cached_content: str | None
|
|
71
|
+
cached_attachments: tuple[AttachmentSnapshot, ...]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True, slots=True)
|
|
75
|
+
class MessageBulkDeleteEvent:
|
|
76
|
+
refs: tuple[MessageRef, ...]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True, slots=True)
|
|
80
|
+
class InviteCreateEvent:
|
|
81
|
+
invite: InviteSnapshot
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True, slots=True)
|
|
85
|
+
class InviteDeleteEvent:
|
|
86
|
+
invite: InviteSnapshot
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True, slots=True)
|
|
90
|
+
class MemberJoinEvent:
|
|
91
|
+
member: MemberSnapshot
|
|
92
|
+
account_created_at: float
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True, slots=True)
|
|
96
|
+
class MemberRemoveEvent:
|
|
97
|
+
guild_id: int
|
|
98
|
+
user_id: int
|
|
99
|
+
roles_at_removal: tuple[int, ...]
|
|
100
|
+
display_name: str = ""
|
|
101
|
+
is_bot: bool = False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True, slots=True)
|
|
105
|
+
class MemberUpdateEvent:
|
|
106
|
+
guild_id: int
|
|
107
|
+
user_id: int
|
|
108
|
+
roles_added: tuple[int, ...]
|
|
109
|
+
roles_removed: tuple[int, ...]
|
|
110
|
+
timed_out_until_before: float | None
|
|
111
|
+
timed_out_until_after: float | None
|
|
112
|
+
nickname_before: str | None
|
|
113
|
+
nickname_after: str | None
|
|
114
|
+
display_name: str = ""
|
|
115
|
+
is_bot: bool = False
|
|
116
|
+
# Names for every role id in roles_added / roles_removed.
|
|
117
|
+
role_names: Mapping[int, str] = field(default_factory=dict)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
type AuditAction = Literal[
|
|
121
|
+
"ban", "unban", "kick", "timeout", "timeout_cleared", "member_update", "other"
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True, slots=True)
|
|
126
|
+
class AuditLogEntryEvent:
|
|
127
|
+
guild_id: int
|
|
128
|
+
entry_id: int
|
|
129
|
+
action: AuditAction
|
|
130
|
+
raw_action: str
|
|
131
|
+
actor_id: int | None
|
|
132
|
+
target_id: int | None
|
|
133
|
+
reason: str | None
|
|
134
|
+
changes: tuple[tuple[str, Any, Any], ...]
|
|
135
|
+
created_at: float
|
|
136
|
+
target_display_name: str | None = None
|
|
137
|
+
# Timeout expiry (unix seconds) for `timeout` actions.
|
|
138
|
+
until: float | None = None
|
|
139
|
+
# True when this bot performed the action (e.g. through a module command).
|
|
140
|
+
actor_is_self: bool = False
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Pure image helpers that do not depend on a host runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
SUPPORTED_IMAGE_MEDIA_TYPES = frozenset({"image/png", "image/jpeg", "image/gif", "image/webp"})
|
|
6
|
+
IMAGE_FILENAME_SUFFIXES = (".png", ".jpg", ".jpeg", ".gif", ".webp")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def looks_like_image_attachment(filename: str | None, content_type: str | None) -> bool:
|
|
10
|
+
"""Use declared metadata as a cheap candidate filter before byte sniffing."""
|
|
11
|
+
declared = str(content_type or "").strip().lower()
|
|
12
|
+
name = str(filename or "").strip().lower()
|
|
13
|
+
return declared.startswith("image/") or name.endswith(IMAGE_FILENAME_SUFFIXES)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def sniff_image_media_type(payload: bytes) -> str | None:
|
|
17
|
+
"""Return a supported image media type from magic bytes, or None."""
|
|
18
|
+
if payload.startswith(b"\x89PNG\r\n\x1a\n"):
|
|
19
|
+
return "image/png"
|
|
20
|
+
if payload.startswith(b"\xff\xd8\xff"):
|
|
21
|
+
return "image/jpeg"
|
|
22
|
+
if payload.startswith((b"GIF87a", b"GIF89a")):
|
|
23
|
+
return "image/gif"
|
|
24
|
+
if len(payload) >= 12 and payload[:4] == b"RIFF" and payload[8:12] == b"WEBP":
|
|
25
|
+
return "image/webp"
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"IMAGE_FILENAME_SUFFIXES",
|
|
31
|
+
"SUPPORTED_IMAGE_MEDIA_TYPES",
|
|
32
|
+
"looks_like_image_attachment",
|
|
33
|
+
"sniff_image_media_type",
|
|
34
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Public settings declarations owned by the module API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from pydantic_settings import BaseSettings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ModuleSetting:
|
|
12
|
+
"""Presentation metadata for one explicitly exposed settings field."""
|
|
13
|
+
|
|
14
|
+
field: str
|
|
15
|
+
label: str
|
|
16
|
+
help: str = ""
|
|
17
|
+
choices: tuple[str, ...] = ()
|
|
18
|
+
minimum: int | float | None = None
|
|
19
|
+
multiline: bool = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class ModuleSettingsDefinition:
|
|
24
|
+
"""A module's settings model and its operator-editable subset."""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
label: str
|
|
28
|
+
model: type[BaseSettings]
|
|
29
|
+
exposed: tuple[ModuleSetting, ...]
|
|
30
|
+
environment_only: frozenset[str] = frozenset()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ["ModuleSetting", "ModuleSettingsDefinition"]
|