agentgraph-server 0.7.0__tar.gz → 0.7.2__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.
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/PKG-INFO +3 -3
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/README.md +2 -2
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/backends/sqlite/backend.py +75 -29
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/cli.py +0 -2
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/core/storage.py +18 -5
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/demo.py +2 -2
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/link.py +7 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/operations.py +17 -4
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/query.py +62 -22
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/browse_api.py +47 -12
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/graph_api.py +2 -1
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/observation.py +26 -3
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/static/viewer.html +1 -1
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph_server.egg-info/PKG-INFO +3 -3
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/pyproject.toml +1 -1
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_browse.py +22 -4
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_cli.py +1 -1
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_docs_build.py +1 -1
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_observation.py +142 -7
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_schema.py +32 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_upsert.py +37 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_viewer_layout_contract.py +7 -1
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/.agents/skills/agentgraph/SKILL.md +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/.agents/skills/agentgraph/references/commands.md +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/.agents/skills/agentgraph/references/data-model.md +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/.agents/skills/agentgraph/references/operations.md +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/.agents/skills/slack-auth/SKILL.md +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/LICENSE +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/auth/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/auth/credentials.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/backends/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/backends/sqlite/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/backends/sqlite/schema.sql +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/backends/sqlite/vector.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/cli_query.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/cli_sync.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/config.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/connectors/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/connectors/base.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/connectors/command_effects.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/connectors/feed.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/connectors/match_patterns.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/connectors/registry.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/connectors/status.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/core/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/core/context.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/core/runtime.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/bookmark.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/delete.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/download.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/embeddings.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/expiration.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/fetch.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/person.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/graph/upsert.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/logging.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/mcp/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/mcp/server.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/perf.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/query_client.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/__init__.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/app.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/meta_api.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/presentation.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/router.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/sync.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/sync_api.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/server/uds.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph/skills.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph_server.egg-info/SOURCES.txt +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph_server.egg-info/dependency_links.txt +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph_server.egg-info/entry_points.txt +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph_server.egg-info/requires.txt +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/agentgraph_server.egg-info/top_level.txt +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/setup.cfg +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_auth.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_auth_status_methods.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_benchmarks.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_command_effects.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_config.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_connectors.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_discord_attachments.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_embeddings.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_entity_types.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_expiration.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_feed_connectors.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_fetch.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_graph_operations.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_launch_demo.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_logging.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_match_patterns.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_mcp_transport.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_query.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_query_client.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_query_parity.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_registry.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_release_metadata.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_route_resolution.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_router.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_rss_connector.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_server_imports.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_server_lifespan.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_server_uds.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_slack_auth_artifacts.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_slack_oauth.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_sync.py +0 -0
- {agentgraph_server-0.7.0 → agentgraph_server-0.7.2}/tests/test_web_connector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgraph-server
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A local-first CLI and MCP server that turns selected digital sources into a searchable graph for the AI agent you already use.
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -100,8 +100,8 @@ These connectors are proof of the pattern, not the boundary of the product. A co
|
|
|
100
100
|
AgentGraph requires Python 3.12+, [`uv`](https://docs.astral.sh/uv/), and Chrome for browser observation.
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
|
-
git clone https://github.com/simonexmachina/
|
|
104
|
-
cd
|
|
103
|
+
git clone https://github.com/simonexmachina/agentgraph
|
|
104
|
+
cd agentgraph
|
|
105
105
|
uv sync --extra all
|
|
106
106
|
source .venv/bin/activate
|
|
107
107
|
agentgraph onboard
|
|
@@ -59,8 +59,8 @@ These connectors are proof of the pattern, not the boundary of the product. A co
|
|
|
59
59
|
AgentGraph requires Python 3.12+, [`uv`](https://docs.astral.sh/uv/), and Chrome for browser observation.
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
git clone https://github.com/simonexmachina/
|
|
63
|
-
cd
|
|
62
|
+
git clone https://github.com/simonexmachina/agentgraph
|
|
63
|
+
cd agentgraph
|
|
64
64
|
uv sync --extra all
|
|
65
65
|
source .venv/bin/activate
|
|
66
66
|
agentgraph onboard
|
|
@@ -93,6 +93,22 @@ def _new_id() -> str:
|
|
|
93
93
|
return str(uuid.uuid4())
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def _content_projection(
|
|
97
|
+
content_limit: int | None, column: str = "content"
|
|
98
|
+
) -> tuple[str, list[int]]:
|
|
99
|
+
"""Return SQL that bounds content before it leaves SQLite."""
|
|
100
|
+
if content_limit is None:
|
|
101
|
+
return column, []
|
|
102
|
+
if content_limit < 1:
|
|
103
|
+
raise ValueError("content_limit must be at least 1")
|
|
104
|
+
return (
|
|
105
|
+
f"CASE WHEN length({column}) > ? THEN substr({column}, 1, ?) || '…' "
|
|
106
|
+
f"ELSE {column} END AS content, "
|
|
107
|
+
f"CASE WHEN length({column}) > ? THEN 1 ELSE 0 END AS content_truncated",
|
|
108
|
+
[content_limit, content_limit - 1, content_limit],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
96
112
|
def _entity_predicates(
|
|
97
113
|
entity_types: list[str] | None = None,
|
|
98
114
|
platform: str | None = None,
|
|
@@ -1252,6 +1268,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1252
1268
|
authored_by: list[str] | None = None,
|
|
1253
1269
|
has_attachments: bool = False,
|
|
1254
1270
|
order_by: str | None = None,
|
|
1271
|
+
content_limit: int | None = None,
|
|
1255
1272
|
) -> list[EntityResult]:
|
|
1256
1273
|
"""Select entities by predicate, ranked by relevance or ordered by date.
|
|
1257
1274
|
|
|
@@ -1279,6 +1296,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1279
1296
|
join_params,
|
|
1280
1297
|
limit,
|
|
1281
1298
|
order_by,
|
|
1299
|
+
content_limit,
|
|
1282
1300
|
)
|
|
1283
1301
|
|
|
1284
1302
|
with timed("sqlite.search_entities", limit=limit, platform=platform):
|
|
@@ -1367,17 +1385,18 @@ class SQLiteBackend(StorageBackend):
|
|
|
1367
1385
|
id_list = [eid for eid, _ in top]
|
|
1368
1386
|
score_map = {eid: sc for eid, sc in top}
|
|
1369
1387
|
placeholders = ",".join("?" * len(id_list))
|
|
1388
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1370
1389
|
with timed("sqlite.search.hydrate", count=len(id_list)):
|
|
1371
1390
|
cursor = await conn.execute(
|
|
1372
1391
|
f"""
|
|
1373
1392
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1374
|
-
title,
|
|
1393
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1375
1394
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1376
1395
|
retention_policy, retention_parent_id,
|
|
1377
1396
|
cumulative_observation_duration_ms, bookmarked
|
|
1378
1397
|
FROM entities WHERE id IN ({placeholders})
|
|
1379
1398
|
""",
|
|
1380
|
-
id_list,
|
|
1399
|
+
[*content_params, *id_list],
|
|
1381
1400
|
)
|
|
1382
1401
|
rows = await cursor.fetchall()
|
|
1383
1402
|
results: list[dict[str, Any]] = []
|
|
@@ -1415,15 +1434,17 @@ class SQLiteBackend(StorageBackend):
|
|
|
1415
1434
|
join_params: list[Any],
|
|
1416
1435
|
limit: int,
|
|
1417
1436
|
order_by: str | None,
|
|
1437
|
+
content_limit: int | None,
|
|
1418
1438
|
) -> list[EntityResult]:
|
|
1419
1439
|
"""Return entities matching the predicates alone, newest first."""
|
|
1420
1440
|
if order_by not in _VALID_ORDER_BY:
|
|
1421
1441
|
order_by = "observed_at"
|
|
1442
|
+
content_column, content_params = _content_projection(content_limit, "e.content")
|
|
1422
1443
|
with timed("sqlite.select_entities", order_by=order_by, limit=limit):
|
|
1423
1444
|
rows = await self._fetchall(
|
|
1424
1445
|
f"""
|
|
1425
1446
|
SELECT e.id, e.entity_type, e.platform, e.platform_entity_id,
|
|
1426
|
-
e.title,
|
|
1447
|
+
e.title, {content_column}, e.metadata, e.created_at, e.updated_at,
|
|
1427
1448
|
e.source_created_at, e.source_updated_at,
|
|
1428
1449
|
e.synced_at, e.observed_at,
|
|
1429
1450
|
e.retention_policy, e.retention_parent_id,
|
|
@@ -1434,38 +1455,44 @@ class SQLiteBackend(StorageBackend):
|
|
|
1434
1455
|
ORDER BY e.{order_by} DESC
|
|
1435
1456
|
LIMIT ?
|
|
1436
1457
|
""",
|
|
1437
|
-
[*join_params, *where_params, limit],
|
|
1458
|
+
[*content_params, *join_params, *where_params, limit],
|
|
1438
1459
|
)
|
|
1439
1460
|
return [_row_to_entity(row) for row in rows]
|
|
1440
1461
|
|
|
1441
|
-
async def get_entity_by_id(
|
|
1462
|
+
async def get_entity_by_id(
|
|
1463
|
+
self, entity_id: str, content_limit: int | None = None
|
|
1464
|
+
) -> EntityResult | None:
|
|
1465
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1442
1466
|
row = await self._fetchone(
|
|
1443
|
-
"""
|
|
1467
|
+
f"""
|
|
1444
1468
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1445
|
-
title,
|
|
1469
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1446
1470
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1447
1471
|
retention_policy, retention_parent_id,
|
|
1448
1472
|
cumulative_observation_duration_ms, bookmarked
|
|
1449
1473
|
FROM entities WHERE id = ?
|
|
1450
1474
|
""",
|
|
1451
|
-
[entity_id],
|
|
1475
|
+
[*content_params, entity_id],
|
|
1452
1476
|
)
|
|
1453
1477
|
return _row_to_entity(row) if row else None
|
|
1454
1478
|
|
|
1455
|
-
async def get_entities_by_ids(
|
|
1479
|
+
async def get_entities_by_ids(
|
|
1480
|
+
self, entity_ids: list[str], content_limit: int | None = None
|
|
1481
|
+
) -> list[EntityResult]:
|
|
1456
1482
|
if not entity_ids:
|
|
1457
1483
|
return []
|
|
1458
1484
|
placeholders = ",".join("?" * len(entity_ids))
|
|
1485
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1459
1486
|
rows = await self._fetchall(
|
|
1460
1487
|
f"""
|
|
1461
1488
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1462
|
-
title,
|
|
1489
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1463
1490
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1464
1491
|
retention_policy, retention_parent_id,
|
|
1465
1492
|
cumulative_observation_duration_ms, bookmarked
|
|
1466
1493
|
FROM entities WHERE id IN ({placeholders})
|
|
1467
1494
|
""",
|
|
1468
|
-
entity_ids,
|
|
1495
|
+
[*content_params, *entity_ids],
|
|
1469
1496
|
)
|
|
1470
1497
|
return [_row_to_entity(r) for r in rows]
|
|
1471
1498
|
|
|
@@ -1476,33 +1503,40 @@ class SQLiteBackend(StorageBackend):
|
|
|
1476
1503
|
by_id = {str(entity["id"]): entity for entity in entities}
|
|
1477
1504
|
return [by_id[entity_id] for entity_id in entity_ids if entity_id in by_id]
|
|
1478
1505
|
|
|
1479
|
-
async def get_entities_by_id_prefix(
|
|
1506
|
+
async def get_entities_by_id_prefix(
|
|
1507
|
+
self, prefix: str, content_limit: int | None = None
|
|
1508
|
+
) -> list[EntityResult]:
|
|
1509
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1480
1510
|
rows = await self._fetchall(
|
|
1481
|
-
"""
|
|
1511
|
+
f"""
|
|
1482
1512
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1483
|
-
title,
|
|
1513
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1484
1514
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1485
1515
|
retention_policy, retention_parent_id,
|
|
1486
1516
|
cumulative_observation_duration_ms, bookmarked
|
|
1487
1517
|
FROM entities WHERE id LIKE ?
|
|
1488
1518
|
""",
|
|
1489
|
-
[f"{prefix}%"],
|
|
1519
|
+
[*content_params, f"{prefix}%"],
|
|
1490
1520
|
)
|
|
1491
1521
|
return [_row_to_entity(row) for row in rows]
|
|
1492
1522
|
|
|
1493
1523
|
async def get_entity_by_platform(
|
|
1494
|
-
self,
|
|
1524
|
+
self,
|
|
1525
|
+
platform: str,
|
|
1526
|
+
platform_entity_id: str,
|
|
1527
|
+
content_limit: int | None = None,
|
|
1495
1528
|
) -> EntityResult | None:
|
|
1529
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1496
1530
|
row = await self._fetchone(
|
|
1497
|
-
"""
|
|
1531
|
+
f"""
|
|
1498
1532
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1499
|
-
title,
|
|
1533
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1500
1534
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1501
1535
|
retention_policy, retention_parent_id,
|
|
1502
1536
|
cumulative_observation_duration_ms, bookmarked
|
|
1503
1537
|
FROM entities WHERE platform = ? AND platform_entity_id = ?
|
|
1504
1538
|
""",
|
|
1505
|
-
[platform, platform_entity_id],
|
|
1539
|
+
[*content_params, platform, platform_entity_id],
|
|
1506
1540
|
)
|
|
1507
1541
|
return _row_to_entity(row) if row else None
|
|
1508
1542
|
|
|
@@ -1512,6 +1546,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1512
1546
|
platform: str | None,
|
|
1513
1547
|
since: datetime | None,
|
|
1514
1548
|
limit: int,
|
|
1549
|
+
content_limit: int | None = None,
|
|
1515
1550
|
) -> list[EntityResult]:
|
|
1516
1551
|
clauses: list[str] = []
|
|
1517
1552
|
params: list[Any] = []
|
|
@@ -1527,10 +1562,11 @@ class SQLiteBackend(StorageBackend):
|
|
|
1527
1562
|
params.append(_utc_stamp(since))
|
|
1528
1563
|
where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
|
|
1529
1564
|
params.append(limit)
|
|
1565
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1530
1566
|
rows = await self._fetchall(
|
|
1531
1567
|
f"""
|
|
1532
1568
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1533
|
-
title,
|
|
1569
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1534
1570
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1535
1571
|
retention_policy, retention_parent_id,
|
|
1536
1572
|
cumulative_observation_duration_ms, bookmarked
|
|
@@ -1539,7 +1575,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1539
1575
|
ORDER BY observed_at DESC
|
|
1540
1576
|
LIMIT ?
|
|
1541
1577
|
""",
|
|
1542
|
-
params,
|
|
1578
|
+
[*content_params, *params],
|
|
1543
1579
|
)
|
|
1544
1580
|
return [_row_to_entity(row) for row in rows]
|
|
1545
1581
|
|
|
@@ -1552,6 +1588,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1552
1588
|
offset: int,
|
|
1553
1589
|
order_by: str | None,
|
|
1554
1590
|
order_dir: str,
|
|
1591
|
+
content_limit: int | None = None,
|
|
1555
1592
|
) -> tuple[list[EntityResult], int]:
|
|
1556
1593
|
order_by_sql = (
|
|
1557
1594
|
_LIST_PAGE_ORDER_BY.get(order_by, "observed_at") if order_by is not None else None
|
|
@@ -1578,10 +1615,11 @@ class SQLiteBackend(StorageBackend):
|
|
|
1578
1615
|
|
|
1579
1616
|
count_row = await self._fetchone(f"SELECT COUNT(*) AS count FROM entities {where}", params)
|
|
1580
1617
|
total = int(count_row["count"]) if count_row else 0
|
|
1618
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1581
1619
|
rows = await self._fetchall(
|
|
1582
1620
|
f"""
|
|
1583
1621
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1584
|
-
title,
|
|
1622
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1585
1623
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1586
1624
|
retention_policy, retention_parent_id,
|
|
1587
1625
|
cumulative_observation_duration_ms, bookmarked
|
|
@@ -1590,7 +1628,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1590
1628
|
{order_clause}
|
|
1591
1629
|
LIMIT ? OFFSET ?
|
|
1592
1630
|
""",
|
|
1593
|
-
[*params, limit, offset],
|
|
1631
|
+
[*content_params, *params, limit, offset],
|
|
1594
1632
|
)
|
|
1595
1633
|
return [_row_to_entity(row) for row in rows], total
|
|
1596
1634
|
|
|
@@ -1603,6 +1641,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1603
1641
|
since: datetime | None,
|
|
1604
1642
|
authored_by: list[str] | None,
|
|
1605
1643
|
has_attachments: bool = False,
|
|
1644
|
+
content_limit: int | None = None,
|
|
1606
1645
|
) -> list[EntityResult]:
|
|
1607
1646
|
"""Single-type filtered read, for connectors that hold the backend directly."""
|
|
1608
1647
|
return await self.search_entities(
|
|
@@ -1616,6 +1655,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1616
1655
|
authored_by=authored_by,
|
|
1617
1656
|
has_attachments=has_attachments,
|
|
1618
1657
|
order_by=order_by,
|
|
1658
|
+
content_limit=content_limit,
|
|
1619
1659
|
)
|
|
1620
1660
|
|
|
1621
1661
|
async def list_recent_metadata_by_edge_target(
|
|
@@ -1740,8 +1780,11 @@ class SQLiteBackend(StorageBackend):
|
|
|
1740
1780
|
)
|
|
1741
1781
|
return [_row_to_edge(row) for row in rows]
|
|
1742
1782
|
|
|
1743
|
-
async def traverse_graph(
|
|
1783
|
+
async def traverse_graph(
|
|
1784
|
+
self, entity_id: str, max_depth: int, content_limit: int | None = None
|
|
1785
|
+
) -> dict[str, Any]:
|
|
1744
1786
|
conn = self._read_conn_or_raise()
|
|
1787
|
+
content_column, content_params = _content_projection(content_limit)
|
|
1745
1788
|
visited: set[str] = set()
|
|
1746
1789
|
frontier: list[str] = [entity_id]
|
|
1747
1790
|
all_nodes: list[EntityResult] = []
|
|
@@ -1755,13 +1798,13 @@ class SQLiteBackend(StorageBackend):
|
|
|
1755
1798
|
cursor = await conn.execute(
|
|
1756
1799
|
f"""
|
|
1757
1800
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1758
|
-
title,
|
|
1801
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1759
1802
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1760
1803
|
retention_policy, retention_parent_id,
|
|
1761
1804
|
cumulative_observation_duration_ms, bookmarked
|
|
1762
1805
|
FROM entities WHERE id IN ({placeholders})
|
|
1763
1806
|
""",
|
|
1764
|
-
frontier,
|
|
1807
|
+
[*content_params, *frontier],
|
|
1765
1808
|
)
|
|
1766
1809
|
for row in await cursor.fetchall():
|
|
1767
1810
|
eid = row["id"]
|
|
@@ -1802,13 +1845,13 @@ class SQLiteBackend(StorageBackend):
|
|
|
1802
1845
|
cursor = await conn.execute(
|
|
1803
1846
|
f"""
|
|
1804
1847
|
SELECT id, entity_type, platform, platform_entity_id,
|
|
1805
|
-
title,
|
|
1848
|
+
title, {content_column}, metadata, created_at, updated_at,
|
|
1806
1849
|
source_created_at, source_updated_at, synced_at, observed_at,
|
|
1807
1850
|
retention_policy, retention_parent_id,
|
|
1808
1851
|
cumulative_observation_duration_ms, bookmarked
|
|
1809
1852
|
FROM entities WHERE id IN ({placeholders})
|
|
1810
1853
|
""",
|
|
1811
|
-
unvisited,
|
|
1854
|
+
[*content_params, *unvisited],
|
|
1812
1855
|
)
|
|
1813
1856
|
for row in await cursor.fetchall():
|
|
1814
1857
|
all_nodes.append(_row_to_entity(row))
|
|
@@ -2107,7 +2150,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
2107
2150
|
|
|
2108
2151
|
def _row_to_entity(row: Any) -> EntityResult:
|
|
2109
2152
|
keys = row.keys() if hasattr(row, "keys") else []
|
|
2110
|
-
|
|
2153
|
+
entity: EntityResult = {
|
|
2111
2154
|
"id": row["id"],
|
|
2112
2155
|
"entity_type": row["entity_type"],
|
|
2113
2156
|
"platform": row["platform"],
|
|
@@ -2131,6 +2174,9 @@ def _row_to_entity(row: Any) -> EntityResult:
|
|
|
2131
2174
|
"bookmarked": bool(row["bookmarked"]) if "bookmarked" in keys else False,
|
|
2132
2175
|
"score": row["score"] if "score" in keys else None,
|
|
2133
2176
|
}
|
|
2177
|
+
if "content_truncated" in keys:
|
|
2178
|
+
entity["content_truncated"] = bool(row["content_truncated"])
|
|
2179
|
+
return entity
|
|
2134
2180
|
|
|
2135
2181
|
|
|
2136
2182
|
def _row_to_edge(row: Any) -> EdgeResult:
|
|
@@ -92,6 +92,7 @@ class StorageBackend(ABC):
|
|
|
92
92
|
authored_by: list[str] | None = None,
|
|
93
93
|
has_attachments: bool = False,
|
|
94
94
|
order_by: str | None = None,
|
|
95
|
+
content_limit: int | None = None,
|
|
95
96
|
) -> list[EntityResult]:
|
|
96
97
|
"""Select entities by predicate, ranked by relevance or ordered by date.
|
|
97
98
|
|
|
@@ -103,17 +104,26 @@ class StorageBackend(ABC):
|
|
|
103
104
|
...
|
|
104
105
|
|
|
105
106
|
@abstractmethod
|
|
106
|
-
async def get_entity_by_id(
|
|
107
|
+
async def get_entity_by_id(
|
|
108
|
+
self, entity_id: str, content_limit: int | None = None
|
|
109
|
+
) -> EntityResult | None: ...
|
|
107
110
|
|
|
108
111
|
@abstractmethod
|
|
109
|
-
async def get_entities_by_ids(
|
|
112
|
+
async def get_entities_by_ids(
|
|
113
|
+
self, entity_ids: list[str], content_limit: int | None = None
|
|
114
|
+
) -> list[EntityResult]: ...
|
|
110
115
|
|
|
111
116
|
@abstractmethod
|
|
112
|
-
async def get_entities_by_id_prefix(
|
|
117
|
+
async def get_entities_by_id_prefix(
|
|
118
|
+
self, prefix: str, content_limit: int | None = None
|
|
119
|
+
) -> list[EntityResult]: ...
|
|
113
120
|
|
|
114
121
|
@abstractmethod
|
|
115
122
|
async def get_entity_by_platform(
|
|
116
|
-
self,
|
|
123
|
+
self,
|
|
124
|
+
platform: str,
|
|
125
|
+
platform_entity_id: str,
|
|
126
|
+
content_limit: int | None = None,
|
|
117
127
|
) -> EntityResult | None: ...
|
|
118
128
|
|
|
119
129
|
@abstractmethod
|
|
@@ -123,6 +133,7 @@ class StorageBackend(ABC):
|
|
|
123
133
|
platform: str | None,
|
|
124
134
|
since: datetime | None,
|
|
125
135
|
limit: int,
|
|
136
|
+
content_limit: int | None = None,
|
|
126
137
|
) -> list[EntityResult]: ...
|
|
127
138
|
|
|
128
139
|
@abstractmethod
|
|
@@ -135,6 +146,7 @@ class StorageBackend(ABC):
|
|
|
135
146
|
offset: int,
|
|
136
147
|
order_by: str | None,
|
|
137
148
|
order_dir: str,
|
|
149
|
+
content_limit: int | None = None,
|
|
138
150
|
) -> tuple[list[EntityResult], int]:
|
|
139
151
|
"""Return an entity page and total count, optionally ordered by a supported field."""
|
|
140
152
|
...
|
|
@@ -149,6 +161,7 @@ class StorageBackend(ABC):
|
|
|
149
161
|
since: datetime | None,
|
|
150
162
|
authored_by: list[str] | None,
|
|
151
163
|
has_attachments: bool = False,
|
|
164
|
+
content_limit: int | None = None,
|
|
152
165
|
) -> list[EntityResult]:
|
|
153
166
|
"""Single-type filtered read, kept for connectors that hold a backend directly.
|
|
154
167
|
|
|
@@ -184,7 +197,7 @@ class StorageBackend(ABC):
|
|
|
184
197
|
|
|
185
198
|
@abstractmethod
|
|
186
199
|
async def traverse_graph(
|
|
187
|
-
self, entity_id: str, max_depth: int
|
|
200
|
+
self, entity_id: str, max_depth: int, content_limit: int | None = None
|
|
188
201
|
) -> dict[str, Any]: ...
|
|
189
202
|
|
|
190
203
|
# --- Linking ---
|
|
@@ -15,11 +15,11 @@ DEMO_FIXTURE_METADATA_KEY = "_agentgraph_fixture"
|
|
|
15
15
|
DEMO_FIXTURE_METADATA_VALUE = "atlas-demo"
|
|
16
16
|
DEMO_CREATED_AT = "2026-08-14T08:00:00Z"
|
|
17
17
|
WEBHOOK_ARTICLE_URL = (
|
|
18
|
-
"https://github.com/simonexmachina/
|
|
18
|
+
"https://github.com/simonexmachina/agentgraph/blob/main/"
|
|
19
19
|
"agentgraph/demo_fixtures/reliable-webhooks.md"
|
|
20
20
|
)
|
|
21
21
|
RETRY_GUIDE_URL = (
|
|
22
|
-
"https://github.com/simonexmachina/
|
|
22
|
+
"https://github.com/simonexmachina/agentgraph/blob/main/"
|
|
23
23
|
"agentgraph/demo_fixtures/retry-guidance.md"
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -47,6 +47,13 @@ async def link_entity_to_urls(platform_entity_id: str, platform: str, content: s
|
|
|
47
47
|
continue
|
|
48
48
|
seen.add(ref.resource_id)
|
|
49
49
|
|
|
50
|
+
# An entity's own URL is normally part of its content — connectors append
|
|
51
|
+
# it to the rendered body, and a page or work item often links to itself
|
|
52
|
+
# from a comment. A self-loop carries no information, so it is dropped
|
|
53
|
+
# here rather than left for every caller to filter out of a traversal.
|
|
54
|
+
if ref.source == platform and ref.resource_id == platform_entity_id:
|
|
55
|
+
continue
|
|
56
|
+
|
|
50
57
|
tgt_id = await backend.find_entity_id(ref.source, ref.resource_id)
|
|
51
58
|
if not tgt_id:
|
|
52
59
|
entity_type = RESOURCE_TYPE_TO_ENTITY_TYPE[ref.resource_type]
|
|
@@ -22,6 +22,8 @@ def is_stub(entity: EntityResult) -> bool:
|
|
|
22
22
|
def summarize_entity(entity: EntityResult, *, content_limit: int = 500) -> EntityResult:
|
|
23
23
|
"""Copy an entity and bound its content for list-style responses."""
|
|
24
24
|
summarized = dict(entity)
|
|
25
|
+
if summarized.get("content_truncated") is True:
|
|
26
|
+
return summarized
|
|
25
27
|
content = summarized.get("content")
|
|
26
28
|
if isinstance(content, str) and len(content) > content_limit:
|
|
27
29
|
summarized["content"] = content[: max(content_limit - 1, 0)].rstrip() + "…"
|
|
@@ -40,14 +42,25 @@ def summarize_entities(
|
|
|
40
42
|
return [summarize_entity(entity, content_limit=content_limit) for entity in entities]
|
|
41
43
|
|
|
42
44
|
|
|
43
|
-
async def resolve_entity(
|
|
45
|
+
async def resolve_entity(
|
|
46
|
+
target: str, content_limit: int | None = None
|
|
47
|
+
) -> EntityResult | None:
|
|
44
48
|
"""Resolve an existing entity from a UUID, prefix, platform ref, or URL."""
|
|
45
|
-
|
|
49
|
+
if is_http_url(target):
|
|
50
|
+
if content_limit is None:
|
|
51
|
+
return await get_entity_by_url(target)
|
|
52
|
+
return await get_entity_by_url(target, content_limit=content_limit)
|
|
53
|
+
return await get_entity(target, content_limit=content_limit)
|
|
46
54
|
|
|
47
55
|
|
|
48
|
-
async def get_entity_details(
|
|
56
|
+
async def get_entity_details(
|
|
57
|
+
target: str,
|
|
58
|
+
*,
|
|
59
|
+
resolve: bool = False,
|
|
60
|
+
content_limit: int | None = None,
|
|
61
|
+
) -> EntityResult | None:
|
|
49
62
|
"""Resolve an entity and optionally fetch it when it is still a stub."""
|
|
50
|
-
entity = await resolve_entity(target)
|
|
63
|
+
entity = await resolve_entity(target, content_limit=content_limit)
|
|
51
64
|
if entity is None or not resolve or not is_stub(entity):
|
|
52
65
|
return entity
|
|
53
66
|
return await refresh_stub(entity)
|
|
@@ -62,6 +62,7 @@ async def search_entities(
|
|
|
62
62
|
authored_by_me: bool = False,
|
|
63
63
|
has_attachments: bool = False,
|
|
64
64
|
order_by: str | None = None,
|
|
65
|
+
content_limit: int | None = None,
|
|
65
66
|
) -> list[EntityResult]:
|
|
66
67
|
"""Select entities by filter, ranked by hybrid relevance when a query is given.
|
|
67
68
|
|
|
@@ -93,12 +94,15 @@ async def search_entities(
|
|
|
93
94
|
authored_by=authored_by,
|
|
94
95
|
has_attachments=has_attachments,
|
|
95
96
|
order_by=order_by,
|
|
97
|
+
content_limit=content_limit,
|
|
96
98
|
)
|
|
97
99
|
_enrich_web_url(results)
|
|
98
100
|
return results
|
|
99
101
|
|
|
100
102
|
|
|
101
|
-
async def get_entity(
|
|
103
|
+
async def get_entity(
|
|
104
|
+
entity_id: str, content_limit: int | None = None
|
|
105
|
+
) -> EntityResult | None:
|
|
102
106
|
"""Fetch a single entity by UUID, unambiguous UUID prefix, or platform ref.
|
|
103
107
|
|
|
104
108
|
Platform ref formats accepted:
|
|
@@ -108,15 +112,15 @@ async def get_entity(entity_id: str) -> EntityResult | None:
|
|
|
108
112
|
backend = get_backend()
|
|
109
113
|
entity: EntityResult | None
|
|
110
114
|
if len(entity_id) == 36 or (len(entity_id) == 32 and "-" not in entity_id):
|
|
111
|
-
entity = await backend.get_entity_by_id(entity_id)
|
|
115
|
+
entity = await backend.get_entity_by_id(entity_id, content_limit=content_limit)
|
|
112
116
|
elif "/" in entity_id:
|
|
113
117
|
parts = entity_id.split("/")
|
|
114
118
|
platform = parts[0]
|
|
115
119
|
pid = "/".join(parts[2:]) if len(parts) >= 3 else "/".join(parts[1:])
|
|
116
|
-
entity = await backend.get_entity_by_platform(platform, pid)
|
|
120
|
+
entity = await backend.get_entity_by_platform(platform, pid, content_limit=content_limit)
|
|
117
121
|
else:
|
|
118
122
|
# UUID prefix — must be unambiguous
|
|
119
|
-
results = await backend.get_entities_by_id_prefix(entity_id)
|
|
123
|
+
results = await backend.get_entities_by_id_prefix(entity_id, content_limit=content_limit)
|
|
120
124
|
if len(results) > 1:
|
|
121
125
|
raise ValueError(
|
|
122
126
|
f"Ambiguous prefix {entity_id!r} matches {len(results)} entities"
|
|
@@ -127,7 +131,9 @@ async def get_entity(entity_id: str) -> EntityResult | None:
|
|
|
127
131
|
return entity
|
|
128
132
|
|
|
129
133
|
|
|
130
|
-
async def get_entity_by_url(
|
|
134
|
+
async def get_entity_by_url(
|
|
135
|
+
url: str, content_limit: int | None = None
|
|
136
|
+
) -> EntityResult | None:
|
|
131
137
|
"""Fetch a single existing entity by URL without fetching or creating it."""
|
|
132
138
|
from agentgraph.connectors.registry import bootstrap, get_connector
|
|
133
139
|
from agentgraph.server.router import classify_url, normalise_url_for_matching
|
|
@@ -138,7 +144,12 @@ async def get_entity_by_url(url: str) -> EntityResult | None:
|
|
|
138
144
|
backend = get_backend()
|
|
139
145
|
|
|
140
146
|
if ref is not None:
|
|
141
|
-
|
|
147
|
+
if content_limit is None:
|
|
148
|
+
entity = await backend.get_entity_by_platform(ref.source, ref.resource_id)
|
|
149
|
+
else:
|
|
150
|
+
entity = await backend.get_entity_by_platform(
|
|
151
|
+
ref.source, ref.resource_id, content_limit=content_limit
|
|
152
|
+
)
|
|
142
153
|
if entity is not None:
|
|
143
154
|
_enrich_web_url([entity])
|
|
144
155
|
return entity
|
|
@@ -148,9 +159,14 @@ async def get_entity_by_url(url: str) -> EntityResult | None:
|
|
|
148
159
|
if web_ref is None:
|
|
149
160
|
return None
|
|
150
161
|
|
|
151
|
-
|
|
162
|
+
if content_limit is None:
|
|
163
|
+
entity = await backend.get_entity_by_platform(web_ref.source, web_ref.resource_id)
|
|
164
|
+
else:
|
|
165
|
+
entity = await backend.get_entity_by_platform(
|
|
166
|
+
web_ref.source, web_ref.resource_id, content_limit=content_limit
|
|
167
|
+
)
|
|
152
168
|
if entity is None:
|
|
153
|
-
entity = await _get_entity_by_metadata_url(normalised_url)
|
|
169
|
+
entity = await _get_entity_by_metadata_url(normalised_url, content_limit=content_limit)
|
|
154
170
|
if entity is not None:
|
|
155
171
|
_enrich_web_url([entity])
|
|
156
172
|
return entity
|
|
@@ -167,8 +183,11 @@ async def get_edges(
|
|
|
167
183
|
async def traverse_graph(
|
|
168
184
|
entity_id: str,
|
|
169
185
|
max_depth: int = 2,
|
|
186
|
+
content_limit: int | None = None,
|
|
170
187
|
) -> dict[str, Any]:
|
|
171
|
-
return await get_backend().traverse_graph(
|
|
188
|
+
return await get_backend().traverse_graph(
|
|
189
|
+
entity_id, max_depth, content_limit=content_limit
|
|
190
|
+
)
|
|
172
191
|
|
|
173
192
|
|
|
174
193
|
async def query_by_filter(
|
|
@@ -198,9 +217,12 @@ async def list_entities(
|
|
|
198
217
|
platform: str | None = None,
|
|
199
218
|
since: str | None = None,
|
|
200
219
|
limit: int = 50,
|
|
220
|
+
content_limit: int | None = None,
|
|
201
221
|
) -> list[EntityResult]:
|
|
202
222
|
since_dt = parse_since(since) if since else None
|
|
203
|
-
results = await get_backend().list_entities(
|
|
223
|
+
results = await get_backend().list_entities(
|
|
224
|
+
entity_types, platform, since_dt, limit, content_limit=content_limit
|
|
225
|
+
)
|
|
204
226
|
_enrich_web_url(results)
|
|
205
227
|
return results
|
|
206
228
|
|
|
@@ -213,10 +235,18 @@ async def list_entities_page(
|
|
|
213
235
|
offset: int = 0,
|
|
214
236
|
order_by: str | None = "observed_at",
|
|
215
237
|
order_dir: str = "desc",
|
|
238
|
+
content_limit: int | None = None,
|
|
216
239
|
) -> tuple[list[EntityResult], int]:
|
|
217
240
|
since_dt = parse_since(since) if since else None
|
|
218
241
|
results, total = await get_backend().list_entities_page(
|
|
219
|
-
entity_types,
|
|
242
|
+
entity_types,
|
|
243
|
+
platform,
|
|
244
|
+
since_dt,
|
|
245
|
+
limit,
|
|
246
|
+
offset,
|
|
247
|
+
order_by,
|
|
248
|
+
order_dir,
|
|
249
|
+
content_limit=content_limit,
|
|
220
250
|
)
|
|
221
251
|
_enrich_web_url(results)
|
|
222
252
|
return results, total
|
|
@@ -226,8 +256,10 @@ async def get_edges_for_entities(entity_ids: list[str]) -> list[EdgeResult]:
|
|
|
226
256
|
return await get_backend().get_edges_for_entities(entity_ids)
|
|
227
257
|
|
|
228
258
|
|
|
229
|
-
async def get_entities_by_ids(
|
|
230
|
-
|
|
259
|
+
async def get_entities_by_ids(
|
|
260
|
+
entity_ids: list[str], content_limit: int | None = None
|
|
261
|
+
) -> list[EntityResult]:
|
|
262
|
+
results = await get_backend().get_entities_by_ids(entity_ids, content_limit=content_limit)
|
|
231
263
|
_enrich_web_url(results)
|
|
232
264
|
return results
|
|
233
265
|
|
|
@@ -243,18 +275,26 @@ def _resolve_me() -> list[str] | None:
|
|
|
243
275
|
return user_ids or None
|
|
244
276
|
|
|
245
277
|
|
|
246
|
-
async def _get_entity_by_metadata_url(
|
|
278
|
+
async def _get_entity_by_metadata_url(
|
|
279
|
+
url: str, content_limit: int | None = None
|
|
280
|
+
) -> EntityResult | None:
|
|
247
281
|
"""Find a document whose connector metadata identifies the browser URL."""
|
|
248
282
|
backend = get_backend()
|
|
249
283
|
for key in ("web_url", "url", "final_url"):
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
284
|
+
if content_limit is None:
|
|
285
|
+
results = await backend.query_by_filter(
|
|
286
|
+
"Document", {key: url}, 1, "updated_at", None, None
|
|
287
|
+
)
|
|
288
|
+
else:
|
|
289
|
+
results = await backend.query_by_filter(
|
|
290
|
+
"Document",
|
|
291
|
+
{key: url},
|
|
292
|
+
1,
|
|
293
|
+
"updated_at",
|
|
294
|
+
None,
|
|
295
|
+
None,
|
|
296
|
+
content_limit=content_limit,
|
|
297
|
+
)
|
|
258
298
|
if results:
|
|
259
299
|
return results[0]
|
|
260
300
|
return None
|