kimi-agent-module-api 2.1.0__tar.gz → 2.2.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.1.0/src/kimi_agent_module_api.egg-info → kimi_agent_module_api-2.2.0}/PKG-INFO +15 -1
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/README.md +14 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/pyproject.toml +1 -1
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/__init__.py +5 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/contracts.py +2 -0
- kimi_agent_module_api-2.2.0/src/kimi_agent_module_api/files.py +61 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/testing.py +66 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/tools.py +4 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0/src/kimi_agent_module_api.egg-info}/PKG-INFO +15 -1
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api.egg-info/SOURCES.txt +2 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/tests/test_contracts.py +2 -2
- kimi_agent_module_api-2.2.0/tests/test_files.py +41 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/LICENSE +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/setup.cfg +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/events.py +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/images.py +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/py.typed +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/settings.py +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/trust.py +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api.egg-info/dependency_links.txt +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api.egg-info/requires.txt +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api.egg-info/top_level.txt +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/tests/test_public_api.py +0 -0
- {kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.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.2.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.files`: `ToolFiles`, `ToolAttachment`, `ToolFile`, and
|
|
34
|
+
`FileAccessError` for invocation-scoped attachment and workspace reads.
|
|
33
35
|
- `kimi_agent_module_api.testing`: a fake for every port, `load_context()` for
|
|
34
36
|
exercising `create()`, and `MemoryStorage` (install the `testing` extra) so a
|
|
35
37
|
module can unit test itself with only this package installed.
|
|
@@ -91,6 +93,18 @@ calls receive the exact source Discord message snowflake; other surfaces receive
|
|
|
91
93
|
Modules use namespaced guild documents and the physical table names returned
|
|
92
94
|
by `ctx.storage.table()`.
|
|
93
95
|
|
|
96
|
+
SDK 2.2 adds optional `ModuleToolContext.files`. Declare
|
|
97
|
+
`ModulePermissions(tool_files=True)`, depend on `kimi-agent-module-api>=2.2,<3`,
|
|
98
|
+
and require `tools.files.v1` in `ModuleSpec.requires_capabilities`. Existing modules
|
|
99
|
+
keep `api_version=2` and receive `files=None` without the permission.
|
|
100
|
+
`ctx.files.attachments` lists admitted current/reply entries;
|
|
101
|
+
`read_attachment(id, max_bytes=...)` and `read_workspace(path, max_bytes=...)`
|
|
102
|
+
return bounded bytes without network downloads. The reader expires when the handler
|
|
103
|
+
returns and is scoped to the actual caller. `testing.FakeToolFiles` supports
|
|
104
|
+
independent tests. See the
|
|
105
|
+
[file access guide](https://github.com/webhead2oo9/kimi-agent/blob/main/docs/module-files.md)
|
|
106
|
+
for moderation, reply-image availability, privacy, and limits.
|
|
107
|
+
|
|
94
108
|
## Testing the SDK
|
|
95
109
|
|
|
96
110
|
From this package directory, run its tests without installing the Kimi application:
|
|
@@ -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.files`: `ToolFiles`, `ToolAttachment`, `ToolFile`, and
|
|
17
|
+
`FileAccessError` for invocation-scoped attachment and workspace reads.
|
|
16
18
|
- `kimi_agent_module_api.testing`: a fake for every port, `load_context()` for
|
|
17
19
|
exercising `create()`, and `MemoryStorage` (install the `testing` extra) so a
|
|
18
20
|
module can unit test itself with only this package installed.
|
|
@@ -74,6 +76,18 @@ calls receive the exact source Discord message snowflake; other surfaces receive
|
|
|
74
76
|
Modules use namespaced guild documents and the physical table names returned
|
|
75
77
|
by `ctx.storage.table()`.
|
|
76
78
|
|
|
79
|
+
SDK 2.2 adds optional `ModuleToolContext.files`. Declare
|
|
80
|
+
`ModulePermissions(tool_files=True)`, depend on `kimi-agent-module-api>=2.2,<3`,
|
|
81
|
+
and require `tools.files.v1` in `ModuleSpec.requires_capabilities`. Existing modules
|
|
82
|
+
keep `api_version=2` and receive `files=None` without the permission.
|
|
83
|
+
`ctx.files.attachments` lists admitted current/reply entries;
|
|
84
|
+
`read_attachment(id, max_bytes=...)` and `read_workspace(path, max_bytes=...)`
|
|
85
|
+
return bounded bytes without network downloads. The reader expires when the handler
|
|
86
|
+
returns and is scoped to the actual caller. `testing.FakeToolFiles` supports
|
|
87
|
+
independent tests. See the
|
|
88
|
+
[file access guide](https://github.com/webhead2oo9/kimi-agent/blob/main/docs/module-files.md)
|
|
89
|
+
for moderation, reply-image availability, privacy, and limits.
|
|
90
|
+
|
|
77
91
|
## Testing the SDK
|
|
78
92
|
|
|
79
93
|
From this package directory, run its tests without installing the Kimi application:
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/__init__.py
RENAMED
|
@@ -37,6 +37,7 @@ from kimi_agent_module_api.contracts import (
|
|
|
37
37
|
TextInputStyle,
|
|
38
38
|
)
|
|
39
39
|
from kimi_agent_module_api.settings import ModuleSetting, ModuleSettingsDefinition
|
|
40
|
+
from kimi_agent_module_api.files import FileAccessError, ToolAttachment, ToolFile, ToolFiles
|
|
40
41
|
from kimi_agent_module_api.tools import (
|
|
41
42
|
ModuleToolContext,
|
|
42
43
|
ModuleToolHandler,
|
|
@@ -152,6 +153,7 @@ __all__ = [
|
|
|
152
153
|
"MODULE_ENTRYPOINT_GROUP",
|
|
153
154
|
"AppModule",
|
|
154
155
|
"ConfigSnapshot",
|
|
156
|
+
"FileAccessError",
|
|
155
157
|
"GuildSettingsSchema",
|
|
156
158
|
"InviteSnapshot",
|
|
157
159
|
"LayoutGallery",
|
|
@@ -183,6 +185,9 @@ __all__ = [
|
|
|
183
185
|
"ServiceRequirement",
|
|
184
186
|
"TextInputSpec",
|
|
185
187
|
"TextInputStyle",
|
|
188
|
+
"ToolAttachment",
|
|
189
|
+
"ToolFile",
|
|
190
|
+
"ToolFiles",
|
|
186
191
|
"TrustTier",
|
|
187
192
|
"render_guild_settings",
|
|
188
193
|
]
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/contracts.py
RENAMED
|
@@ -143,6 +143,8 @@ class ModulePermissions:
|
|
|
143
143
|
override_target_policy: bool = False
|
|
144
144
|
raw_bot: bool = False
|
|
145
145
|
raw_storage: bool = False
|
|
146
|
+
# Read admitted attachments and caller-owned workspace files in tool handlers.
|
|
147
|
+
tool_files: bool = False
|
|
146
148
|
|
|
147
149
|
|
|
148
150
|
@dataclass(frozen=True, slots=True)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Read-only, caller-scoped file access during one module tool invocation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Literal, Protocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FileAccessError(ValueError):
|
|
10
|
+
"""A bounded, safe-to-display file error; never includes host paths or URLs."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, code: str, message: str) -> None:
|
|
13
|
+
super().__init__(message)
|
|
14
|
+
self.code = code
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class ToolAttachment:
|
|
19
|
+
"""An admitted attachment or an explicit unavailable entry.
|
|
20
|
+
|
|
21
|
+
``id`` is opaque and valid only for this invocation. ``workspace_path`` is
|
|
22
|
+
relative to the caller's workspace, and may be reused in later turns.
|
|
23
|
+
Reply images have generated filenames when the host has no source name.
|
|
24
|
+
No Discord SDK objects, signed URLs, or absolute paths are exposed.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
id: str
|
|
28
|
+
filename: str
|
|
29
|
+
size: int
|
|
30
|
+
media_type: str | None
|
|
31
|
+
source: Literal["current", "reply"] = "current"
|
|
32
|
+
workspace_path: str | None = None
|
|
33
|
+
unavailable_reason: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class ToolFile:
|
|
38
|
+
filename: str
|
|
39
|
+
media_type: str | None
|
|
40
|
+
data: bytes = field(repr=False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ToolFiles(Protocol):
|
|
44
|
+
"""Available only when ``permissions.tool_files`` is declared.
|
|
45
|
+
|
|
46
|
+
Reads are bounded by the requested limit and host limits, and share an
|
|
47
|
+
aggregate byte budget per invocation. They use the caller's workspace and
|
|
48
|
+
admitted turn inputs; they never fetch arbitrary URLs or re-fetch Discord.
|
|
49
|
+
The port expires when the handler returns. Copying bytes does not extend
|
|
50
|
+
the host's privacy/retention guarantees; modules own any further persistence.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def attachments(self) -> tuple[ToolAttachment, ...]: ...
|
|
55
|
+
|
|
56
|
+
async def read_attachment(self, attachment_id: str, *, max_bytes: int) -> ToolFile: ...
|
|
57
|
+
|
|
58
|
+
async def read_workspace(self, path: str, *, max_bytes: int) -> ToolFile: ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__all__ = ["FileAccessError", "ToolAttachment", "ToolFile", "ToolFiles"]
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/testing.py
RENAMED
|
@@ -23,6 +23,7 @@ from typing import Any, TypeVar, overload
|
|
|
23
23
|
from pydantic_settings import BaseSettings
|
|
24
24
|
|
|
25
25
|
from kimi_agent_module_api.trust import TrustTier
|
|
26
|
+
from kimi_agent_module_api.files import FileAccessError, ToolAttachment, ToolFile
|
|
26
27
|
|
|
27
28
|
from kimi_agent_module_api.contracts import (
|
|
28
29
|
ALL_DISCORD_ACTIONS,
|
|
@@ -1313,6 +1314,70 @@ def load_context(
|
|
|
1313
1314
|
return context, recorder
|
|
1314
1315
|
|
|
1315
1316
|
|
|
1317
|
+
class FakeToolFiles:
|
|
1318
|
+
"""In-memory invocation files; paths and IDs are exact fixture keys.
|
|
1319
|
+
|
|
1320
|
+
``close()`` simulates handler return. Bounds and aggregate accounting match
|
|
1321
|
+
the host. This fake never reads the test process's filesystem or network.
|
|
1322
|
+
"""
|
|
1323
|
+
|
|
1324
|
+
def __init__(
|
|
1325
|
+
self,
|
|
1326
|
+
attachments: tuple[ToolAttachment, ...] = (),
|
|
1327
|
+
*,
|
|
1328
|
+
attachment_files: Mapping[str, ToolFile] | None = None,
|
|
1329
|
+
workspace_files: Mapping[str, ToolFile] | None = None,
|
|
1330
|
+
max_bytes: int = 50 * 1024 * 1024,
|
|
1331
|
+
) -> None:
|
|
1332
|
+
self._attachments = attachments
|
|
1333
|
+
self.attachment_files = dict(attachment_files or {})
|
|
1334
|
+
self.workspace_files = dict(workspace_files or {})
|
|
1335
|
+
self.reads: list[tuple[str, str, int]] = []
|
|
1336
|
+
self._remaining = min(max_bytes, 50 * 1024 * 1024)
|
|
1337
|
+
self._active = True
|
|
1338
|
+
|
|
1339
|
+
def close(self) -> None:
|
|
1340
|
+
self._active = False
|
|
1341
|
+
|
|
1342
|
+
def _check_active(self) -> None:
|
|
1343
|
+
if not self._active:
|
|
1344
|
+
raise FileAccessError("expired", "File access expired when the tool invocation ended.")
|
|
1345
|
+
|
|
1346
|
+
@property
|
|
1347
|
+
def attachments(self) -> tuple[ToolAttachment, ...]:
|
|
1348
|
+
self._check_active()
|
|
1349
|
+
return self._attachments
|
|
1350
|
+
|
|
1351
|
+
def _read(self, file: ToolFile | None, max_bytes: int) -> ToolFile:
|
|
1352
|
+
self._check_active()
|
|
1353
|
+
if type(max_bytes) is not int or max_bytes < 1:
|
|
1354
|
+
raise FileAccessError("invalid_limit", "max_bytes must be a positive integer.")
|
|
1355
|
+
if self._remaining <= 0:
|
|
1356
|
+
raise FileAccessError(
|
|
1357
|
+
"budget_exhausted", "The invocation's file read budget is exhausted."
|
|
1358
|
+
)
|
|
1359
|
+
if file is None:
|
|
1360
|
+
raise FileAccessError("unavailable", "File is not available in this fixture.")
|
|
1361
|
+
if len(file.data) > min(max_bytes, self._remaining):
|
|
1362
|
+
raise FileAccessError("too_large", "File exceeds the file read limit.")
|
|
1363
|
+
self._remaining -= len(file.data)
|
|
1364
|
+
return file
|
|
1365
|
+
|
|
1366
|
+
async def read_attachment(self, attachment_id: str, *, max_bytes: int) -> ToolFile:
|
|
1367
|
+
self._check_active()
|
|
1368
|
+
entry = next((item for item in self.attachments if item.id == attachment_id), None)
|
|
1369
|
+
if entry is None:
|
|
1370
|
+
raise FileAccessError("unknown_attachment", "Unknown attachment for this invocation.")
|
|
1371
|
+
if entry.unavailable_reason:
|
|
1372
|
+
raise FileAccessError("unavailable", entry.unavailable_reason)
|
|
1373
|
+
self.reads.append(("attachment", attachment_id, max_bytes))
|
|
1374
|
+
return self._read(self.attachment_files.get(attachment_id), max_bytes)
|
|
1375
|
+
|
|
1376
|
+
async def read_workspace(self, path: str, *, max_bytes: int) -> ToolFile:
|
|
1377
|
+
self.reads.append(("workspace", path, max_bytes))
|
|
1378
|
+
return self._read(self.workspace_files.get(path), max_bytes)
|
|
1379
|
+
|
|
1380
|
+
|
|
1316
1381
|
__all__ = [
|
|
1317
1382
|
"DiscordCall",
|
|
1318
1383
|
"FakeDiscordActions",
|
|
@@ -1327,6 +1392,7 @@ __all__ = [
|
|
|
1327
1392
|
"FakeResponse",
|
|
1328
1393
|
"FakeScheduler",
|
|
1329
1394
|
"FakeServiceRegistry",
|
|
1395
|
+
"FakeToolFiles",
|
|
1330
1396
|
"FakeTrust",
|
|
1331
1397
|
"LoadContextRecorder",
|
|
1332
1398
|
"MemoryStorage",
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/tools.py
RENAMED
|
@@ -7,6 +7,7 @@ from dataclasses import dataclass, field
|
|
|
7
7
|
from typing import Any, Protocol
|
|
8
8
|
|
|
9
9
|
from kimi_agent_module_api.trust import TrustTier
|
|
10
|
+
from kimi_agent_module_api.files import ToolFiles
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@dataclass(frozen=True, slots=True)
|
|
@@ -32,6 +33,9 @@ class ModuleToolContext:
|
|
|
32
33
|
# Discord message that initiated this model turn. It is absent for personal
|
|
33
34
|
# app commands and other surfaces that are not rooted in a Discord message.
|
|
34
35
|
trigger_discord_message_id: int | None = None
|
|
36
|
+
# Invocation-scoped read-only port. Declare permissions.tool_files and
|
|
37
|
+
# require tools.files.v1; never retain this port beyond the handler.
|
|
38
|
+
files: ToolFiles | None = None
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
type ModuleToolHandler = Callable[[dict[str, Any], ModuleToolContext], Coroutine[Any, Any, str]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kimi-agent-module-api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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.files`: `ToolFiles`, `ToolAttachment`, `ToolFile`, and
|
|
34
|
+
`FileAccessError` for invocation-scoped attachment and workspace reads.
|
|
33
35
|
- `kimi_agent_module_api.testing`: a fake for every port, `load_context()` for
|
|
34
36
|
exercising `create()`, and `MemoryStorage` (install the `testing` extra) so a
|
|
35
37
|
module can unit test itself with only this package installed.
|
|
@@ -91,6 +93,18 @@ calls receive the exact source Discord message snowflake; other surfaces receive
|
|
|
91
93
|
Modules use namespaced guild documents and the physical table names returned
|
|
92
94
|
by `ctx.storage.table()`.
|
|
93
95
|
|
|
96
|
+
SDK 2.2 adds optional `ModuleToolContext.files`. Declare
|
|
97
|
+
`ModulePermissions(tool_files=True)`, depend on `kimi-agent-module-api>=2.2,<3`,
|
|
98
|
+
and require `tools.files.v1` in `ModuleSpec.requires_capabilities`. Existing modules
|
|
99
|
+
keep `api_version=2` and receive `files=None` without the permission.
|
|
100
|
+
`ctx.files.attachments` lists admitted current/reply entries;
|
|
101
|
+
`read_attachment(id, max_bytes=...)` and `read_workspace(path, max_bytes=...)`
|
|
102
|
+
return bounded bytes without network downloads. The reader expires when the handler
|
|
103
|
+
returns and is scoped to the actual caller. `testing.FakeToolFiles` supports
|
|
104
|
+
independent tests. See the
|
|
105
|
+
[file access guide](https://github.com/webhead2oo9/kimi-agent/blob/main/docs/module-files.md)
|
|
106
|
+
for moderation, reply-image availability, privacy, and limits.
|
|
107
|
+
|
|
94
108
|
## Testing the SDK
|
|
95
109
|
|
|
96
110
|
From this package directory, run its tests without installing the Kimi application:
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
src/kimi_agent_module_api/__init__.py
|
|
5
5
|
src/kimi_agent_module_api/contracts.py
|
|
6
6
|
src/kimi_agent_module_api/events.py
|
|
7
|
+
src/kimi_agent_module_api/files.py
|
|
7
8
|
src/kimi_agent_module_api/images.py
|
|
8
9
|
src/kimi_agent_module_api/py.typed
|
|
9
10
|
src/kimi_agent_module_api/settings.py
|
|
@@ -16,5 +17,6 @@ src/kimi_agent_module_api.egg-info/dependency_links.txt
|
|
|
16
17
|
src/kimi_agent_module_api.egg-info/requires.txt
|
|
17
18
|
src/kimi_agent_module_api.egg-info/top_level.txt
|
|
18
19
|
tests/test_contracts.py
|
|
20
|
+
tests/test_files.py
|
|
19
21
|
tests/test_public_api.py
|
|
20
22
|
tests/test_testing.py
|
|
@@ -83,8 +83,8 @@ def test_spec_and_runtime_context_keep_stable_defaults() -> None:
|
|
|
83
83
|
assert {"events", "scheduler", "storage", "discord", "interactions", "services"} <= required
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
def
|
|
87
|
-
assert version("kimi-agent-module-api") == "2.
|
|
86
|
+
def test_distribution_version_includes_tool_files_contract() -> None:
|
|
87
|
+
assert version("kimi-agent-module-api") == "2.2.0"
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
def test_spec_requires_an_explicit_keyword_api_version() -> None:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from kimi_agent_module_api import FileAccessError, ToolAttachment, ToolFile, ToolFiles
|
|
6
|
+
from kimi_agent_module_api.testing import FakeToolFiles
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
async def test_fake_preserves_bytes_and_enforces_aggregate_budget() -> None:
|
|
11
|
+
attachment = ToolAttachment("a", "image.png", 3, "image/png")
|
|
12
|
+
file = ToolFile("image.png", "image/png", b"raw")
|
|
13
|
+
fake = FakeToolFiles(
|
|
14
|
+
(attachment,),
|
|
15
|
+
attachment_files={"a": file},
|
|
16
|
+
workspace_files={"saved.png": file},
|
|
17
|
+
max_bytes=5,
|
|
18
|
+
)
|
|
19
|
+
port: ToolFiles = fake
|
|
20
|
+
assert (await port.read_attachment("a", max_bytes=3)).data == b"raw"
|
|
21
|
+
with pytest.raises(FileAccessError, match="limit"):
|
|
22
|
+
await port.read_workspace("saved.png", max_bytes=3)
|
|
23
|
+
assert "raw" not in repr(file)
|
|
24
|
+
fake.close()
|
|
25
|
+
with pytest.raises(FileAccessError) as error:
|
|
26
|
+
await port.read_attachment("a", max_bytes=3)
|
|
27
|
+
assert error.value.code == "expired"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.asyncio
|
|
31
|
+
async def test_fake_does_not_read_unavailable_attachments() -> None:
|
|
32
|
+
fake = FakeToolFiles(
|
|
33
|
+
(ToolAttachment("a", "audio.wav", 3, "audio/wav", unavailable_reason="Blocked"),),
|
|
34
|
+
attachment_files={"a": ToolFile("audio.wav", "audio/wav", b"raw")},
|
|
35
|
+
)
|
|
36
|
+
with pytest.raises(FileAccessError, match="Blocked"):
|
|
37
|
+
await fake.read_attachment("a", max_bytes=3)
|
|
38
|
+
with pytest.raises(FileAccessError) as error:
|
|
39
|
+
await fake.read_attachment("other", max_bytes=3)
|
|
40
|
+
assert error.value.code == "unknown_attachment"
|
|
41
|
+
assert not fake.reads
|
|
File without changes
|
|
File without changes
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/events.py
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/images.py
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/py.typed
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.0}/src/kimi_agent_module_api/settings.py
RENAMED
|
File without changes
|
{kimi_agent_module_api-2.1.0 → kimi_agent_module_api-2.2.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
|