kimi-agent-module-api 2.2.0__tar.gz → 2.4.0__tar.gz
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-2.2.0/src/kimi_agent_module_api.egg-info → kimi_agent_module_api-2.4.0}/PKG-INFO +24 -7
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/README.md +23 -6
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/pyproject.toml +1 -1
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/__init__.py +21 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/contracts.py +7 -0
- kimi_agent_module_api-2.4.0/src/kimi_agent_module_api/scheduled_results.py +105 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/testing.py +98 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/tools.py +36 -2
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0/src/kimi_agent_module_api.egg-info}/PKG-INFO +24 -7
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api.egg-info/SOURCES.txt +2 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/tests/test_contracts.py +62 -3
- kimi_agent_module_api-2.4.0/tests/test_scheduled_results.py +99 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/LICENSE +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/setup.cfg +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/events.py +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/files.py +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/images.py +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/py.typed +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/settings.py +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/trust.py +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api.egg-info/dependency_links.txt +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api.egg-info/requires.txt +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api.egg-info/top_level.txt +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/tests/test_files.py +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/tests/test_public_api.py +0 -0
- {kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/tests/test_testing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kimi-agent-module-api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Stable contracts for community-built assistant modules
|
|
5
5
|
Author: Webhead
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,6 +30,8 @@ implementation, or module loader. It exports:
|
|
|
30
30
|
(storage, scheduler, events, Discord actions, interactions, HTTP, services,
|
|
31
31
|
trust, proposals, health) plus the validators the host runs at preflight.
|
|
32
32
|
- `kimi_agent_module_api.events`: the normalized `discord.*` event payloads.
|
|
33
|
+
- `kimi_agent_module_api.scheduled_results`: durable subscriptions to published
|
|
34
|
+
task output, typed results, and bounded attachment readers (SDK 2.4).
|
|
33
35
|
- `kimi_agent_module_api.files`: `ToolFiles`, `ToolAttachment`, `ToolFile`, and
|
|
34
36
|
`FileAccessError` for invocation-scoped attachment and workspace reads.
|
|
35
37
|
- `kimi_agent_module_api.testing`: a fake for every port, `load_context()` for
|
|
@@ -84,11 +86,13 @@ Message-deletion events include cached author classification:
|
|
|
84
86
|
`MessageDeleteEvent.author_is_bot` and `MessageBulkDeleteEvent.bot_message_ids`.
|
|
85
87
|
The values remain unknown for messages that were absent from Discord's cache.
|
|
86
88
|
|
|
87
|
-
SDK 2.1 adds `ModuleToolContext.trigger_discord_message_id`.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`
|
|
89
|
+
SDK 2.1 adds `ModuleToolContext.trigger_discord_message_id`. SDK 2.3 adds
|
|
90
|
+
`trigger_discord_message_snapshot`, an immutable host-owned capture of the
|
|
91
|
+
message, guild, channel, author, content, and bot status made at turn entry.
|
|
92
|
+
Mention-path tool calls receive both; personal and non-message surfaces receive
|
|
93
|
+
`None`. Modules that need authoritative evidence from the triggering message
|
|
94
|
+
should require `kimi-agent-module-api>=2.3,<3` and use the snapshot instead of
|
|
95
|
+
re-fetching mutable or deletable Discord state.
|
|
92
96
|
|
|
93
97
|
Modules use namespaced guild documents and the physical table names returned
|
|
94
98
|
by `ctx.storage.table()`.
|
|
@@ -101,7 +105,9 @@ keep `api_version=2` and receive `files=None` without the permission.
|
|
|
101
105
|
`read_attachment(id, max_bytes=...)` and `read_workspace(path, max_bytes=...)`
|
|
102
106
|
return bounded bytes without network downloads. The reader expires when the handler
|
|
103
107
|
returns and is scoped to the actual caller. `testing.FakeToolFiles` supports
|
|
104
|
-
independent tests.
|
|
108
|
+
independent tests. Module `ctx.http` methods apply an 8 MiB host ceiling even when
|
|
109
|
+
callers supply `max_bytes`; `download` buffers and validates the bounded response
|
|
110
|
+
before yielding chunks so connections are released on early consumer exit. See the
|
|
105
111
|
[file access guide](https://github.com/webhead2oo9/kimi-agent/blob/main/docs/module-files.md)
|
|
106
112
|
for moderation, reply-image availability, privacy, and limits.
|
|
107
113
|
|
|
@@ -112,3 +118,14 @@ From this package directory, run its tests without installing the Kimi applicati
|
|
|
112
118
|
```console
|
|
113
119
|
uv run --isolated --group test python -m pytest -q
|
|
114
120
|
```
|
|
121
|
+
|
|
122
|
+
SDK 2.4 adds `ScheduledResults`: named, guild-scoped subscriptions to task output
|
|
123
|
+
following confirmed Discord publication. Declare
|
|
124
|
+
`ModulePermissions(scheduled_results=("my_subscription",))`, require the host
|
|
125
|
+
capability `scheduled_results.v1`, and register through `ctx.scheduled_results`
|
|
126
|
+
during startup. Handlers receive a typed `ScheduledResult` and an invocation-scoped
|
|
127
|
+
`ScheduledResultFiles` reader. Returning acknowledges the result; exceptions retry
|
|
128
|
+
with the same notification ID. Use that ID for idempotent processing.
|
|
129
|
+
`FakeScheduledResults` supports standalone tests. See the
|
|
130
|
+
[published-result guide](https://github.com/Kimi-Discord-Agent/kimi-agent/blob/main/docs/module-scheduled-results.md)
|
|
131
|
+
for access checks, limits, retention, and deletion responsibilities.
|
|
@@ -13,6 +13,8 @@ implementation, or module loader. It exports:
|
|
|
13
13
|
(storage, scheduler, events, Discord actions, interactions, HTTP, services,
|
|
14
14
|
trust, proposals, health) plus the validators the host runs at preflight.
|
|
15
15
|
- `kimi_agent_module_api.events`: the normalized `discord.*` event payloads.
|
|
16
|
+
- `kimi_agent_module_api.scheduled_results`: durable subscriptions to published
|
|
17
|
+
task output, typed results, and bounded attachment readers (SDK 2.4).
|
|
16
18
|
- `kimi_agent_module_api.files`: `ToolFiles`, `ToolAttachment`, `ToolFile`, and
|
|
17
19
|
`FileAccessError` for invocation-scoped attachment and workspace reads.
|
|
18
20
|
- `kimi_agent_module_api.testing`: a fake for every port, `load_context()` for
|
|
@@ -67,11 +69,13 @@ Message-deletion events include cached author classification:
|
|
|
67
69
|
`MessageDeleteEvent.author_is_bot` and `MessageBulkDeleteEvent.bot_message_ids`.
|
|
68
70
|
The values remain unknown for messages that were absent from Discord's cache.
|
|
69
71
|
|
|
70
|
-
SDK 2.1 adds `ModuleToolContext.trigger_discord_message_id`.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`
|
|
72
|
+
SDK 2.1 adds `ModuleToolContext.trigger_discord_message_id`. SDK 2.3 adds
|
|
73
|
+
`trigger_discord_message_snapshot`, an immutable host-owned capture of the
|
|
74
|
+
message, guild, channel, author, content, and bot status made at turn entry.
|
|
75
|
+
Mention-path tool calls receive both; personal and non-message surfaces receive
|
|
76
|
+
`None`. Modules that need authoritative evidence from the triggering message
|
|
77
|
+
should require `kimi-agent-module-api>=2.3,<3` and use the snapshot instead of
|
|
78
|
+
re-fetching mutable or deletable Discord state.
|
|
75
79
|
|
|
76
80
|
Modules use namespaced guild documents and the physical table names returned
|
|
77
81
|
by `ctx.storage.table()`.
|
|
@@ -84,7 +88,9 @@ keep `api_version=2` and receive `files=None` without the permission.
|
|
|
84
88
|
`read_attachment(id, max_bytes=...)` and `read_workspace(path, max_bytes=...)`
|
|
85
89
|
return bounded bytes without network downloads. The reader expires when the handler
|
|
86
90
|
returns and is scoped to the actual caller. `testing.FakeToolFiles` supports
|
|
87
|
-
independent tests.
|
|
91
|
+
independent tests. Module `ctx.http` methods apply an 8 MiB host ceiling even when
|
|
92
|
+
callers supply `max_bytes`; `download` buffers and validates the bounded response
|
|
93
|
+
before yielding chunks so connections are released on early consumer exit. See the
|
|
88
94
|
[file access guide](https://github.com/webhead2oo9/kimi-agent/blob/main/docs/module-files.md)
|
|
89
95
|
for moderation, reply-image availability, privacy, and limits.
|
|
90
96
|
|
|
@@ -95,3 +101,14 @@ From this package directory, run its tests without installing the Kimi applicati
|
|
|
95
101
|
```console
|
|
96
102
|
uv run --isolated --group test python -m pytest -q
|
|
97
103
|
```
|
|
104
|
+
|
|
105
|
+
SDK 2.4 adds `ScheduledResults`: named, guild-scoped subscriptions to task output
|
|
106
|
+
following confirmed Discord publication. Declare
|
|
107
|
+
`ModulePermissions(scheduled_results=("my_subscription",))`, require the host
|
|
108
|
+
capability `scheduled_results.v1`, and register through `ctx.scheduled_results`
|
|
109
|
+
during startup. Handlers receive a typed `ScheduledResult` and an invocation-scoped
|
|
110
|
+
`ScheduledResultFiles` reader. Returning acknowledges the result; exceptions retry
|
|
111
|
+
with the same notification ID. Use that ID for idempotent processing.
|
|
112
|
+
`FakeScheduledResults` supports standalone tests. See the
|
|
113
|
+
[published-result guide](https://github.com/Kimi-Discord-Agent/kimi-agent/blob/main/docs/module-scheduled-results.md)
|
|
114
|
+
for access checks, limits, retention, and deletion responsibilities.
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/__init__.py
RENAMED
|
@@ -42,8 +42,19 @@ from kimi_agent_module_api.tools import (
|
|
|
42
42
|
ModuleToolContext,
|
|
43
43
|
ModuleToolHandler,
|
|
44
44
|
ModuleToolRegistry,
|
|
45
|
+
ModuleTurnBudget,
|
|
46
|
+
TriggeringDiscordMessageSnapshot,
|
|
45
47
|
)
|
|
46
48
|
from kimi_agent_module_api.trust import TrustTier
|
|
49
|
+
from kimi_agent_module_api.scheduled_results import (
|
|
50
|
+
ScheduledResult,
|
|
51
|
+
ScheduledResultAccessError,
|
|
52
|
+
ScheduledResultAttachment,
|
|
53
|
+
ScheduledResultFiles,
|
|
54
|
+
ScheduledResultHandler,
|
|
55
|
+
ScheduledResultMessage,
|
|
56
|
+
ScheduledResults,
|
|
57
|
+
)
|
|
47
58
|
|
|
48
59
|
MODULE_API_VERSION = 2
|
|
49
60
|
MODULE_ENTRYPOINT_GROUP = "kimi_agent.modules"
|
|
@@ -145,6 +156,7 @@ class ModuleRuntimeContext:
|
|
|
145
156
|
proposals: ProposalService | None = None
|
|
146
157
|
raw_bot: Any = None
|
|
147
158
|
raw_storage: Any = None
|
|
159
|
+
scheduled_results: ScheduledResults | None = None
|
|
148
160
|
|
|
149
161
|
|
|
150
162
|
__all__ = [
|
|
@@ -173,6 +185,7 @@ __all__ = [
|
|
|
173
185
|
"ModuleToolContext",
|
|
174
186
|
"ModuleToolHandler",
|
|
175
187
|
"ModuleToolRegistry",
|
|
188
|
+
"ModuleTurnBudget",
|
|
176
189
|
"OutgoingLayout",
|
|
177
190
|
"ProposalActor",
|
|
178
191
|
"ProposalError",
|
|
@@ -180,6 +193,13 @@ __all__ = [
|
|
|
180
193
|
"ProposalService",
|
|
181
194
|
"ProposalState",
|
|
182
195
|
"RoleSnapshot",
|
|
196
|
+
"ScheduledResult",
|
|
197
|
+
"ScheduledResultAccessError",
|
|
198
|
+
"ScheduledResultAttachment",
|
|
199
|
+
"ScheduledResultFiles",
|
|
200
|
+
"ScheduledResultHandler",
|
|
201
|
+
"ScheduledResultMessage",
|
|
202
|
+
"ScheduledResults",
|
|
183
203
|
"ScopedModuleMigration",
|
|
184
204
|
"ServiceDeclaration",
|
|
185
205
|
"ServiceRequirement",
|
|
@@ -188,6 +208,7 @@ __all__ = [
|
|
|
188
208
|
"ToolAttachment",
|
|
189
209
|
"ToolFile",
|
|
190
210
|
"ToolFiles",
|
|
211
|
+
"TriggeringDiscordMessageSnapshot",
|
|
191
212
|
"TrustTier",
|
|
192
213
|
"render_guild_settings",
|
|
193
214
|
]
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/contracts.py
RENAMED
|
@@ -145,6 +145,8 @@ class ModulePermissions:
|
|
|
145
145
|
raw_storage: bool = False
|
|
146
146
|
# Read admitted attachments and caller-owned workspace files in tool handlers.
|
|
147
147
|
tool_files: bool = False
|
|
148
|
+
# Names of durable, explicitly guild-scoped published-task subscriptions.
|
|
149
|
+
scheduled_results: tuple[str, ...] = ()
|
|
148
150
|
|
|
149
151
|
|
|
150
152
|
@dataclass(frozen=True, slots=True)
|
|
@@ -439,6 +441,11 @@ def validate_permissions(module_name: str, permissions: ModulePermissions) -> No
|
|
|
439
441
|
)
|
|
440
442
|
for rule in permissions.http_hosts:
|
|
441
443
|
validate_host_rule(rule)
|
|
444
|
+
names = permissions.scheduled_results
|
|
445
|
+
if len(set(names)) != len(names) or any(
|
|
446
|
+
not re.fullmatch(r"[a-z0-9_]{1,64}", name) for name in names
|
|
447
|
+
):
|
|
448
|
+
raise ModuleContractError("scheduled-result subscription names must be unique and valid")
|
|
442
449
|
|
|
443
450
|
|
|
444
451
|
def validate_services(
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Durable subscriptions to confirmed Discord publications, separate from module jobs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Literal, Protocol
|
|
8
|
+
|
|
9
|
+
from kimi_agent_module_api.contracts import MessageRef, ModuleContractError
|
|
10
|
+
|
|
11
|
+
MAX_RESULT_FILE_BYTES = 8 * 1024 * 1024
|
|
12
|
+
MAX_RESULT_TOTAL_FILE_BYTES = 16 * 1024 * 1024
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ScheduledResultAccessError(ModuleContractError):
|
|
16
|
+
"""The subscription or its retained output is unavailable to this caller."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class ScheduledResultAttachment:
|
|
21
|
+
id: str
|
|
22
|
+
filename: str
|
|
23
|
+
size_bytes: int
|
|
24
|
+
description: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class ScheduledResultMessage:
|
|
29
|
+
message: MessageRef
|
|
30
|
+
content: str
|
|
31
|
+
attachments: tuple[ScheduledResultAttachment, ...] = ()
|
|
32
|
+
# The published Discord embed as JSON, capped at 32 KiB. No network fetches.
|
|
33
|
+
embed_json: str | None = None
|
|
34
|
+
embed_unavailable_reason: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class ScheduledResult:
|
|
39
|
+
notification_id: str
|
|
40
|
+
subscription: str
|
|
41
|
+
task_id: str
|
|
42
|
+
run_id: str
|
|
43
|
+
revision: int
|
|
44
|
+
guild_id: int
|
|
45
|
+
owner_id: int
|
|
46
|
+
published_at: float
|
|
47
|
+
expires_at: float
|
|
48
|
+
messages: tuple[ScheduledResultMessage, ...]
|
|
49
|
+
outcome: Literal["completed"] = "completed"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ScheduledResultFiles(Protocol):
|
|
53
|
+
"""Read only this notification's attachments during its handler invocation.
|
|
54
|
+
|
|
55
|
+
The host rechecks access on every read. Reads are capped at 8 MiB each and
|
|
56
|
+
16 MiB total per invocation, including repeated reads. Handles expire on
|
|
57
|
+
return, cancellation, deletion, or retention expiry; copy bytes deliberately.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
async def read(
|
|
61
|
+
self, attachment_id: str, *, max_bytes: int = MAX_RESULT_FILE_BYTES
|
|
62
|
+
) -> bytes: ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
type ScheduledResultHandler = Callable[[ScheduledResult, ScheduledResultFiles], Awaitable[None]]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ScheduledResults(Protocol):
|
|
69
|
+
"""A module-bound port. Declare each name in ``permissions.scheduled_results``.
|
|
70
|
+
|
|
71
|
+
Register on every start. A name is scoped to one guild per registration;
|
|
72
|
+
the same name may be registered in several guilds. Existing subscriptions
|
|
73
|
+
survive downtime; new registrations never replay historical publications.
|
|
74
|
+
Returning acknowledges the notification. Raising retries with the same ID.
|
|
75
|
+
Use that ID to make your own writes/remote requests idempotent.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
async def subscribe(
|
|
79
|
+
self, name: str, *, guild_id: int, handler: ScheduledResultHandler
|
|
80
|
+
) -> None: ...
|
|
81
|
+
|
|
82
|
+
async def unsubscribe(self, name: str, *, guild_id: int) -> None:
|
|
83
|
+
"""Forget this subscription and its pending/acknowledged notifications."""
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def validate_result_subscription(name: str, guild_id: int) -> None:
|
|
88
|
+
# Keep validation here usable by the host and standalone test fakes.
|
|
89
|
+
if (
|
|
90
|
+
not isinstance(name, str)
|
|
91
|
+
or not 1 <= len(name) <= 64
|
|
92
|
+
or any(char not in "abcdefghijklmnopqrstuvwxyz0123456789_" for char in name)
|
|
93
|
+
):
|
|
94
|
+
raise ModuleContractError("scheduled-result names use 1-64 lowercase letters, digits or _")
|
|
95
|
+
if isinstance(guild_id, bool) or not isinstance(guild_id, int) or not 0 < guild_id < 2**64:
|
|
96
|
+
raise ModuleContractError("scheduled-result subscriptions require a positive guild ID")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def validate_result_read_limit(max_bytes: int) -> None:
|
|
100
|
+
if (
|
|
101
|
+
isinstance(max_bytes, bool)
|
|
102
|
+
or not isinstance(max_bytes, int)
|
|
103
|
+
or not (0 < max_bytes <= MAX_RESULT_FILE_BYTES)
|
|
104
|
+
):
|
|
105
|
+
raise ScheduledResultAccessError("attachment read limit must be between 1 byte and 8 MiB")
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/testing.py
RENAMED
|
@@ -24,6 +24,15 @@ from pydantic_settings import BaseSettings
|
|
|
24
24
|
|
|
25
25
|
from kimi_agent_module_api.trust import TrustTier
|
|
26
26
|
from kimi_agent_module_api.files import FileAccessError, ToolAttachment, ToolFile
|
|
27
|
+
from kimi_agent_module_api.scheduled_results import (
|
|
28
|
+
MAX_RESULT_FILE_BYTES,
|
|
29
|
+
MAX_RESULT_TOTAL_FILE_BYTES,
|
|
30
|
+
ScheduledResult,
|
|
31
|
+
ScheduledResultAccessError,
|
|
32
|
+
ScheduledResultHandler,
|
|
33
|
+
validate_result_read_limit,
|
|
34
|
+
validate_result_subscription,
|
|
35
|
+
)
|
|
27
36
|
|
|
28
37
|
from kimi_agent_module_api.contracts import (
|
|
29
38
|
ALL_DISCORD_ACTIONS,
|
|
@@ -85,6 +94,93 @@ _MAX_FLOAT_LOG = math.log(sys.float_info.max)
|
|
|
85
94
|
_T = TypeVar("_T")
|
|
86
95
|
|
|
87
96
|
|
|
97
|
+
class FakeScheduledResultFiles:
|
|
98
|
+
"""An invocation-scoped reader; the same size/lifetime limits as the host."""
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self, files: Mapping[str, bytes], *, available: Callable[[], bool] = lambda: True
|
|
102
|
+
) -> None:
|
|
103
|
+
self.files = files
|
|
104
|
+
self.available = available
|
|
105
|
+
self.open = True
|
|
106
|
+
self.remaining = MAX_RESULT_TOTAL_FILE_BYTES
|
|
107
|
+
|
|
108
|
+
async def read(self, attachment_id: str, *, max_bytes: int = MAX_RESULT_FILE_BYTES) -> bytes:
|
|
109
|
+
validate_result_read_limit(max_bytes)
|
|
110
|
+
data = self.files.get(attachment_id)
|
|
111
|
+
if (
|
|
112
|
+
not self.open
|
|
113
|
+
or not self.available()
|
|
114
|
+
or self.remaining <= 0
|
|
115
|
+
or data is None
|
|
116
|
+
or len(data) > min(max_bytes, self.remaining)
|
|
117
|
+
):
|
|
118
|
+
raise ScheduledResultAccessError("Result file unavailable or read limit exceeded")
|
|
119
|
+
self.remaining -= len(data)
|
|
120
|
+
return data
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class FakeScheduledResults:
|
|
124
|
+
"""Drive retries explicitly with ``deliver``; successful IDs are acknowledged.
|
|
125
|
+
|
|
126
|
+
A failed invocation propagates its exception and can be delivered again.
|
|
127
|
+
Supply the original result object to model a restart or duplicate attempt.
|
|
128
|
+
This fake has no background runner, Discord access, or persistence.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
declared: tuple[str, ...] = (),
|
|
134
|
+
*,
|
|
135
|
+
is_guild_active: Callable[[int], bool] = lambda _guild_id: True,
|
|
136
|
+
) -> None:
|
|
137
|
+
self.declared, self.is_guild_active = declared, is_guild_active
|
|
138
|
+
self.handlers: dict[tuple[str, int], ScheduledResultHandler] = {}
|
|
139
|
+
self.acknowledged: set[str] = set()
|
|
140
|
+
self.attempts: list[str] = []
|
|
141
|
+
|
|
142
|
+
def _validate(self, name: str, guild_id: int) -> None:
|
|
143
|
+
validate_result_subscription(name, guild_id)
|
|
144
|
+
if name not in self.declared:
|
|
145
|
+
raise ScheduledResultAccessError("Undeclared scheduled-result subscription")
|
|
146
|
+
|
|
147
|
+
async def subscribe(self, name: str, *, guild_id: int, handler: ScheduledResultHandler) -> None:
|
|
148
|
+
self._validate(name, guild_id)
|
|
149
|
+
self.handlers[name, guild_id] = handler
|
|
150
|
+
|
|
151
|
+
async def unsubscribe(self, name: str, *, guild_id: int) -> None:
|
|
152
|
+
self._validate(name, guild_id)
|
|
153
|
+
self.handlers.pop((name, guild_id), None)
|
|
154
|
+
|
|
155
|
+
async def deliver(
|
|
156
|
+
self,
|
|
157
|
+
result: ScheduledResult,
|
|
158
|
+
*,
|
|
159
|
+
files: Mapping[str, bytes] | None = None,
|
|
160
|
+
preview: bool = False,
|
|
161
|
+
) -> None:
|
|
162
|
+
if preview or result.notification_id in self.acknowledged:
|
|
163
|
+
return
|
|
164
|
+
handler = self.handlers.get((result.subscription, result.guild_id))
|
|
165
|
+
if handler is None or not self.is_guild_active(result.guild_id):
|
|
166
|
+
raise ScheduledResultAccessError("Subscriber unavailable in this guild")
|
|
167
|
+
visible = {a.id for m in result.messages for a in m.attachments}
|
|
168
|
+
reader = FakeScheduledResultFiles(
|
|
169
|
+
{k: v for k, v in (files or {}).items() if k in visible},
|
|
170
|
+
available=lambda: (
|
|
171
|
+
self.is_guild_active(result.guild_id)
|
|
172
|
+
and result.subscription in self.declared
|
|
173
|
+
and self.handlers.get((result.subscription, result.guild_id)) is handler
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
self.attempts.append(result.notification_id)
|
|
177
|
+
try:
|
|
178
|
+
await handler(result, reader)
|
|
179
|
+
self.acknowledged.add(result.notification_id)
|
|
180
|
+
finally:
|
|
181
|
+
reader.open = False
|
|
182
|
+
|
|
183
|
+
|
|
88
184
|
@dataclass(slots=True)
|
|
89
185
|
class _Closable:
|
|
90
186
|
_on_close: Callable[[], object]
|
|
@@ -1390,6 +1486,8 @@ __all__ = [
|
|
|
1390
1486
|
"FakeInteractions",
|
|
1391
1487
|
"FakeProposals",
|
|
1392
1488
|
"FakeResponse",
|
|
1489
|
+
"FakeScheduledResultFiles",
|
|
1490
|
+
"FakeScheduledResults",
|
|
1393
1491
|
"FakeScheduler",
|
|
1394
1492
|
"FakeServiceRegistry",
|
|
1395
1493
|
"FakeToolFiles",
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/tools.py
RENAMED
|
@@ -6,8 +6,29 @@ from collections.abc import Callable, Coroutine, Mapping
|
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from typing import Any, Protocol
|
|
8
8
|
|
|
9
|
-
from kimi_agent_module_api.trust import TrustTier
|
|
10
9
|
from kimi_agent_module_api.files import ToolFiles
|
|
10
|
+
from kimi_agent_module_api.trust import TrustTier
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ModuleTurnBudget(Protocol):
|
|
14
|
+
"""Host-owned counters shared by every module call in one outer model turn."""
|
|
15
|
+
|
|
16
|
+
def consume(self, name: str, limit: int) -> bool:
|
|
17
|
+
"""Consume one named allowance, or return ``False`` without changing it."""
|
|
18
|
+
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class TriggeringDiscordMessageSnapshot:
|
|
24
|
+
"""Immutable evidence captured from the Discord message at turn entry."""
|
|
25
|
+
|
|
26
|
+
message_id: int
|
|
27
|
+
guild_id: int
|
|
28
|
+
channel_id: int
|
|
29
|
+
author_id: int
|
|
30
|
+
content: str
|
|
31
|
+
author_is_bot: bool
|
|
11
32
|
|
|
12
33
|
|
|
13
34
|
@dataclass(frozen=True, slots=True)
|
|
@@ -36,6 +57,13 @@ class ModuleToolContext:
|
|
|
36
57
|
# Invocation-scoped read-only port. Declare permissions.tool_files and
|
|
37
58
|
# require tools.files.v1; never retain this port beyond the handler.
|
|
38
59
|
files: ToolFiles | None = None
|
|
60
|
+
# Host-owned values captured before turn preparation can await. Unlike a
|
|
61
|
+
# later Discord fetch, this evidence cannot change or disappear mid-turn.
|
|
62
|
+
# Added after all API 2.2 fields to preserve positional construction.
|
|
63
|
+
trigger_discord_message_snapshot: TriggeringDiscordMessageSnapshot | None = None
|
|
64
|
+
# Mutable host-owned port whose lifetime is the complete outer model turn.
|
|
65
|
+
# Counters are automatically namespaced to the installed module.
|
|
66
|
+
turn_budget: ModuleTurnBudget | None = None
|
|
39
67
|
|
|
40
68
|
|
|
41
69
|
type ModuleToolHandler = Callable[[dict[str, Any], ModuleToolContext], Coroutine[Any, Any, str]]
|
|
@@ -71,4 +99,10 @@ class ModuleToolRegistry(Protocol):
|
|
|
71
99
|
) -> None: ...
|
|
72
100
|
|
|
73
101
|
|
|
74
|
-
__all__ = [
|
|
102
|
+
__all__ = [
|
|
103
|
+
"ModuleToolContext",
|
|
104
|
+
"ModuleToolHandler",
|
|
105
|
+
"ModuleToolRegistry",
|
|
106
|
+
"ModuleTurnBudget",
|
|
107
|
+
"TriggeringDiscordMessageSnapshot",
|
|
108
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kimi-agent-module-api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Stable contracts for community-built assistant modules
|
|
5
5
|
Author: Webhead
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,6 +30,8 @@ implementation, or module loader. It exports:
|
|
|
30
30
|
(storage, scheduler, events, Discord actions, interactions, HTTP, services,
|
|
31
31
|
trust, proposals, health) plus the validators the host runs at preflight.
|
|
32
32
|
- `kimi_agent_module_api.events`: the normalized `discord.*` event payloads.
|
|
33
|
+
- `kimi_agent_module_api.scheduled_results`: durable subscriptions to published
|
|
34
|
+
task output, typed results, and bounded attachment readers (SDK 2.4).
|
|
33
35
|
- `kimi_agent_module_api.files`: `ToolFiles`, `ToolAttachment`, `ToolFile`, and
|
|
34
36
|
`FileAccessError` for invocation-scoped attachment and workspace reads.
|
|
35
37
|
- `kimi_agent_module_api.testing`: a fake for every port, `load_context()` for
|
|
@@ -84,11 +86,13 @@ Message-deletion events include cached author classification:
|
|
|
84
86
|
`MessageDeleteEvent.author_is_bot` and `MessageBulkDeleteEvent.bot_message_ids`.
|
|
85
87
|
The values remain unknown for messages that were absent from Discord's cache.
|
|
86
88
|
|
|
87
|
-
SDK 2.1 adds `ModuleToolContext.trigger_discord_message_id`.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`
|
|
89
|
+
SDK 2.1 adds `ModuleToolContext.trigger_discord_message_id`. SDK 2.3 adds
|
|
90
|
+
`trigger_discord_message_snapshot`, an immutable host-owned capture of the
|
|
91
|
+
message, guild, channel, author, content, and bot status made at turn entry.
|
|
92
|
+
Mention-path tool calls receive both; personal and non-message surfaces receive
|
|
93
|
+
`None`. Modules that need authoritative evidence from the triggering message
|
|
94
|
+
should require `kimi-agent-module-api>=2.3,<3` and use the snapshot instead of
|
|
95
|
+
re-fetching mutable or deletable Discord state.
|
|
92
96
|
|
|
93
97
|
Modules use namespaced guild documents and the physical table names returned
|
|
94
98
|
by `ctx.storage.table()`.
|
|
@@ -101,7 +105,9 @@ keep `api_version=2` and receive `files=None` without the permission.
|
|
|
101
105
|
`read_attachment(id, max_bytes=...)` and `read_workspace(path, max_bytes=...)`
|
|
102
106
|
return bounded bytes without network downloads. The reader expires when the handler
|
|
103
107
|
returns and is scoped to the actual caller. `testing.FakeToolFiles` supports
|
|
104
|
-
independent tests.
|
|
108
|
+
independent tests. Module `ctx.http` methods apply an 8 MiB host ceiling even when
|
|
109
|
+
callers supply `max_bytes`; `download` buffers and validates the bounded response
|
|
110
|
+
before yielding chunks so connections are released on early consumer exit. See the
|
|
105
111
|
[file access guide](https://github.com/webhead2oo9/kimi-agent/blob/main/docs/module-files.md)
|
|
106
112
|
for moderation, reply-image availability, privacy, and limits.
|
|
107
113
|
|
|
@@ -112,3 +118,14 @@ From this package directory, run its tests without installing the Kimi applicati
|
|
|
112
118
|
```console
|
|
113
119
|
uv run --isolated --group test python -m pytest -q
|
|
114
120
|
```
|
|
121
|
+
|
|
122
|
+
SDK 2.4 adds `ScheduledResults`: named, guild-scoped subscriptions to task output
|
|
123
|
+
following confirmed Discord publication. Declare
|
|
124
|
+
`ModulePermissions(scheduled_results=("my_subscription",))`, require the host
|
|
125
|
+
capability `scheduled_results.v1`, and register through `ctx.scheduled_results`
|
|
126
|
+
during startup. Handlers receive a typed `ScheduledResult` and an invocation-scoped
|
|
127
|
+
`ScheduledResultFiles` reader. Returning acknowledges the result; exceptions retry
|
|
128
|
+
with the same notification ID. Use that ID for idempotent processing.
|
|
129
|
+
`FakeScheduledResults` supports standalone tests. See the
|
|
130
|
+
[published-result guide](https://github.com/Kimi-Discord-Agent/kimi-agent/blob/main/docs/module-scheduled-results.md)
|
|
131
|
+
for access checks, limits, retention, and deletion responsibilities.
|
|
@@ -7,6 +7,7 @@ src/kimi_agent_module_api/events.py
|
|
|
7
7
|
src/kimi_agent_module_api/files.py
|
|
8
8
|
src/kimi_agent_module_api/images.py
|
|
9
9
|
src/kimi_agent_module_api/py.typed
|
|
10
|
+
src/kimi_agent_module_api/scheduled_results.py
|
|
10
11
|
src/kimi_agent_module_api/settings.py
|
|
11
12
|
src/kimi_agent_module_api/testing.py
|
|
12
13
|
src/kimi_agent_module_api/tools.py
|
|
@@ -19,4 +20,5 @@ src/kimi_agent_module_api.egg-info/top_level.txt
|
|
|
19
20
|
tests/test_contracts.py
|
|
20
21
|
tests/test_files.py
|
|
21
22
|
tests/test_public_api.py
|
|
23
|
+
tests/test_scheduled_results.py
|
|
22
24
|
tests/test_testing.py
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from importlib.metadata import version
|
|
6
5
|
import dataclasses
|
|
6
|
+
from importlib.metadata import version
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
@@ -19,6 +19,7 @@ from kimi_agent_module_api import (
|
|
|
19
19
|
ModuleRuntimeContext,
|
|
20
20
|
ModuleSpec,
|
|
21
21
|
ModuleToolContext,
|
|
22
|
+
TriggeringDiscordMessageSnapshot,
|
|
22
23
|
TrustTier,
|
|
23
24
|
render_guild_settings,
|
|
24
25
|
)
|
|
@@ -83,8 +84,66 @@ def test_spec_and_runtime_context_keep_stable_defaults() -> None:
|
|
|
83
84
|
assert {"events", "scheduler", "storage", "discord", "interactions", "services"} <= required
|
|
84
85
|
|
|
85
86
|
|
|
86
|
-
def
|
|
87
|
-
assert version("kimi-agent-module-api") == "2.
|
|
87
|
+
def test_distribution_version_includes_scheduled_result_contract() -> None:
|
|
88
|
+
assert version("kimi-agent-module-api") == "2.4.0"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_triggering_discord_message_snapshot_is_immutable() -> None:
|
|
92
|
+
snapshot = TriggeringDiscordMessageSnapshot(
|
|
93
|
+
message_id=11,
|
|
94
|
+
guild_id=22,
|
|
95
|
+
channel_id=33,
|
|
96
|
+
author_id=44,
|
|
97
|
+
content="original",
|
|
98
|
+
author_is_bot=False,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
102
|
+
snapshot.content = "edited" # type: ignore[misc]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_module_tool_context_preserves_api_2_2_nine_positional_arguments() -> None:
|
|
106
|
+
files = object()
|
|
107
|
+
|
|
108
|
+
context = ModuleToolContext(
|
|
109
|
+
11,
|
|
110
|
+
"Alice",
|
|
111
|
+
22,
|
|
112
|
+
33,
|
|
113
|
+
44,
|
|
114
|
+
TrustTier.REGULAR,
|
|
115
|
+
{"demo": {"enabled": True}},
|
|
116
|
+
55,
|
|
117
|
+
files, # type: ignore[arg-type]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
assert context.files is files
|
|
121
|
+
assert context.trigger_discord_message_snapshot is None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_module_tool_context_adds_turn_budget_after_prior_positional_fields() -> None:
|
|
125
|
+
class Budget:
|
|
126
|
+
def consume(self, name: str, limit: int) -> bool:
|
|
127
|
+
return bool(name) and limit > 0
|
|
128
|
+
|
|
129
|
+
snapshot = TriggeringDiscordMessageSnapshot(55, 22, 33, 11, "content", False)
|
|
130
|
+
budget = Budget()
|
|
131
|
+
context = ModuleToolContext(
|
|
132
|
+
11,
|
|
133
|
+
"Alice",
|
|
134
|
+
22,
|
|
135
|
+
33,
|
|
136
|
+
44,
|
|
137
|
+
TrustTier.REGULAR,
|
|
138
|
+
{},
|
|
139
|
+
55,
|
|
140
|
+
None,
|
|
141
|
+
snapshot,
|
|
142
|
+
budget,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
assert context.trigger_discord_message_snapshot is snapshot
|
|
146
|
+
assert context.turn_budget is budget
|
|
88
147
|
|
|
89
148
|
|
|
90
149
|
def test_spec_requires_an_explicit_keyword_api_version() -> None:
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Exercise the public subscriber contract without importing a host."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import FrozenInstanceError, replace
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from kimi_agent_module_api import (
|
|
10
|
+
ModulePermissions,
|
|
11
|
+
ScheduledResult,
|
|
12
|
+
ScheduledResultAccessError,
|
|
13
|
+
ScheduledResultAttachment,
|
|
14
|
+
ScheduledResultMessage,
|
|
15
|
+
)
|
|
16
|
+
from kimi_agent_module_api.contracts import MessageRef, ModuleContractError, validate_permissions
|
|
17
|
+
from kimi_agent_module_api.testing import FakeScheduledResults
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def result():
|
|
21
|
+
return ScheduledResult(
|
|
22
|
+
notification_id="notification",
|
|
23
|
+
subscription="reports",
|
|
24
|
+
task_id="task",
|
|
25
|
+
run_id="run",
|
|
26
|
+
revision=3,
|
|
27
|
+
guild_id=100,
|
|
28
|
+
owner_id=10,
|
|
29
|
+
published_at=1000,
|
|
30
|
+
expires_at=2000,
|
|
31
|
+
messages=(
|
|
32
|
+
ScheduledResultMessage(
|
|
33
|
+
MessageRef(100, 300, 900),
|
|
34
|
+
"Published",
|
|
35
|
+
(ScheduledResultAttachment("file", "result.txt", 3),),
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_result_is_immutable_and_permissions_must_be_named():
|
|
42
|
+
with pytest.raises(FrozenInstanceError):
|
|
43
|
+
result().owner_id = 99
|
|
44
|
+
validate_permissions("reports", ModulePermissions(scheduled_results=("reports",)))
|
|
45
|
+
for names in (("Upper Case",), ("reports", "reports"), ("",)):
|
|
46
|
+
with pytest.raises(ModuleContractError):
|
|
47
|
+
validate_permissions("reports", ModulePermissions(scheduled_results=names))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_fake_retry_acknowledgement_preview_and_file_lifetime():
|
|
52
|
+
fake = FakeScheduledResults(("reports",))
|
|
53
|
+
attempts, readers = [], []
|
|
54
|
+
|
|
55
|
+
async def handler(notification, files):
|
|
56
|
+
attempts.append(notification.notification_id)
|
|
57
|
+
readers.append(files)
|
|
58
|
+
assert await files.read("file") == b"abc"
|
|
59
|
+
with pytest.raises(ScheduledResultAccessError):
|
|
60
|
+
await files.read("not published")
|
|
61
|
+
with pytest.raises(ScheduledResultAccessError):
|
|
62
|
+
await files.read("file", max_bytes=2)
|
|
63
|
+
if len(attempts) == 1:
|
|
64
|
+
raise RuntimeError("retry me")
|
|
65
|
+
|
|
66
|
+
await fake.subscribe("reports", guild_id=100, handler=handler)
|
|
67
|
+
await fake.deliver(result(), preview=True)
|
|
68
|
+
assert attempts == []
|
|
69
|
+
files = {"file": b"abc", "not published": b"private"}
|
|
70
|
+
with pytest.raises(RuntimeError, match="retry me"):
|
|
71
|
+
await fake.deliver(result(), files=files)
|
|
72
|
+
assert not fake.acknowledged
|
|
73
|
+
await fake.deliver(result(), files=files)
|
|
74
|
+
await fake.deliver(result(), files=files)
|
|
75
|
+
assert attempts == ["notification", "notification"]
|
|
76
|
+
assert fake.acknowledged == {"notification"}
|
|
77
|
+
for reader in readers:
|
|
78
|
+
with pytest.raises(ScheduledResultAccessError):
|
|
79
|
+
await reader.read("file")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.asyncio
|
|
83
|
+
async def test_fake_permission_and_guild_scope():
|
|
84
|
+
fake = FakeScheduledResults(("reports",), is_guild_active=lambda guild: guild == 100)
|
|
85
|
+
|
|
86
|
+
async def handler(_result, _files):
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
with pytest.raises(ScheduledResultAccessError):
|
|
90
|
+
await fake.subscribe("other", guild_id=100, handler=handler)
|
|
91
|
+
with pytest.raises(ModuleContractError):
|
|
92
|
+
await fake.subscribe("reports", guild_id=0, handler=handler)
|
|
93
|
+
await fake.subscribe("reports", guild_id=200, handler=handler)
|
|
94
|
+
with pytest.raises(ScheduledResultAccessError):
|
|
95
|
+
await fake.deliver(replace(result(), guild_id=200))
|
|
96
|
+
await fake.subscribe("reports", guild_id=100, handler=handler)
|
|
97
|
+
await fake.unsubscribe("reports", guild_id=100)
|
|
98
|
+
with pytest.raises(ScheduledResultAccessError):
|
|
99
|
+
await fake.deliver(result())
|
|
File without changes
|
|
File without changes
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/events.py
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/files.py
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/images.py
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/py.typed
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/settings.py
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.2.0 → kimi_agent_module_api-2.4.0}/src/kimi_agent_module_api/trust.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|