deeptutor-partners 1.7.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.
- deeptutor/api/routers/_partners_channel_schema.py +176 -0
- deeptutor/api/routers/partner_groups.py +534 -0
- deeptutor/api/routers/partners.py +2111 -0
- deeptutor/capabilities/handoff/__init__.py +5 -0
- deeptutor/capabilities/handoff/capability.py +512 -0
- deeptutor/capabilities/partner_authoring/__init__.py +13 -0
- deeptutor/capabilities/partner_authoring/binding.py +100 -0
- deeptutor/capabilities/partner_authoring/capability.py +71 -0
- deeptutor/capabilities/partner_authoring/prompts/en/heuristic.md +1 -0
- deeptutor/capabilities/partner_authoring/prompts/en/system.md +9 -0
- deeptutor/capabilities/partner_authoring/prompts/zh/heuristic.md +1 -0
- deeptutor/capabilities/partner_authoring/prompts/zh/system.md +9 -0
- deeptutor/capabilities/partner_authoring/tools.py +83 -0
- deeptutor/capabilities/partner_group/__init__.py +13 -0
- deeptutor/capabilities/partner_group/capability.py +229 -0
- deeptutor/capabilities/partner_group/prompts/en/invoke_other.md +1 -0
- deeptutor/capabilities/partner_group/prompts/en/system.md +3 -0
- deeptutor/capabilities/partner_group/prompts/zh/invoke_other.md +1 -0
- deeptutor/capabilities/partner_group/prompts/zh/system.md +3 -0
- deeptutor/capabilities/partner_group/tools.py +162 -0
- deeptutor/partners/__init__.py +11 -0
- deeptutor/partners/bus/__init__.py +6 -0
- deeptutor/partners/bus/events.py +41 -0
- deeptutor/partners/bus/queue.py +63 -0
- deeptutor/partners/channels/__init__.py +6 -0
- deeptutor/partners/channels/base.py +276 -0
- deeptutor/partners/channels/dingtalk.py +550 -0
- deeptutor/partners/channels/discord.py +517 -0
- deeptutor/partners/channels/email.py +470 -0
- deeptutor/partners/channels/feishu.py +2459 -0
- deeptutor/partners/channels/lark_http.py +79 -0
- deeptutor/partners/channels/manager.py +518 -0
- deeptutor/partners/channels/matrix.py +844 -0
- deeptutor/partners/channels/mattermost.py +450 -0
- deeptutor/partners/channels/mochat.py +1076 -0
- deeptutor/partners/channels/msteams.py +847 -0
- deeptutor/partners/channels/napcat.py +594 -0
- deeptutor/partners/channels/qq.py +203 -0
- deeptutor/partners/channels/registry.py +97 -0
- deeptutor/partners/channels/slack.py +333 -0
- deeptutor/partners/channels/telegram.py +1157 -0
- deeptutor/partners/channels/wecom.py +398 -0
- deeptutor/partners/channels/weixin.py +1445 -0
- deeptutor/partners/channels/weixin_qr.py +202 -0
- deeptutor/partners/channels/whatsapp.py +210 -0
- deeptutor/partners/channels/zulip.py +859 -0
- deeptutor/partners/config/__init__.py +26 -0
- deeptutor/partners/config/paths.py +89 -0
- deeptutor/partners/config/schema.py +50 -0
- deeptutor/partners/helpers.py +109 -0
- deeptutor/partners/network.py +101 -0
- deeptutor/partners/transcription.py +61 -0
- deeptutor/services/partner_groups/__init__.py +18 -0
- deeptutor/services/partner_groups/consultation.py +40 -0
- deeptutor/services/partner_groups/manager.py +1638 -0
- deeptutor/services/partner_groups/memory.py +204 -0
- deeptutor/services/partner_groups/models.py +115 -0
- deeptutor/services/partner_groups/modes.py +195 -0
- deeptutor/services/partner_groups/store.py +442 -0
- deeptutor/services/partners/__init__.py +28 -0
- deeptutor/services/partners/channel_onboarding.py +552 -0
- deeptutor/services/partners/channel_state_migration.py +127 -0
- deeptutor/services/partners/commands.py +497 -0
- deeptutor/services/partners/drafts.py +145 -0
- deeptutor/services/partners/interaction.py +189 -0
- deeptutor/services/partners/links.py +197 -0
- deeptutor/services/partners/manager.py +1877 -0
- deeptutor/services/partners/model_runtime.py +36 -0
- deeptutor/services/partners/module_service.py +15 -0
- deeptutor/services/partners/runtime.py +1174 -0
- deeptutor/services/partners/runtime_status.py +133 -0
- deeptutor/services/partners/scope.py +43 -0
- deeptutor/services/partners/sessions.py +462 -0
- deeptutor/services/partners/web_continuity.py +175 -0
- deeptutor/services/partners/weixin_onboarding.py +324 -0
- deeptutor/services/partners/workspace.py +451 -0
- deeptutor/services/partners/workspace_binding.py +48 -0
- deeptutor/tools/partner_memory.py +315 -0
- deeptutor_cli/partner.py +103 -0
- deeptutor_partners-1.7.0.dist-info/METADATA +24 -0
- deeptutor_partners-1.7.0.dist-info/RECORD +83 -0
- deeptutor_partners-1.7.0.dist-info/WHEEL +5 -0
- deeptutor_partners-1.7.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Channel-schema introspection — bridges Pydantic channel configs to the Web UI.
|
|
2
|
+
|
|
3
|
+
Used by ``GET /api/partners/channels/schema`` so the front-end can render
|
|
4
|
+
generic forms for ANY channel (built-in or plugin) without hard-coding fields.
|
|
5
|
+
|
|
6
|
+
Why live here vs inside ``deeptutor.partners.channels``?
|
|
7
|
+
* This is an API-shaping concern (JSON Schema flattening, secret-field
|
|
8
|
+
detection) — keeping it next to the route avoids polluting the runtime
|
|
9
|
+
channel package with HTTP-specific helpers.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import inspect
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
from deeptutor.services.partners.manager import _is_secret_field
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resolve_config_model(channel_cls: type) -> type[BaseModel] | None:
|
|
23
|
+
"""Find the Pydantic config model paired with ``channel_cls``.
|
|
24
|
+
|
|
25
|
+
Convention every built-in channel follows: ``XxxChannel`` lives in the
|
|
26
|
+
same module as ``XxxConfig`` (e.g. ``TelegramChannel`` ↔ ``TelegramConfig``).
|
|
27
|
+
Falls back to "any ``*Config`` BaseModel in the module".
|
|
28
|
+
"""
|
|
29
|
+
module = inspect.getmodule(channel_cls)
|
|
30
|
+
if module is None:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
expected = channel_cls.__name__.replace("Channel", "") + "Config"
|
|
34
|
+
candidate = getattr(module, expected, None)
|
|
35
|
+
if isinstance(candidate, type) and issubclass(candidate, BaseModel):
|
|
36
|
+
return candidate
|
|
37
|
+
|
|
38
|
+
for _, obj in inspect.getmembers(module):
|
|
39
|
+
if (
|
|
40
|
+
isinstance(obj, type)
|
|
41
|
+
and obj is not BaseModel
|
|
42
|
+
and issubclass(obj, BaseModel)
|
|
43
|
+
and obj.__name__.endswith("Config")
|
|
44
|
+
):
|
|
45
|
+
return obj
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def inline_refs(schema: dict[str, Any]) -> dict[str, Any]:
|
|
50
|
+
"""Flatten Pydantic's ``$defs`` / ``$ref`` so the front-end doesn't need a resolver.
|
|
51
|
+
|
|
52
|
+
Nested model fields (e.g. ``slack.dm: SlackDMConfig``) become inline
|
|
53
|
+
``type: object`` subtrees with their own ``properties``.
|
|
54
|
+
"""
|
|
55
|
+
defs: dict[str, Any] = dict(schema.get("$defs", {}))
|
|
56
|
+
|
|
57
|
+
def _walk(node: Any) -> Any:
|
|
58
|
+
if isinstance(node, dict):
|
|
59
|
+
ref = node.get("$ref")
|
|
60
|
+
if isinstance(ref, str) and ref.startswith("#/$defs/"):
|
|
61
|
+
ref_name = ref.rsplit("/", 1)[-1]
|
|
62
|
+
resolved = defs.get(ref_name, {})
|
|
63
|
+
merged = {**resolved}
|
|
64
|
+
# Allow per-field overrides (description, default) from the ref site.
|
|
65
|
+
for k, v in node.items():
|
|
66
|
+
if k != "$ref":
|
|
67
|
+
merged[k] = v
|
|
68
|
+
return _walk(merged)
|
|
69
|
+
return {k: _walk(v) for k, v in node.items()}
|
|
70
|
+
if isinstance(node, list):
|
|
71
|
+
return [_walk(item) for item in node]
|
|
72
|
+
return node
|
|
73
|
+
|
|
74
|
+
out = _walk(schema)
|
|
75
|
+
if isinstance(out, dict):
|
|
76
|
+
out.pop("$defs", None)
|
|
77
|
+
return out
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _schema_accepts_string(prop_schema: dict[str, Any]) -> bool:
|
|
81
|
+
"""True iff the JSON-Schema fragment can hold a string value.
|
|
82
|
+
|
|
83
|
+
Used to filter out booleans/integers/arrays whose name happens to contain
|
|
84
|
+
a secret-looking substring (e.g. ``user_token_read_only: bool``).
|
|
85
|
+
"""
|
|
86
|
+
t = prop_schema.get("type")
|
|
87
|
+
if t == "string":
|
|
88
|
+
return True
|
|
89
|
+
if isinstance(t, list) and "string" in t:
|
|
90
|
+
return True
|
|
91
|
+
for variant in prop_schema.get("anyOf", []):
|
|
92
|
+
if isinstance(variant, dict) and variant.get("type") == "string":
|
|
93
|
+
return True
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def collect_secret_fields(schema: dict[str, Any], prefix: str = "") -> list[str]:
|
|
98
|
+
"""Return dot-paths for every string-typed property whose name hints at a secret.
|
|
99
|
+
|
|
100
|
+
e.g. ``["token"]`` for telegram, ``["imap_password", "smtp_password"]``
|
|
101
|
+
for email, ``["bot_token", "app_token"]`` for slack. A field like
|
|
102
|
+
``user_token_read_only: bool`` is intentionally skipped.
|
|
103
|
+
"""
|
|
104
|
+
paths: list[str] = []
|
|
105
|
+
properties = schema.get("properties") if isinstance(schema, dict) else None
|
|
106
|
+
if not isinstance(properties, dict):
|
|
107
|
+
return paths
|
|
108
|
+
|
|
109
|
+
for prop_name, prop_schema in properties.items():
|
|
110
|
+
if not isinstance(prop_schema, dict):
|
|
111
|
+
continue
|
|
112
|
+
full = f"{prefix}{prop_name}" if not prefix else f"{prefix}.{prop_name}"
|
|
113
|
+
if _is_secret_field(prop_name) and _schema_accepts_string(prop_schema):
|
|
114
|
+
paths.append(full)
|
|
115
|
+
if prop_schema.get("type") == "object":
|
|
116
|
+
paths.extend(collect_secret_fields(prop_schema, prefix=full))
|
|
117
|
+
return paths
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def channel_schema_payload(channel_cls: type) -> dict[str, Any] | None:
|
|
121
|
+
"""Build the per-channel schema payload, or ``None`` if no config model found."""
|
|
122
|
+
model = resolve_config_model(channel_cls)
|
|
123
|
+
if model is None:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
# by_alias=False → property names match Python field names (snake_case),
|
|
127
|
+
# which is exactly the shape we persist in ``config.yaml`` and what every
|
|
128
|
+
# channel's ``__init__`` expects when ``model_validate(dict)`` is called.
|
|
129
|
+
# The pydantic Base config has populate_by_name=True so the runtime still
|
|
130
|
+
# accepts both forms; we standardise on snake_case for the wire schema.
|
|
131
|
+
raw = model.model_json_schema(by_alias=False)
|
|
132
|
+
flat = inline_refs(raw)
|
|
133
|
+
secret_fields = collect_secret_fields(flat)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
default_config = model().model_dump(mode="json", by_alias=False)
|
|
137
|
+
except Exception:
|
|
138
|
+
default_config = {}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"name": getattr(channel_cls, "name", channel_cls.__name__),
|
|
142
|
+
"display_name": getattr(channel_cls, "display_name", channel_cls.__name__),
|
|
143
|
+
"default_config": default_config,
|
|
144
|
+
"secret_fields": secret_fields,
|
|
145
|
+
"json_schema": flat,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def all_channel_schemas() -> dict[str, dict[str, Any]]:
|
|
150
|
+
"""Build the schema dict for every discovered channel (built-in + plugins).
|
|
151
|
+
|
|
152
|
+
Channels whose module failed to import (missing optional dependency)
|
|
153
|
+
still appear, marked ``available: False`` with the import error — the UI
|
|
154
|
+
shows them grayed out instead of silently dropping them.
|
|
155
|
+
"""
|
|
156
|
+
from deeptutor.partners.channels.registry import discover_all_with_errors
|
|
157
|
+
|
|
158
|
+
channels, errors = discover_all_with_errors()
|
|
159
|
+
|
|
160
|
+
out: dict[str, dict[str, Any]] = {}
|
|
161
|
+
for name, cls in channels.items():
|
|
162
|
+
payload = channel_schema_payload(cls)
|
|
163
|
+
if payload is not None:
|
|
164
|
+
payload["available"] = True
|
|
165
|
+
out[name] = payload
|
|
166
|
+
for name, reason in errors.items():
|
|
167
|
+
out[name] = {
|
|
168
|
+
"name": name,
|
|
169
|
+
"display_name": name.title(),
|
|
170
|
+
"available": False,
|
|
171
|
+
"unavailable_reason": reason,
|
|
172
|
+
"default_config": {"enabled": False},
|
|
173
|
+
"secret_fields": [],
|
|
174
|
+
"json_schema": None,
|
|
175
|
+
}
|
|
176
|
+
return out
|