remdb 0.3.242__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.
Potentially problematic release.
This version of remdb might be problematic. Click here for more details.
- rem/__init__.py +129 -0
- rem/agentic/README.md +760 -0
- rem/agentic/__init__.py +54 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +38 -0
- rem/agentic/agents/agent_manager.py +311 -0
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +425 -0
- rem/agentic/context_builder.py +360 -0
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +273 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +240 -0
- rem/agentic/providers/phoenix.py +926 -0
- rem/agentic/providers/pydantic_ai.py +854 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +737 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +242 -0
- rem/api/README.md +657 -0
- rem/api/deps.py +253 -0
- rem/api/main.py +460 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +820 -0
- rem/api/mcp_router/server.py +243 -0
- rem/api/mcp_router/tools.py +1605 -0
- rem/api/middleware/tracking.py +172 -0
- rem/api/routers/admin.py +520 -0
- rem/api/routers/auth.py +898 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/child_streaming.py +394 -0
- rem/api/routers/chat/completions.py +702 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +202 -0
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +546 -0
- rem/api/routers/chat/streaming.py +950 -0
- rem/api/routers/chat/streaming_utils.py +327 -0
- rem/api/routers/common.py +18 -0
- rem/api/routers/dev.py +87 -0
- rem/api/routers/feedback.py +276 -0
- rem/api/routers/messages.py +620 -0
- rem/api/routers/models.py +86 -0
- rem/api/routers/query.py +362 -0
- rem/api/routers/shared_sessions.py +422 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +36 -0
- rem/auth/jwt.py +367 -0
- rem/auth/middleware.py +318 -0
- rem/auth/providers/__init__.py +16 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/email.py +215 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +517 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +299 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +549 -0
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +495 -0
- rem/cli/commands/db.py +828 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1698 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +388 -0
- rem/cli/commands/query.py +109 -0
- rem/cli/commands/scaffold.py +47 -0
- rem/cli/commands/schema.py +230 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/commands/session.py +453 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +123 -0
- rem/config.py +244 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +70 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +672 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +246 -0
- rem/models/entities/__init__.py +68 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +64 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +181 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/session.py +84 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/subscriber.py +175 -0
- rem/models/entities/user.py +93 -0
- rem/py.typed +0 -0
- rem/registry.py +373 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/agent-builder.yaml +235 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +132 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +18 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +760 -0
- rem/services/content/service.py +762 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +322 -0
- rem/services/dreaming/moment_service.py +251 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/email/__init__.py +10 -0
- rem/services/email/service.py +522 -0
- rem/services/email/templates.py +360 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +127 -0
- rem/services/embeddings/worker.py +435 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +960 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +757 -0
- rem/services/postgres/__init__.py +49 -0
- rem/services/postgres/diff_service.py +599 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/programmable_diff_service.py +635 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
- rem/services/postgres/register_type.py +353 -0
- rem/services/postgres/repository.py +481 -0
- rem/services/postgres/schema_generator.py +661 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +355 -0
- rem/services/rate_limit.py +113 -0
- rem/services/rem/README.md +318 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +180 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +608 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +13 -0
- rem/services/session/compression.py +488 -0
- rem/services/session/pydantic_messages.py +310 -0
- rem/services/session/reload.py +85 -0
- rem/services/user_service.py +130 -0
- rem/settings.py +1877 -0
- rem/sql/background_indexes.sql +52 -0
- rem/sql/migrations/001_install.sql +983 -0
- rem/sql/migrations/002_install_models.sql +3157 -0
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +282 -0
- rem/sql/migrations/005_schema_update.sql +145 -0
- rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +628 -0
- rem/utils/__init__.py +61 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/constants.py +97 -0
- rem/utils/date_utils.py +228 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +436 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/files.py +323 -0
- rem/utils/markdown.py +16 -0
- rem/utils/mime_types.py +158 -0
- rem/utils/model_helpers.py +492 -0
- rem/utils/schema_loader.py +649 -0
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +350 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +325 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +7 -0
- rem/workers/db_listener.py +579 -0
- rem/workers/db_maintainer.py +74 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- rem/workers/unlogged_maintainer.py +463 -0
- remdb-0.3.242.dist-info/METADATA +1632 -0
- remdb-0.3.242.dist-info/RECORD +235 -0
- remdb-0.3.242.dist-info/WHEEL +4 -0
- remdb-0.3.242.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Schema - Agent schema definitions in REM.
|
|
3
|
+
|
|
4
|
+
Schemas represent agent definitions that can be loaded into Pydantic AI.
|
|
5
|
+
They store JsonSchema specifications that define agent capabilities, tools,
|
|
6
|
+
and output structures.
|
|
7
|
+
|
|
8
|
+
Schemas are used for:
|
|
9
|
+
- Agent definition storage and versioning
|
|
10
|
+
- Dynamic agent loading via X-Agent-Schema header
|
|
11
|
+
- Agent registry and discovery
|
|
12
|
+
- Schema validation and documentation
|
|
13
|
+
- Ontology extraction configuration
|
|
14
|
+
|
|
15
|
+
Key Fields:
|
|
16
|
+
- name: Human-readable schema identifier
|
|
17
|
+
- content: Markdown documentation and instructions
|
|
18
|
+
- spec: JsonSchema specification (Pydantic model definition)
|
|
19
|
+
- category: Schema classification (agent-type, workflow, ontology-extractor, etc.)
|
|
20
|
+
- provider_configs: Optional LLM provider configurations (for multi-provider support)
|
|
21
|
+
- embedding_fields: Fields in extracted_data that should be embedded for semantic search
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
from pydantic import Field
|
|
27
|
+
|
|
28
|
+
from ..core import CoreModel
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Schema(CoreModel):
|
|
32
|
+
"""
|
|
33
|
+
Agent schema definition.
|
|
34
|
+
|
|
35
|
+
Schemas define agents that can be dynamically loaded into Pydantic AI.
|
|
36
|
+
They store JsonSchema specifications with embedded metadata for tools,
|
|
37
|
+
resources, and system prompts.
|
|
38
|
+
|
|
39
|
+
For ontology extraction agents:
|
|
40
|
+
- `provider_configs` enables multi-provider support (test across Anthropic, OpenAI, etc.)
|
|
41
|
+
- `embedding_fields` specifies which output fields should be embedded for semantic search
|
|
42
|
+
|
|
43
|
+
Tenant isolation is provided via CoreModel.tenant_id field.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
name: str = Field(
|
|
47
|
+
...,
|
|
48
|
+
description="Human-readable schema name (used as identifier)",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
content: str = Field(
|
|
52
|
+
default="",
|
|
53
|
+
description="Markdown documentation and instructions for the schema",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
spec: dict = Field(
|
|
57
|
+
...,
|
|
58
|
+
description="JsonSchema specification defining the agent structure and capabilities",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
category: Optional[str] = Field(
|
|
62
|
+
default=None,
|
|
63
|
+
description=(
|
|
64
|
+
"Schema category distinguishing schema types. "
|
|
65
|
+
"Values: 'agent' (AI agents), 'evaluator' (LLM-as-a-Judge evaluators). "
|
|
66
|
+
"Maps directly from json_schema_extra.kind field during ingestion."
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Ontology extraction support
|
|
71
|
+
provider_configs: list[dict] = Field(
|
|
72
|
+
default_factory=list,
|
|
73
|
+
description=(
|
|
74
|
+
"Optional provider configurations for multi-provider testing. "
|
|
75
|
+
"Each dict has 'provider_name' and 'model_name'. "
|
|
76
|
+
"Example: [{'provider_name': 'anthropic', 'model_name': 'claude-sonnet-4-5'}]"
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
embedding_fields: list[str] = Field(
|
|
81
|
+
default_factory=list,
|
|
82
|
+
description=(
|
|
83
|
+
"JSON paths in extracted_data to embed for semantic search. "
|
|
84
|
+
"Example: ['summary', 'candidate_name', 'skills'] for CV extraction. "
|
|
85
|
+
"Values will be concatenated and embedded using configured embedding provider."
|
|
86
|
+
),
|
|
87
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session - Conversation sessions in REM.
|
|
3
|
+
|
|
4
|
+
Sessions group related messages together and can have different modes:
|
|
5
|
+
- normal: Standard conversation session
|
|
6
|
+
- evaluation: For LLM evaluation, stores original trace and overridden settings
|
|
7
|
+
|
|
8
|
+
Sessions allow overriding settings like model, temperature, and custom prompts
|
|
9
|
+
for evaluation and experimentation purposes.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
|
|
16
|
+
from ..core import CoreModel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SessionMode(str, Enum):
|
|
20
|
+
"""Session mode types."""
|
|
21
|
+
|
|
22
|
+
NORMAL = "normal"
|
|
23
|
+
EVALUATION = "evaluation"
|
|
24
|
+
CLONE = "clone"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Session(CoreModel):
|
|
28
|
+
"""
|
|
29
|
+
Conversation session container.
|
|
30
|
+
|
|
31
|
+
Groups messages together and supports different modes for normal conversations
|
|
32
|
+
and evaluation/experimentation scenarios.
|
|
33
|
+
|
|
34
|
+
For evaluation sessions, stores:
|
|
35
|
+
- original_trace_id: Reference to the original session being evaluated
|
|
36
|
+
- settings_overrides: Model, temperature, prompt overrides
|
|
37
|
+
- prompt: Custom prompt being tested
|
|
38
|
+
|
|
39
|
+
Default sessions are lightweight - just a session_id on messages.
|
|
40
|
+
Special sessions store additional metadata for experiments.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
name: str = Field(
|
|
44
|
+
...,
|
|
45
|
+
description="Session name/identifier",
|
|
46
|
+
json_schema_extra={"entity_key": True},
|
|
47
|
+
)
|
|
48
|
+
mode: SessionMode = Field(
|
|
49
|
+
default=SessionMode.NORMAL,
|
|
50
|
+
description="Session mode: 'normal' or 'evaluation'",
|
|
51
|
+
)
|
|
52
|
+
description: str | None = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
description="Optional session description",
|
|
55
|
+
)
|
|
56
|
+
# Evaluation-specific fields
|
|
57
|
+
original_trace_id: str | None = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="For evaluation mode: ID of the original session/trace being evaluated",
|
|
60
|
+
)
|
|
61
|
+
settings_overrides: dict | None = Field(
|
|
62
|
+
default=None,
|
|
63
|
+
description="Settings overrides (model, temperature, max_tokens, system_prompt)",
|
|
64
|
+
)
|
|
65
|
+
prompt: str | None = Field(
|
|
66
|
+
default=None,
|
|
67
|
+
description="Custom prompt for this session (can override agent prompt)",
|
|
68
|
+
)
|
|
69
|
+
# Agent context
|
|
70
|
+
agent_schema_uri: str | None = Field(
|
|
71
|
+
default=None,
|
|
72
|
+
description="Agent schema used for this session",
|
|
73
|
+
)
|
|
74
|
+
# Summary stats (updated as session progresses)
|
|
75
|
+
message_count: int = Field(
|
|
76
|
+
default=0,
|
|
77
|
+
description="Number of messages in this session",
|
|
78
|
+
)
|
|
79
|
+
total_tokens: int | None = Field(
|
|
80
|
+
default=None,
|
|
81
|
+
description="Total tokens used in this session",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
model_config = {"use_enum_values": True}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SharedSession - Session sharing between users in REM.
|
|
3
|
+
|
|
4
|
+
SharedSessions enable collaborative access to conversation sessions. When a user
|
|
5
|
+
shares a session with another user, a SharedSession record is created to track
|
|
6
|
+
this relationship.
|
|
7
|
+
|
|
8
|
+
## Design Philosophy
|
|
9
|
+
|
|
10
|
+
Messages already have a session_id field that links them to sessions. The Session
|
|
11
|
+
entity itself is optional and can be left-joined - we don't require explicit Session
|
|
12
|
+
records for sharing to work. What matters is the session_id on messages.
|
|
13
|
+
|
|
14
|
+
SharedSession is a lightweight linking table that:
|
|
15
|
+
1. Records who shared which session with whom
|
|
16
|
+
2. Enables soft deletion (deleted_at) so shares can be revoked without data loss
|
|
17
|
+
3. Supports aggregation queries to see "who is sharing with me"
|
|
18
|
+
|
|
19
|
+
## Data Model
|
|
20
|
+
|
|
21
|
+
SharedSession
|
|
22
|
+
├── session_id: str # The session being shared (matches Message.session_id)
|
|
23
|
+
├── owner_user_id: str # Who owns/created the session (the sharer)
|
|
24
|
+
├── shared_with_user_id: str # Who the session is shared with (the recipient)
|
|
25
|
+
├── tenant_id: str # Multi-tenancy isolation
|
|
26
|
+
├── created_at: datetime # When the share was created
|
|
27
|
+
├── updated_at: datetime # Last modification
|
|
28
|
+
└── deleted_at: datetime # Soft delete (null = active share)
|
|
29
|
+
|
|
30
|
+
## Aggregation Query
|
|
31
|
+
|
|
32
|
+
The primary use case is answering: "Who is sharing messages with me?"
|
|
33
|
+
|
|
34
|
+
This is provided by a Postgres function that aggregates:
|
|
35
|
+
- Messages grouped by owner_user_id
|
|
36
|
+
- Joined with users table for name/email
|
|
37
|
+
- Counting messages with min/max dates
|
|
38
|
+
- Filtering out deleted shares
|
|
39
|
+
|
|
40
|
+
Result shape:
|
|
41
|
+
{
|
|
42
|
+
"user_id": "uuid",
|
|
43
|
+
"name": "John Doe",
|
|
44
|
+
"email": "john@example.com",
|
|
45
|
+
"message_count": 42,
|
|
46
|
+
"first_message_at": "2024-01-15T10:30:00Z",
|
|
47
|
+
"last_message_at": "2024-03-20T14:45:00Z",
|
|
48
|
+
"session_count": 3
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
## API Endpoints
|
|
52
|
+
|
|
53
|
+
1. POST /api/v1/sessions/{session_id}/share
|
|
54
|
+
- Share a session with another user
|
|
55
|
+
- Body: { "shared_with_user_id": "..." }
|
|
56
|
+
- Creates SharedSession record
|
|
57
|
+
|
|
58
|
+
2. DELETE /api/v1/sessions/{session_id}/share/{shared_with_user_id}
|
|
59
|
+
- Revoke a share (soft delete)
|
|
60
|
+
- Sets deleted_at on SharedSession
|
|
61
|
+
|
|
62
|
+
3. GET /api/v1/shared-with-me
|
|
63
|
+
- Get paginated aggregate of users sharing with you
|
|
64
|
+
- Query params: page, page_size (default 50)
|
|
65
|
+
- Returns: list of user summaries with message counts
|
|
66
|
+
|
|
67
|
+
4. GET /api/v1/shared-with-me/{user_id}/messages
|
|
68
|
+
- Get messages from a specific user's shared sessions
|
|
69
|
+
- Uses existing session message loading
|
|
70
|
+
- Respects pagination
|
|
71
|
+
|
|
72
|
+
## Soft Delete Pattern
|
|
73
|
+
|
|
74
|
+
Removing a share does NOT delete the SharedSession record. Instead:
|
|
75
|
+
- deleted_at is set to current timestamp
|
|
76
|
+
- All queries filter WHERE deleted_at IS NULL
|
|
77
|
+
- This preserves audit trail and allows "undo"
|
|
78
|
+
|
|
79
|
+
To permanently delete, an admin can run:
|
|
80
|
+
DELETE FROM shared_sessions WHERE deleted_at IS NOT NULL AND deleted_at < NOW() - INTERVAL '30 days'
|
|
81
|
+
|
|
82
|
+
## Example Usage
|
|
83
|
+
|
|
84
|
+
# Share a session
|
|
85
|
+
POST /api/v1/sessions/abc-123/share
|
|
86
|
+
{"shared_with_user_id": "user-456"}
|
|
87
|
+
|
|
88
|
+
# See who's sharing with me
|
|
89
|
+
GET /api/v1/shared-with-me
|
|
90
|
+
{
|
|
91
|
+
"data": [
|
|
92
|
+
{
|
|
93
|
+
"user_id": "user-789",
|
|
94
|
+
"name": "Alice",
|
|
95
|
+
"email": "alice@example.com",
|
|
96
|
+
"message_count": 150,
|
|
97
|
+
"session_count": 5,
|
|
98
|
+
"first_message_at": "2024-01-01T00:00:00Z",
|
|
99
|
+
"last_message_at": "2024-03-15T12:00:00Z"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"metadata": {"total": 1, "page": 1, "page_size": 50, ...}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Get messages from Alice's shared sessions
|
|
106
|
+
GET /api/v1/shared-with-me/user-789/messages?page=1&page_size=50
|
|
107
|
+
|
|
108
|
+
# Revoke a share
|
|
109
|
+
DELETE /api/v1/sessions/abc-123/share/user-456
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
from datetime import datetime
|
|
113
|
+
from typing import Optional
|
|
114
|
+
|
|
115
|
+
from pydantic import BaseModel, Field
|
|
116
|
+
|
|
117
|
+
from ..core import CoreModel
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class SharedSession(CoreModel):
|
|
121
|
+
"""
|
|
122
|
+
Session sharing record between users.
|
|
123
|
+
|
|
124
|
+
Links a session (identified by session_id from Message records) to a
|
|
125
|
+
recipient user, enabling collaborative access to conversation history.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
session_id: str = Field(
|
|
129
|
+
...,
|
|
130
|
+
description="The session being shared (matches Message.session_id)",
|
|
131
|
+
)
|
|
132
|
+
owner_user_id: str = Field(
|
|
133
|
+
...,
|
|
134
|
+
description="User ID of the session owner (the sharer)",
|
|
135
|
+
)
|
|
136
|
+
shared_with_user_id: str = Field(
|
|
137
|
+
...,
|
|
138
|
+
description="User ID of the recipient (who can now view the session)",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class SharedSessionCreate(BaseModel):
|
|
143
|
+
"""Request to create a session share."""
|
|
144
|
+
|
|
145
|
+
shared_with_user_id: str = Field(
|
|
146
|
+
...,
|
|
147
|
+
description="User ID to share the session with",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SharedWithMeSummary(BaseModel):
|
|
152
|
+
"""
|
|
153
|
+
Aggregate summary of a user sharing sessions with you.
|
|
154
|
+
|
|
155
|
+
Returned by GET /api/v1/shared-with-me endpoint.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
user_id: str = Field(description="User ID of the person sharing with you")
|
|
159
|
+
name: Optional[str] = Field(default=None, description="User's display name")
|
|
160
|
+
email: Optional[str] = Field(default=None, description="User's email address")
|
|
161
|
+
message_count: int = Field(description="Total messages across all shared sessions")
|
|
162
|
+
session_count: int = Field(description="Number of sessions shared with you")
|
|
163
|
+
first_message_at: Optional[datetime] = Field(
|
|
164
|
+
default=None,
|
|
165
|
+
description="Timestamp of earliest message in shared sessions",
|
|
166
|
+
)
|
|
167
|
+
last_message_at: Optional[datetime] = Field(
|
|
168
|
+
default=None,
|
|
169
|
+
description="Timestamp of most recent message in shared sessions",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class SharedWithMeResponse(BaseModel):
|
|
174
|
+
"""Response for paginated shared-with-me query."""
|
|
175
|
+
|
|
176
|
+
object: str = "list"
|
|
177
|
+
data: list[SharedWithMeSummary] = Field(
|
|
178
|
+
description="List of users sharing sessions with you"
|
|
179
|
+
)
|
|
180
|
+
metadata: dict = Field(description="Pagination metadata")
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Subscriber - Email subscription management.
|
|
3
|
+
|
|
4
|
+
This model stores subscribers who sign up via websites/apps.
|
|
5
|
+
Subscribers can be collected before user registration for newsletters,
|
|
6
|
+
updates, and approval-based access control.
|
|
7
|
+
|
|
8
|
+
Key features:
|
|
9
|
+
- Deterministic UUID from email (same email = same ID)
|
|
10
|
+
- Approval workflow for access control
|
|
11
|
+
- Tags for segmentation
|
|
12
|
+
- Origin tracking for analytics
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import uuid
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from pydantic import Field, EmailStr, model_validator
|
|
21
|
+
|
|
22
|
+
from ..core import CoreModel
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SubscriberStatus(str, Enum):
|
|
26
|
+
"""Subscription status."""
|
|
27
|
+
|
|
28
|
+
ACTIVE = "active" # Actively subscribed
|
|
29
|
+
UNSUBSCRIBED = "unsubscribed" # User unsubscribed
|
|
30
|
+
BOUNCED = "bounced" # Email bounced
|
|
31
|
+
PENDING = "pending" # Pending confirmation (if double opt-in)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SubscriberOrigin(str, Enum):
|
|
35
|
+
"""Where the subscription originated from."""
|
|
36
|
+
|
|
37
|
+
WEBSITE = "website" # Main website subscribe form
|
|
38
|
+
LANDING_PAGE = "landing_page" # Campaign landing page
|
|
39
|
+
APP = "app" # In-app subscription
|
|
40
|
+
IMPORT = "import" # Bulk import
|
|
41
|
+
REFERRAL = "referral" # Referred by another user
|
|
42
|
+
OTHER = "other"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Subscriber(CoreModel):
|
|
46
|
+
"""
|
|
47
|
+
Email subscriber for newsletters and access control.
|
|
48
|
+
|
|
49
|
+
This model captures subscribers who sign up via the website, landing pages,
|
|
50
|
+
or in-app prompts. Uses deterministic UUID from email for natural upserts.
|
|
51
|
+
|
|
52
|
+
Access control via `approved` field:
|
|
53
|
+
- When email auth checks subscriber status, only approved subscribers
|
|
54
|
+
can complete login (if approval is enabled in settings).
|
|
55
|
+
- Subscribers can be pre-approved, or approved manually/automatically.
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
from rem.services.postgres import Repository
|
|
59
|
+
from rem.models.entities import Subscriber, SubscriberStatus
|
|
60
|
+
|
|
61
|
+
repo = Repository(Subscriber, db=db)
|
|
62
|
+
|
|
63
|
+
# Create subscriber (ID auto-generated from email)
|
|
64
|
+
subscriber = Subscriber(
|
|
65
|
+
email="user@example.com",
|
|
66
|
+
name="John Doe",
|
|
67
|
+
origin=SubscriberOrigin.WEBSITE,
|
|
68
|
+
)
|
|
69
|
+
await repo.upsert(subscriber)
|
|
70
|
+
|
|
71
|
+
# Check if approved for login
|
|
72
|
+
subscriber = await repo.get_by_id(subscriber.id, tenant_id="default")
|
|
73
|
+
if subscriber and subscriber.approved:
|
|
74
|
+
# Allow login
|
|
75
|
+
pass
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# Required field
|
|
79
|
+
email: EmailStr = Field(
|
|
80
|
+
description="Subscriber's email address (unique identifier)"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Optional fields
|
|
84
|
+
name: Optional[str] = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="Subscriber's name (optional)"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
comment: Optional[str] = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
max_length=500,
|
|
92
|
+
description="Optional comment or message from subscriber"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
status: SubscriberStatus = Field(
|
|
96
|
+
default=SubscriberStatus.ACTIVE,
|
|
97
|
+
description="Current subscription status"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Access control
|
|
101
|
+
approved: bool = Field(
|
|
102
|
+
default=False,
|
|
103
|
+
description="Whether subscriber is approved for login (for approval workflows)"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
approved_at: Optional[datetime] = Field(
|
|
107
|
+
default=None,
|
|
108
|
+
description="When the subscriber was approved"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
approved_by: Optional[str] = Field(
|
|
112
|
+
default=None,
|
|
113
|
+
description="Who approved the subscriber (user ID or 'system')"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Origin tracking
|
|
117
|
+
origin: SubscriberOrigin = Field(
|
|
118
|
+
default=SubscriberOrigin.WEBSITE,
|
|
119
|
+
description="Where the subscription originated"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
origin_detail: Optional[str] = Field(
|
|
123
|
+
default=None,
|
|
124
|
+
description="Additional origin context (e.g., campaign name, page URL)"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Timestamps
|
|
128
|
+
subscribed_at: datetime = Field(
|
|
129
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
130
|
+
description="When the subscription was created"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
unsubscribed_at: Optional[datetime] = Field(
|
|
134
|
+
default=None,
|
|
135
|
+
description="When the user unsubscribed (if applicable)"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Compliance
|
|
139
|
+
ip_address: Optional[str] = Field(
|
|
140
|
+
default=None,
|
|
141
|
+
description="IP address at subscription time (for compliance)"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
user_agent: Optional[str] = Field(
|
|
145
|
+
default=None,
|
|
146
|
+
description="Browser user agent at subscription time"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Segmentation
|
|
150
|
+
tags: list[str] = Field(
|
|
151
|
+
default_factory=list,
|
|
152
|
+
description="Tags for segmentation (e.g., ['early-access', 'beta'])"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def email_to_uuid(email: str) -> uuid.UUID:
|
|
157
|
+
"""Generate a deterministic UUID from an email address.
|
|
158
|
+
|
|
159
|
+
Uses UUID v5 with DNS namespace for consistency with
|
|
160
|
+
EmailService.generate_user_id_from_email().
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
email: Email address
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Deterministic UUID
|
|
167
|
+
"""
|
|
168
|
+
return uuid.uuid5(uuid.NAMESPACE_DNS, email.lower().strip())
|
|
169
|
+
|
|
170
|
+
@model_validator(mode="after")
|
|
171
|
+
def set_id_from_email(self) -> "Subscriber":
|
|
172
|
+
"""Auto-generate deterministic ID from email for natural upsert."""
|
|
173
|
+
if self.email:
|
|
174
|
+
self.id = self.email_to_uuid(self.email)
|
|
175
|
+
return self
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User - User entity in REM.
|
|
3
|
+
|
|
4
|
+
Users represent people in the system, either as content creators,
|
|
5
|
+
participants in moments, or entities referenced in resources.
|
|
6
|
+
|
|
7
|
+
Users can be discovered through:
|
|
8
|
+
- Entity extraction from resources
|
|
9
|
+
- Moment present_persons lists
|
|
10
|
+
- Direct user registration
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import Field
|
|
18
|
+
|
|
19
|
+
from ..core import CoreModel
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UserTier(str, Enum):
|
|
23
|
+
"""User subscription tier for feature gating."""
|
|
24
|
+
|
|
25
|
+
BLOCKED = "blocked" # User is blocked from logging in
|
|
26
|
+
ANONYMOUS = "anonymous"
|
|
27
|
+
FREE = "free"
|
|
28
|
+
BASIC = "basic"
|
|
29
|
+
PRO = "pro"
|
|
30
|
+
SILVER = "silver" # Deprecated? Keeping for backward compatibility if needed
|
|
31
|
+
GOLD = "gold" # Deprecated? Keeping for backward compatibility if needed
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class User(CoreModel):
|
|
35
|
+
"""
|
|
36
|
+
User entity.
|
|
37
|
+
|
|
38
|
+
Represents people in the REM system, either as active users
|
|
39
|
+
or entities extracted from content. Tenant isolation is provided
|
|
40
|
+
via CoreModel.tenant_id field.
|
|
41
|
+
|
|
42
|
+
Enhanced by dreaming worker:
|
|
43
|
+
- summary: Generated from activity analysis
|
|
44
|
+
- interests: Extracted from resources and sessions
|
|
45
|
+
- activity_level: Computed from recent engagement
|
|
46
|
+
- preferred_topics: Extracted from moment/resource topics
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
name: str = Field(
|
|
50
|
+
...,
|
|
51
|
+
description="User name (human-readable, used as graph label)",
|
|
52
|
+
json_schema_extra={"entity_key": True}, # Primary business key for KV lookups
|
|
53
|
+
)
|
|
54
|
+
email: Optional[str] = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
description="User email address",
|
|
57
|
+
)
|
|
58
|
+
role: Optional[str] = Field(
|
|
59
|
+
default=None,
|
|
60
|
+
description="User role (employee, contractor, external, etc.)",
|
|
61
|
+
)
|
|
62
|
+
tier: UserTier = Field(
|
|
63
|
+
default=UserTier.FREE,
|
|
64
|
+
description="User subscription tier (free, basic, pro) for feature gating",
|
|
65
|
+
)
|
|
66
|
+
anonymous_ids: list[str] = Field(
|
|
67
|
+
default_factory=list,
|
|
68
|
+
description="Linked anonymous session IDs used for merging history",
|
|
69
|
+
)
|
|
70
|
+
sec_policy: dict = Field(
|
|
71
|
+
default_factory=dict,
|
|
72
|
+
description="Security policy configuration (JSON, extensible for custom policies)",
|
|
73
|
+
)
|
|
74
|
+
summary: Optional[str] = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description="LLM-generated user profile summary (updated by dreaming worker)",
|
|
77
|
+
)
|
|
78
|
+
interests: list[str] = Field(
|
|
79
|
+
default_factory=list,
|
|
80
|
+
description="User interests extracted from activity",
|
|
81
|
+
)
|
|
82
|
+
preferred_topics: list[str] = Field(
|
|
83
|
+
default_factory=list,
|
|
84
|
+
description="Frequently discussed topics in kebab-case",
|
|
85
|
+
)
|
|
86
|
+
activity_level: Optional[str] = Field(
|
|
87
|
+
default=None,
|
|
88
|
+
description="Activity level: active, moderate, inactive",
|
|
89
|
+
)
|
|
90
|
+
last_active_at: Optional[datetime] = Field(
|
|
91
|
+
default=None,
|
|
92
|
+
description="Last activity timestamp",
|
|
93
|
+
)
|
rem/py.typed
ADDED
|
File without changes
|