deeptutor-server 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.
Files changed (48) hide show
  1. deeptutor/api/__init__.py +0 -0
  2. deeptutor/api/contracts/__init__.py +21 -0
  3. deeptutor/api/contracts/export.py +110 -0
  4. deeptutor/api/contracts/turn_protocol.py +313 -0
  5. deeptutor/api/main.py +547 -0
  6. deeptutor/api/module_routes.py +42 -0
  7. deeptutor/api/router_index.json +5752 -0
  8. deeptutor/api/router_loader.py +139 -0
  9. deeptutor/api/routers/__init__.py +1 -0
  10. deeptutor/api/routers/agent_config.py +59 -0
  11. deeptutor/api/routers/attachments.py +79 -0
  12. deeptutor/api/routers/auth.py +1937 -0
  13. deeptutor/api/routers/capabilities.py +44 -0
  14. deeptutor/api/routers/capabilities_settings.py +38 -0
  15. deeptutor/api/routers/dashboard.py +292 -0
  16. deeptutor/api/routers/file_library.py +169 -0
  17. deeptutor/api/routers/file_preview.py +205 -0
  18. deeptutor/api/routers/imports.py +146 -0
  19. deeptutor/api/routers/learning_journal.py +18 -0
  20. deeptutor/api/routers/mcp_settings.py +133 -0
  21. deeptutor/api/routers/modules.py +12 -0
  22. deeptutor/api/routers/multi_user.py +943 -0
  23. deeptutor/api/routers/outputs.py +97 -0
  24. deeptutor/api/routers/personas.py +154 -0
  25. deeptutor/api/routers/plugins.py +416 -0
  26. deeptutor/api/routers/quiz_judge.py +464 -0
  27. deeptutor/api/routers/sessions.py +801 -0
  28. deeptutor/api/routers/settings.py +2503 -0
  29. deeptutor/api/routers/skills.py +315 -0
  30. deeptutor/api/routers/space_cli_apps.py +222 -0
  31. deeptutor/api/routers/space_mcp.py +460 -0
  32. deeptutor/api/routers/subagents.py +306 -0
  33. deeptutor/api/routers/system.py +633 -0
  34. deeptutor/api/routers/task_board.py +139 -0
  35. deeptutor/api/routers/tools.py +268 -0
  36. deeptutor/api/routers/unified_ws.py +385 -0
  37. deeptutor/api/routers/workspace.py +371 -0
  38. deeptutor/api/run_server.py +99 -0
  39. deeptutor/api/security_guard.py +91 -0
  40. deeptutor/api/utils/http_headers.py +27 -0
  41. deeptutor/api/utils/progress_broadcaster.py +73 -0
  42. deeptutor/api/utils/task_id_manager.py +120 -0
  43. deeptutor/api/utils/task_log_stream.py +362 -0
  44. deeptutor/api/utils/tool_options.py +133 -0
  45. deeptutor_server-1.7.0.dist-info/METADATA +14 -0
  46. deeptutor_server-1.7.0.dist-info/RECORD +48 -0
  47. deeptutor_server-1.7.0.dist-info/WHEEL +5 -0
  48. deeptutor_server-1.7.0.dist-info/top_level.txt +1 -0
File without changes
@@ -0,0 +1,21 @@
1
+ """Public API and WebSocket contract models."""
2
+
3
+ from .turn_protocol import (
4
+ ErrorEnvelope,
5
+ RuntimeStatus,
6
+ StreamEvent,
7
+ StreamEventType,
8
+ TurnFailureCode,
9
+ TurnProtocolDocument,
10
+ TurnStatus,
11
+ )
12
+
13
+ __all__ = [
14
+ "ErrorEnvelope",
15
+ "RuntimeStatus",
16
+ "StreamEvent",
17
+ "StreamEventType",
18
+ "TurnFailureCode",
19
+ "TurnProtocolDocument",
20
+ "TurnStatus",
21
+ ]
@@ -0,0 +1,110 @@
1
+ """Deterministically export backend-owned browser contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from copy import deepcopy
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel
12
+
13
+ from deeptutor.app.contracts import TurnRequest
14
+
15
+ from .turn_protocol import (
16
+ ErrorEnvelope,
17
+ RuntimeStatus,
18
+ SessionDetail,
19
+ SessionSummary,
20
+ TurnProtocolDocument,
21
+ )
22
+
23
+ PROJECT_ROOT = Path(__file__).resolve().parents[3]
24
+ DEFAULT_OUTPUT_DIR = PROJECT_ROOT / "web" / "contracts" / "schema"
25
+ HTTP_METHODS = {"delete", "get", "head", "options", "patch", "post", "put", "trace"}
26
+
27
+
28
+ def _json_text(payload: dict[str, Any]) -> str:
29
+ return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
30
+
31
+
32
+ def _merge_model_schema(components: dict[str, Any], model: type[BaseModel]) -> None:
33
+ schema = model.model_json_schema(ref_template="#/components/schemas/{model}")
34
+ definitions = schema.pop("$defs", {})
35
+ components.update(definitions)
36
+ components[model.__name__] = schema
37
+
38
+
39
+ def _assert_unique_operation_ids(openapi: dict[str, Any]) -> None:
40
+ """Reject router defects instead of hiding them in generated artifacts."""
41
+
42
+ seen: dict[str, tuple[str, str]] = {}
43
+ duplicates: list[str] = []
44
+ for path, path_item in openapi.get("paths", {}).items():
45
+ for method, operation in path_item.items():
46
+ if method not in HTTP_METHODS or not isinstance(operation, dict):
47
+ continue
48
+ operation_id = operation.get("operationId")
49
+ if not operation_id:
50
+ continue
51
+ previous = seen.get(operation_id)
52
+ if previous is None:
53
+ seen[operation_id] = (path, method)
54
+ continue
55
+ duplicates.append(
56
+ f"{operation_id}: {previous[1].upper()} {previous[0]} and {method.upper()} {path}"
57
+ )
58
+ if duplicates:
59
+ raise RuntimeError("Duplicate OpenAPI operation IDs:\n" + "\n".join(duplicates))
60
+
61
+
62
+ def render_contracts() -> dict[str, str]:
63
+ from deeptutor.api.main import app
64
+
65
+ openapi = deepcopy(app.openapi())
66
+ _assert_unique_operation_ids(openapi)
67
+ schemas = openapi.setdefault("components", {}).setdefault("schemas", {})
68
+ for model in (TurnRequest, RuntimeStatus, SessionSummary, SessionDetail, ErrorEnvelope):
69
+ _merge_model_schema(schemas, model)
70
+ openapi["x-deeptutor-web-protocol-version"] = "2.0"
71
+
72
+ protocol = TurnProtocolDocument.model_json_schema()
73
+ return {
74
+ "openapi.json": _json_text(openapi),
75
+ "turn-protocol.json": _json_text(protocol),
76
+ }
77
+
78
+
79
+ def write_contracts(output_dir: Path, *, check: bool = False) -> list[str]:
80
+ rendered = render_contracts()
81
+ changed: list[str] = []
82
+ for filename, content in rendered.items():
83
+ target = output_dir / filename
84
+ current = target.read_text(encoding="utf-8") if target.exists() else None
85
+ if current == content:
86
+ continue
87
+ changed.append(filename)
88
+ if not check:
89
+ output_dir.mkdir(parents=True, exist_ok=True)
90
+ target.write_text(content, encoding="utf-8")
91
+ return changed
92
+
93
+
94
+ def main(argv: list[str] | None = None) -> int:
95
+ parser = argparse.ArgumentParser(description=__doc__)
96
+ parser.add_argument("--check", action="store_true", help="fail when artifacts drift")
97
+ parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
98
+ args = parser.parse_args(argv)
99
+
100
+ changed = write_contracts(args.output_dir, check=args.check)
101
+ if args.check and changed:
102
+ print("Frontend contract drift: " + ", ".join(changed))
103
+ return 1
104
+ if changed:
105
+ print("Updated frontend contracts: " + ", ".join(changed))
106
+ return 0
107
+
108
+
109
+ if __name__ == "__main__":
110
+ raise SystemExit(main())
@@ -0,0 +1,313 @@
1
+ """Canonical v2 wire models for the browser turn protocol."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+ from typing import Annotated, Any, Literal
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
9
+
10
+ from deeptutor.app.contracts import TurnRequest
11
+
12
+ PROTOCOL_VERSION: Literal["2.0"] = "2.0"
13
+ MINIMUM_WEB_PROTOCOL_VERSION: Literal["2.0"] = "2.0"
14
+
15
+
16
+ class TurnStatus(str, Enum):
17
+ QUEUED = "queued"
18
+ RUNNING = "running"
19
+ WAITING_INPUT = "waiting_input"
20
+ COMPLETED = "completed"
21
+ FAILED = "failed"
22
+ CANCELLED = "cancelled"
23
+
24
+
25
+ class TurnQueryState(str, Enum):
26
+ """Client observation state, including server-side recovery windows."""
27
+
28
+ QUEUED = "queued"
29
+ RUNNING = "running"
30
+ WAITING_INPUT = "waiting_input"
31
+ RECOVERING = "recovering"
32
+ COMPLETED = "completed"
33
+ FAILED = "failed"
34
+ CANCELLED = "cancelled"
35
+
36
+
37
+ class TurnFailureCode(str, Enum):
38
+ WORKER_LOST = "worker_lost"
39
+ LEASE_LOST = "lease_lost"
40
+ COORDINATION_UNAVAILABLE = "coordination_unavailable"
41
+ PROVIDER_ERROR = "provider_error"
42
+ INTERNAL_ERROR = "internal_error"
43
+ REJECTED = "rejected"
44
+ SERVER_SHUTDOWN = "server_shutdown"
45
+
46
+
47
+ class StreamEventType(str, Enum):
48
+ STAGE_START = "stage_start"
49
+ STAGE_END = "stage_end"
50
+ THINKING = "thinking"
51
+ OBSERVATION = "observation"
52
+ CONTENT = "content"
53
+ TOOL_CALL = "tool_call"
54
+ TOOL_RESULT = "tool_result"
55
+ PROGRESS = "progress"
56
+ SOURCES = "sources"
57
+ RESULT = "result"
58
+ ERROR = "error"
59
+ SESSION = "session"
60
+ SESSION_META = "session_meta"
61
+ WAIT_FOR_INPUT = "wait_for_input"
62
+ DONE = "done"
63
+
64
+
65
+ class WireModel(BaseModel):
66
+ model_config = ConfigDict(extra="forbid")
67
+
68
+
69
+ class StartTurnCommand(TurnRequest):
70
+ type: Literal["message", "start_turn"] = "start_turn"
71
+ protocol_version: Literal["2.0"]
72
+
73
+
74
+ class SubscribeTurnCommand(WireModel):
75
+ type: Literal["subscribe_turn"] = "subscribe_turn"
76
+ turn_id: str = Field(min_length=1)
77
+ after_seq: int = Field(default=0, ge=0)
78
+ protocol_version: Literal["2.0"]
79
+
80
+
81
+ class SubscribeSessionCommand(WireModel):
82
+ type: Literal["subscribe_session"] = "subscribe_session"
83
+ session_id: str = Field(min_length=1)
84
+ after_seq: int = Field(default=0, ge=0)
85
+ protocol_version: Literal["2.0"]
86
+
87
+
88
+ class ResumeTurnCommand(WireModel):
89
+ type: Literal["resume_from"] = "resume_from"
90
+ turn_id: str = Field(min_length=1)
91
+ seq: int = Field(default=0, ge=0)
92
+ protocol_version: Literal["2.0"]
93
+
94
+
95
+ class UnsubscribeCommand(WireModel):
96
+ type: Literal["unsubscribe"] = "unsubscribe"
97
+ turn_id: str | None = None
98
+ session_id: str | None = None
99
+ protocol_version: Literal["2.0"]
100
+
101
+ @model_validator(mode="after")
102
+ def _has_target(self) -> "UnsubscribeCommand":
103
+ if not self.turn_id and not self.session_id:
104
+ raise ValueError("unsubscribe requires turn_id or session_id")
105
+ return self
106
+
107
+
108
+ class CancelTurnCommand(WireModel):
109
+ type: Literal["cancel_turn"] = "cancel_turn"
110
+ turn_id: str = Field(min_length=1)
111
+ command_id: str = Field(min_length=1)
112
+ protocol_version: Literal["2.0"]
113
+
114
+
115
+ class RegenerateCommand(WireModel):
116
+ type: Literal["regenerate"] = "regenerate"
117
+ session_id: str = Field(min_length=1)
118
+ overrides: dict[str, Any] = Field(default_factory=dict)
119
+ protocol_version: Literal["2.0"]
120
+
121
+
122
+ class UserAnswer(WireModel):
123
+ questionId: str = Field(min_length=1)
124
+ text: str = ""
125
+
126
+
127
+ class SubmitUserReplyCommand(WireModel):
128
+ type: Literal["submit_user_reply"] = "submit_user_reply"
129
+ turn_id: str = Field(min_length=1)
130
+ text: str | None = None
131
+ answers: list[UserAnswer] | None = None
132
+ command_id: str = Field(min_length=1)
133
+ protocol_version: Literal["2.0"]
134
+
135
+ @model_validator(mode="after")
136
+ def _has_reply(self) -> "SubmitUserReplyCommand":
137
+ if self.text is None and not self.answers:
138
+ raise ValueError("submit_user_reply requires text or answers")
139
+ return self
140
+
141
+
142
+ class UserInputCommand(WireModel):
143
+ """A message for a turn that is still running, read at its next step."""
144
+
145
+ type: Literal["user_input"] = "user_input"
146
+ turn_id: str = Field(min_length=1)
147
+ content: str
148
+ command_id: str = Field(min_length=1)
149
+ # The command id of an earlier, still unread ``user_input`` to take back.
150
+ withdraw: str | None = None
151
+ protocol_version: Literal["2.0"]
152
+
153
+
154
+ class CheckActiveTurnCommand(WireModel):
155
+ type: Literal["check_active_turn"] = "check_active_turn"
156
+ session_id: str = Field(min_length=1)
157
+ protocol_version: Literal["2.0"]
158
+
159
+
160
+ class PingCommand(WireModel):
161
+ type: Literal["ping"] = "ping"
162
+ protocol_version: Literal["2.0"]
163
+
164
+
165
+ ClientCommand = Annotated[
166
+ StartTurnCommand
167
+ | SubscribeTurnCommand
168
+ | SubscribeSessionCommand
169
+ | ResumeTurnCommand
170
+ | UnsubscribeCommand
171
+ | CancelTurnCommand
172
+ | RegenerateCommand
173
+ | SubmitUserReplyCommand
174
+ | UserInputCommand
175
+ | CheckActiveTurnCommand
176
+ | PingCommand,
177
+ Field(discriminator="type"),
178
+ ]
179
+
180
+
181
+ class StreamEvent(WireModel):
182
+ type: StreamEventType
183
+ source: str = ""
184
+ stage: str = ""
185
+ content: str = ""
186
+ metadata: dict[str, Any] = Field(default_factory=dict)
187
+ session_id: str = ""
188
+ turn_id: str = ""
189
+ seq: int = Field(default=0, ge=0)
190
+ timestamp: float
191
+ protocol_version: Literal["2.0"] = PROTOCOL_VERSION
192
+
193
+
194
+ class ActiveTurnInfo(WireModel):
195
+ type: Literal["active_turn_info"] = "active_turn_info"
196
+ turn_id: str = ""
197
+ status: TurnQueryState | Literal["none"] = "none"
198
+ owner_id: str = ""
199
+ protocol_version: Literal["2.0"] = PROTOCOL_VERSION
200
+
201
+
202
+ class PongEvent(WireModel):
203
+ type: Literal["pong"] = "pong"
204
+ protocol_version: Literal["2.0"] = PROTOCOL_VERSION
205
+
206
+
207
+ class CommandAckEvent(WireModel):
208
+ type: Literal["command_ack"] = "command_ack"
209
+ command_id: str = Field(min_length=1)
210
+ command_type: Literal["cancel_turn", "submit_user_reply", "user_input"]
211
+ accepted: bool
212
+ turn_id: str = ""
213
+ error_code: str = ""
214
+ message: str = ""
215
+ protocol_version: Literal["2.0"] = PROTOCOL_VERSION
216
+
217
+
218
+ class ProtocolErrorEvent(WireModel):
219
+ type: Literal["protocol_error"] = "protocol_error"
220
+ error_code: str = Field(min_length=1)
221
+ message: str
222
+ retryable: bool = False
223
+ session_id: str = ""
224
+ turn_id: str = ""
225
+ protocol_version: Literal["2.0"] = PROTOCOL_VERSION
226
+
227
+
228
+ ServerEvent = StreamEvent | ActiveTurnInfo | PongEvent | CommandAckEvent | ProtocolErrorEvent
229
+
230
+
231
+ class TurnSummary(WireModel):
232
+ id: str
233
+ session_id: str
234
+ status: TurnStatus
235
+ query_state: TurnQueryState | None = None
236
+ capability: str = ""
237
+ owner_id: str = ""
238
+ last_seq: int = Field(default=0, ge=0)
239
+ error: str = ""
240
+ error_code: TurnFailureCode | None = None
241
+ retryable: bool = False
242
+ created_at: float | None = None
243
+ updated_at: float | None = None
244
+
245
+
246
+ class SessionSummary(WireModel):
247
+ id: str
248
+ title: str
249
+ created_at: float | None = None
250
+ updated_at: float | None = None
251
+ active_turn: TurnSummary | None = None
252
+
253
+
254
+ class SessionDetail(SessionSummary):
255
+ messages: list[dict[str, Any]] = Field(default_factory=list)
256
+ preferences: dict[str, Any] = Field(default_factory=dict)
257
+
258
+
259
+ class RuntimeStatus(WireModel):
260
+ worker_id: str
261
+ worker_count: int = Field(ge=1)
262
+ coordination_mode: Literal["memory", "redis"]
263
+ redis_configured: bool
264
+ redis_status: Literal["ok", "unavailable", "not_configured"]
265
+ leader_id: str | None = None
266
+ leader_healthy: bool | None = None
267
+ owner_turn_count: int = Field(default=0, ge=0)
268
+ recovery_backlog: int = Field(default=0, ge=0)
269
+ lease_ttl_seconds: int = Field(ge=1)
270
+ renew_interval_seconds: int = Field(ge=1)
271
+ recovery_interval_seconds: int = Field(ge=1)
272
+ protocol_version: Literal["2.0"] = PROTOCOL_VERSION
273
+ minimum_web_protocol_version: Literal["2.0"] = MINIMUM_WEB_PROTOCOL_VERSION
274
+
275
+
276
+ class ErrorEnvelope(WireModel):
277
+ error_code: str
278
+ message: str
279
+ retryable: bool = False
280
+ correlation_id: str | None = None
281
+
282
+
283
+ class TurnProtocolDocument(WireModel):
284
+ protocol_version: Literal["2.0"] = PROTOCOL_VERSION
285
+ minimum_web_protocol_version: Literal["2.0"] = MINIMUM_WEB_PROTOCOL_VERSION
286
+ client_command: ClientCommand
287
+ server_event: ServerEvent
288
+ runtime_status: RuntimeStatus
289
+ session_summary: SessionSummary
290
+ session_detail: SessionDetail
291
+ error: ErrorEnvelope
292
+
293
+
294
+ __all__ = [
295
+ "ActiveTurnInfo",
296
+ "ClientCommand",
297
+ "CommandAckEvent",
298
+ "ErrorEnvelope",
299
+ "MINIMUM_WEB_PROTOCOL_VERSION",
300
+ "PROTOCOL_VERSION",
301
+ "ProtocolErrorEvent",
302
+ "RuntimeStatus",
303
+ "ServerEvent",
304
+ "SessionDetail",
305
+ "SessionSummary",
306
+ "StreamEvent",
307
+ "StreamEventType",
308
+ "TurnFailureCode",
309
+ "TurnProtocolDocument",
310
+ "TurnQueryState",
311
+ "TurnStatus",
312
+ "TurnSummary",
313
+ ]