remdb 0.3.172__py3-none-any.whl → 0.3.223__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/agentic/README.md +262 -2
- rem/agentic/context.py +173 -0
- rem/agentic/context_builder.py +12 -2
- rem/agentic/mcp/tool_wrapper.py +39 -16
- rem/agentic/providers/pydantic_ai.py +46 -43
- rem/agentic/schema.py +2 -2
- rem/agentic/tools/rem_tools.py +11 -0
- rem/api/main.py +1 -1
- rem/api/mcp_router/resources.py +64 -8
- rem/api/mcp_router/server.py +31 -24
- rem/api/mcp_router/tools.py +621 -166
- rem/api/routers/admin.py +30 -4
- rem/api/routers/auth.py +114 -15
- rem/api/routers/chat/completions.py +66 -18
- rem/api/routers/chat/sse_events.py +7 -3
- rem/api/routers/chat/streaming.py +254 -22
- rem/api/routers/common.py +18 -0
- rem/api/routers/dev.py +7 -1
- rem/api/routers/feedback.py +9 -1
- rem/api/routers/messages.py +176 -38
- rem/api/routers/models.py +9 -1
- rem/api/routers/query.py +12 -1
- rem/api/routers/shared_sessions.py +16 -0
- rem/auth/jwt.py +19 -4
- rem/auth/middleware.py +42 -28
- rem/cli/README.md +62 -0
- rem/cli/commands/ask.py +1 -1
- rem/cli/commands/db.py +148 -70
- rem/cli/commands/process.py +171 -43
- rem/models/entities/ontology.py +91 -101
- rem/schemas/agents/rem.yaml +1 -1
- rem/services/content/service.py +18 -5
- rem/services/email/service.py +11 -2
- rem/services/embeddings/worker.py +26 -12
- rem/services/postgres/__init__.py +28 -3
- rem/services/postgres/diff_service.py +57 -5
- rem/services/postgres/programmable_diff_service.py +635 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +2 -2
- rem/services/postgres/register_type.py +12 -11
- rem/services/postgres/repository.py +46 -25
- rem/services/postgres/schema_generator.py +5 -5
- rem/services/postgres/sql_builder.py +6 -5
- rem/services/session/__init__.py +8 -1
- rem/services/session/compression.py +40 -2
- rem/services/session/pydantic_messages.py +276 -0
- rem/settings.py +28 -0
- rem/sql/background_indexes.sql +5 -0
- rem/sql/migrations/001_install.sql +157 -10
- rem/sql/migrations/002_install_models.sql +160 -132
- rem/sql/migrations/004_cache_system.sql +7 -275
- rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
- rem/utils/model_helpers.py +101 -0
- rem/utils/schema_loader.py +6 -6
- {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/METADATA +1 -1
- {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/RECORD +57 -53
- {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/WHEEL +0 -0
- {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/entry_points.txt +0 -0
|
@@ -44,6 +44,33 @@ BEGIN
|
|
|
44
44
|
RAISE NOTICE '✓ All required extensions installed successfully';
|
|
45
45
|
END $$;
|
|
46
46
|
|
|
47
|
+
-- ============================================================================
|
|
48
|
+
-- NORMALIZATION HELPER
|
|
49
|
+
-- ============================================================================
|
|
50
|
+
|
|
51
|
+
-- Normalize entity keys to lower-kebab-case for consistent lookups
|
|
52
|
+
-- "Mood Disorder" -> "mood-disorder"
|
|
53
|
+
-- "mood_disorder" -> "mood-disorder"
|
|
54
|
+
-- "MoodDisorder" -> "mood-disorder"
|
|
55
|
+
CREATE OR REPLACE FUNCTION normalize_key(input TEXT)
|
|
56
|
+
RETURNS TEXT AS $$
|
|
57
|
+
BEGIN
|
|
58
|
+
RETURN lower(
|
|
59
|
+
regexp_replace(
|
|
60
|
+
regexp_replace(
|
|
61
|
+
regexp_replace(input, '([a-z])([A-Z])', '\1-\2', 'g'), -- camelCase -> kebab
|
|
62
|
+
'[_\s]+', '-', 'g' -- underscores/spaces -> hyphens
|
|
63
|
+
),
|
|
64
|
+
'-+', '-', 'g' -- collapse multiple hyphens
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
END;
|
|
68
|
+
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
69
|
+
|
|
70
|
+
COMMENT ON FUNCTION normalize_key IS
|
|
71
|
+
'Normalizes entity keys to lower-kebab-case for consistent lookups.
|
|
72
|
+
Examples: "Mood Disorder" -> "mood-disorder", "mood_disorder" -> "mood-disorder"';
|
|
73
|
+
|
|
47
74
|
-- ============================================================================
|
|
48
75
|
-- MIGRATION TRACKING
|
|
49
76
|
-- ============================================================================
|
|
@@ -94,18 +121,18 @@ CREATE UNLOGGED TABLE IF NOT EXISTS kv_store (
|
|
|
94
121
|
entity_key VARCHAR(255) NOT NULL,
|
|
95
122
|
entity_type VARCHAR(100) NOT NULL,
|
|
96
123
|
entity_id UUID NOT NULL,
|
|
97
|
-
tenant_id VARCHAR(100)
|
|
124
|
+
tenant_id VARCHAR(100), -- NULL = public/shared data
|
|
98
125
|
user_id VARCHAR(100),
|
|
99
126
|
content_summary TEXT,
|
|
100
127
|
metadata JSONB DEFAULT '{}',
|
|
101
128
|
graph_edges JSONB DEFAULT '[]'::jsonb, -- Cached edges for fast graph traversal
|
|
102
129
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
103
|
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
104
|
-
|
|
105
|
-
-- Composite primary key: entity_key unique per tenant
|
|
106
|
-
PRIMARY KEY (tenant_id, entity_key)
|
|
130
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
107
131
|
);
|
|
108
132
|
|
|
133
|
+
-- Unique constraint on (tenant_id, entity_key) using COALESCE to handle NULL tenant_id
|
|
134
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_kv_store_tenant_key ON kv_store (COALESCE(tenant_id, ''), entity_key);
|
|
135
|
+
|
|
109
136
|
-- Index for user-scoped lookups (when user_id IS NOT NULL)
|
|
110
137
|
CREATE INDEX IF NOT EXISTS idx_kv_store_user ON kv_store (tenant_id, user_id)
|
|
111
138
|
WHERE user_id IS NOT NULL;
|
|
@@ -146,7 +173,7 @@ COMMENT ON COLUMN kv_store.entity_id IS
|
|
|
146
173
|
'UUID from primary table for reverse lookup';
|
|
147
174
|
|
|
148
175
|
COMMENT ON COLUMN kv_store.tenant_id IS
|
|
149
|
-
'Tenant identifier for multi-tenancy isolation';
|
|
176
|
+
'Tenant identifier for multi-tenancy isolation. NULL = public/shared data visible to all.';
|
|
150
177
|
|
|
151
178
|
COMMENT ON COLUMN kv_store.user_id IS
|
|
152
179
|
'Optional user scoping. NULL = system-level entity, visible to all users in tenant';
|
|
@@ -237,14 +264,19 @@ BEGIN
|
|
|
237
264
|
|
|
238
265
|
-- First lookup in KV store to get entity_type (table name)
|
|
239
266
|
-- Include user-owned AND public (NULL user_id) entries
|
|
267
|
+
-- Normalize input key for consistent matching
|
|
240
268
|
SELECT kv.entity_type INTO entity_table
|
|
241
269
|
FROM kv_store kv
|
|
242
270
|
WHERE (kv.user_id = effective_user_id OR kv.user_id IS NULL)
|
|
243
|
-
AND kv.entity_key = p_entity_key
|
|
271
|
+
AND kv.entity_key = normalize_key(p_entity_key)
|
|
244
272
|
LIMIT 1;
|
|
245
273
|
|
|
246
|
-
-- If not found,
|
|
274
|
+
-- If not found, check if cache is empty and maybe trigger rebuild
|
|
247
275
|
IF entity_table IS NULL THEN
|
|
276
|
+
-- SELF-HEALING: Check if this is because cache is empty
|
|
277
|
+
IF rem_kv_store_empty(effective_user_id) THEN
|
|
278
|
+
PERFORM maybe_trigger_kv_rebuild(effective_user_id, 'rem_lookup');
|
|
279
|
+
END IF;
|
|
248
280
|
RETURN;
|
|
249
281
|
END IF;
|
|
250
282
|
|
|
@@ -329,6 +361,7 @@ DECLARE
|
|
|
329
361
|
entities_by_table JSONB := '{}'::jsonb;
|
|
330
362
|
table_keys JSONB;
|
|
331
363
|
effective_user_id VARCHAR(100);
|
|
364
|
+
v_found_any BOOLEAN := FALSE;
|
|
332
365
|
BEGIN
|
|
333
366
|
effective_user_id := COALESCE(p_user_id, p_tenant_id);
|
|
334
367
|
|
|
@@ -345,6 +378,7 @@ BEGIN
|
|
|
345
378
|
ORDER BY sim_score DESC
|
|
346
379
|
LIMIT p_limit
|
|
347
380
|
LOOP
|
|
381
|
+
v_found_any := TRUE;
|
|
348
382
|
-- Build JSONB mapping {table: [keys]}
|
|
349
383
|
IF entities_by_table ? kv_matches.entity_type THEN
|
|
350
384
|
table_keys := entities_by_table->kv_matches.entity_type;
|
|
@@ -362,6 +396,11 @@ BEGIN
|
|
|
362
396
|
END IF;
|
|
363
397
|
END LOOP;
|
|
364
398
|
|
|
399
|
+
-- SELF-HEALING: If no matches and cache is empty, trigger rebuild
|
|
400
|
+
IF NOT v_found_any AND rem_kv_store_empty(effective_user_id) THEN
|
|
401
|
+
PERFORM maybe_trigger_kv_rebuild(effective_user_id, 'rem_fuzzy');
|
|
402
|
+
END IF;
|
|
403
|
+
|
|
365
404
|
-- Fetch full records using rem_fetch (which now supports NULL user_id)
|
|
366
405
|
RETURN QUERY
|
|
367
406
|
SELECT
|
|
@@ -408,12 +447,29 @@ DECLARE
|
|
|
408
447
|
entities_by_table JSONB := '{}'::jsonb;
|
|
409
448
|
table_keys JSONB;
|
|
410
449
|
effective_user_id VARCHAR(100);
|
|
450
|
+
v_found_start BOOLEAN := FALSE;
|
|
411
451
|
BEGIN
|
|
412
452
|
effective_user_id := COALESCE(p_user_id, p_tenant_id);
|
|
413
453
|
|
|
454
|
+
-- Check if start entity exists in kv_store
|
|
455
|
+
SELECT TRUE INTO v_found_start
|
|
456
|
+
FROM kv_store kv
|
|
457
|
+
WHERE (kv.user_id = effective_user_id OR kv.user_id IS NULL)
|
|
458
|
+
AND kv.entity_key = normalize_key(p_entity_key)
|
|
459
|
+
LIMIT 1;
|
|
460
|
+
|
|
461
|
+
-- SELF-HEALING: If start not found and cache is empty, trigger rebuild
|
|
462
|
+
IF NOT COALESCE(v_found_start, FALSE) THEN
|
|
463
|
+
IF rem_kv_store_empty(effective_user_id) THEN
|
|
464
|
+
PERFORM maybe_trigger_kv_rebuild(effective_user_id, 'rem_traverse');
|
|
465
|
+
END IF;
|
|
466
|
+
RETURN;
|
|
467
|
+
END IF;
|
|
468
|
+
|
|
414
469
|
FOR graph_keys IN
|
|
415
470
|
WITH RECURSIVE graph_traversal AS (
|
|
416
471
|
-- Base case: Find starting entity (user-owned OR public)
|
|
472
|
+
-- Normalize input key for consistent matching
|
|
417
473
|
SELECT
|
|
418
474
|
0 AS depth,
|
|
419
475
|
kv.entity_key,
|
|
@@ -424,7 +480,7 @@ BEGIN
|
|
|
424
480
|
ARRAY[kv.entity_key]::TEXT[] AS path
|
|
425
481
|
FROM kv_store kv
|
|
426
482
|
WHERE (kv.user_id = effective_user_id OR kv.user_id IS NULL)
|
|
427
|
-
AND kv.entity_key = p_entity_key
|
|
483
|
+
AND kv.entity_key = normalize_key(p_entity_key)
|
|
428
484
|
|
|
429
485
|
UNION ALL
|
|
430
486
|
|
|
@@ -441,7 +497,7 @@ BEGIN
|
|
|
441
497
|
JOIN kv_store source_kv ON source_kv.entity_key = gt.entity_key
|
|
442
498
|
AND (source_kv.user_id = effective_user_id OR source_kv.user_id IS NULL)
|
|
443
499
|
CROSS JOIN LATERAL jsonb_array_elements(COALESCE(source_kv.graph_edges, '[]'::jsonb)) AS edge
|
|
444
|
-
JOIN kv_store target_kv ON target_kv.entity_key = (edge->>'dst')::VARCHAR(255)
|
|
500
|
+
JOIN kv_store target_kv ON target_kv.entity_key = normalize_key((edge->>'dst')::VARCHAR(255))
|
|
445
501
|
AND (target_kv.user_id = effective_user_id OR target_kv.user_id IS NULL)
|
|
446
502
|
WHERE gt.depth < p_max_depth
|
|
447
503
|
AND (p_rel_type IS NULL OR (edge->>'rel_type')::VARCHAR(100) = p_rel_type)
|
|
@@ -760,6 +816,97 @@ $$ LANGUAGE plpgsql STABLE;
|
|
|
760
816
|
COMMENT ON FUNCTION fn_get_shared_messages IS
|
|
761
817
|
'Get messages from sessions shared by a specific user with the recipient.';
|
|
762
818
|
|
|
819
|
+
-- ============================================================================
|
|
820
|
+
-- SESSIONS WITH USER INFO
|
|
821
|
+
-- ============================================================================
|
|
822
|
+
-- Function to list sessions with user details (name, email) for admin views
|
|
823
|
+
|
|
824
|
+
-- List sessions with user info, CTE pagination
|
|
825
|
+
-- Note: messages.session_id stores the session UUID (sessions.id)
|
|
826
|
+
CREATE OR REPLACE FUNCTION fn_list_sessions_with_user(
|
|
827
|
+
p_user_id VARCHAR(256) DEFAULT NULL, -- Filter by user_id (NULL = all users, admin only)
|
|
828
|
+
p_user_name VARCHAR(256) DEFAULT NULL, -- Filter by user name (partial match, admin only)
|
|
829
|
+
p_user_email VARCHAR(256) DEFAULT NULL, -- Filter by user email (partial match, admin only)
|
|
830
|
+
p_mode VARCHAR(50) DEFAULT NULL, -- Filter by session mode
|
|
831
|
+
p_page INTEGER DEFAULT 1,
|
|
832
|
+
p_page_size INTEGER DEFAULT 50
|
|
833
|
+
)
|
|
834
|
+
RETURNS TABLE(
|
|
835
|
+
id UUID,
|
|
836
|
+
name VARCHAR(256),
|
|
837
|
+
mode TEXT,
|
|
838
|
+
description TEXT,
|
|
839
|
+
user_id VARCHAR(256),
|
|
840
|
+
user_name VARCHAR(256),
|
|
841
|
+
user_email VARCHAR(256),
|
|
842
|
+
message_count INTEGER,
|
|
843
|
+
total_tokens INTEGER,
|
|
844
|
+
created_at TIMESTAMP,
|
|
845
|
+
updated_at TIMESTAMP,
|
|
846
|
+
metadata JSONB,
|
|
847
|
+
total_count BIGINT
|
|
848
|
+
) AS $$
|
|
849
|
+
BEGIN
|
|
850
|
+
RETURN QUERY
|
|
851
|
+
WITH session_msg_counts AS (
|
|
852
|
+
-- Count messages per session (joining on session UUID)
|
|
853
|
+
SELECT
|
|
854
|
+
m.session_id,
|
|
855
|
+
COUNT(*)::INTEGER as actual_message_count
|
|
856
|
+
FROM messages m
|
|
857
|
+
GROUP BY m.session_id
|
|
858
|
+
),
|
|
859
|
+
filtered_sessions AS (
|
|
860
|
+
SELECT
|
|
861
|
+
s.id,
|
|
862
|
+
s.name,
|
|
863
|
+
s.mode,
|
|
864
|
+
s.description,
|
|
865
|
+
s.user_id,
|
|
866
|
+
COALESCE(u.name, s.user_id)::VARCHAR(256) AS user_name,
|
|
867
|
+
u.email::VARCHAR(256) AS user_email,
|
|
868
|
+
COALESCE(mc.actual_message_count, 0) AS message_count,
|
|
869
|
+
s.total_tokens,
|
|
870
|
+
s.created_at,
|
|
871
|
+
s.updated_at,
|
|
872
|
+
s.metadata
|
|
873
|
+
FROM sessions s
|
|
874
|
+
LEFT JOIN users u ON u.id::text = s.user_id
|
|
875
|
+
LEFT JOIN session_msg_counts mc ON mc.session_id = s.id::text
|
|
876
|
+
WHERE s.deleted_at IS NULL
|
|
877
|
+
AND (p_user_id IS NULL OR s.user_id = p_user_id)
|
|
878
|
+
AND (p_user_name IS NULL OR u.name ILIKE '%' || p_user_name || '%')
|
|
879
|
+
AND (p_user_email IS NULL OR u.email ILIKE '%' || p_user_email || '%')
|
|
880
|
+
AND (p_mode IS NULL OR s.mode = p_mode)
|
|
881
|
+
),
|
|
882
|
+
counted AS (
|
|
883
|
+
SELECT *, COUNT(*) OVER () AS total_count
|
|
884
|
+
FROM filtered_sessions
|
|
885
|
+
)
|
|
886
|
+
SELECT
|
|
887
|
+
c.id,
|
|
888
|
+
c.name,
|
|
889
|
+
c.mode,
|
|
890
|
+
c.description,
|
|
891
|
+
c.user_id,
|
|
892
|
+
c.user_name,
|
|
893
|
+
c.user_email,
|
|
894
|
+
c.message_count,
|
|
895
|
+
c.total_tokens,
|
|
896
|
+
c.created_at,
|
|
897
|
+
c.updated_at,
|
|
898
|
+
c.metadata,
|
|
899
|
+
c.total_count
|
|
900
|
+
FROM counted c
|
|
901
|
+
ORDER BY c.created_at DESC
|
|
902
|
+
LIMIT p_page_size
|
|
903
|
+
OFFSET (p_page - 1) * p_page_size;
|
|
904
|
+
END;
|
|
905
|
+
$$ LANGUAGE plpgsql STABLE;
|
|
906
|
+
|
|
907
|
+
COMMENT ON FUNCTION fn_list_sessions_with_user IS
|
|
908
|
+
'List sessions with user details and computed message counts. Joins messages on session name.';
|
|
909
|
+
|
|
763
910
|
-- ============================================================================
|
|
764
911
|
-- RECORD INSTALLATION
|
|
765
912
|
-- ============================================================================
|