agentgraph-server 0.7.2__tar.gz → 0.8.0__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.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/SKILL.md +26 -10
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/references/commands.md +7 -2
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/references/data-model.md +6 -1
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/references/operations.md +4 -2
- {agentgraph_server-0.7.2/agentgraph_server.egg-info → agentgraph_server-0.8.0}/PKG-INFO +12 -12
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/backend.py +6 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/cli.py +21 -3
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/cli_query.py +2 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/base.py +73 -31
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/feed.py +10 -6
- agentgraph_server-0.8.0/agentgraph/connectors/registry.py +175 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/status.py +6 -1
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/storage.py +1 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/link.py +3 -2
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/query.py +3 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/mcp/server.py +40 -30
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/query_client.py +5 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/graph_api.py +2 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/meta_api.py +2 -3
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/static/viewer.html +12 -4
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/skills.py +22 -7
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0/agentgraph_server.egg-info}/PKG-INFO +12 -12
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/SOURCES.txt +2 -0
- agentgraph_server-0.8.0/agentgraph_server.egg-info/requires.txt +38 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/pyproject.toml +12 -12
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_browse.py +3 -1
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_cli.py +104 -1
- agentgraph_server-0.8.0/tests/test_connector_discovery.py +83 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_entity_types.py +66 -3
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_observation.py +13 -0
- agentgraph_server-0.8.0/tests/test_observed_since.py +200 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_query.py +23 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_registry.py +78 -1
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_release_metadata.py +2 -2
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_viewer_layout_contract.py +20 -1
- agentgraph_server-0.7.2/agentgraph/connectors/registry.py +0 -86
- agentgraph_server-0.7.2/agentgraph_server.egg-info/requires.txt +0 -38
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/slack-auth/SKILL.md +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/LICENSE +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/README.md +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/auth/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/auth/credentials.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/schema.sql +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/backends/sqlite/vector.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/cli_sync.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/config.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/command_effects.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/connectors/match_patterns.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/context.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/core/runtime.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/demo.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/bookmark.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/delete.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/download.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/embeddings.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/expiration.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/fetch.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/operations.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/person.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/graph/upsert.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/logging.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/mcp/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/perf.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/__init__.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/app.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/browse_api.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/observation.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/presentation.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/router.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/sync.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/sync_api.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph/server/uds.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/dependency_links.txt +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/entry_points.txt +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/agentgraph_server.egg-info/top_level.txt +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/setup.cfg +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_auth.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_auth_status_methods.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_benchmarks.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_command_effects.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_config.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_connectors.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_discord_attachments.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_docs_build.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_embeddings.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_expiration.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_feed_connectors.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_fetch.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_graph_operations.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_launch_demo.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_logging.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_match_patterns.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_mcp_transport.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_query_client.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_query_parity.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_route_resolution.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_router.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_rss_connector.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_schema.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_server_imports.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_server_lifespan.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_server_uds.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_slack_auth_artifacts.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_slack_oauth.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_sync.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_upsert.py +0 -0
- {agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/tests/test_web_connector.py +0 -0
|
@@ -13,10 +13,6 @@ Prefer the `agentgraph` CLI when a shell is available. Use the equivalent MCP to
|
|
|
13
13
|
when AgentGraph is connected directly to the agent. Do not query AgentGraph's SQLite
|
|
14
14
|
database or connector internals directly.
|
|
15
15
|
|
|
16
|
-
Use `agentgraph demo add` or `add_demo_tool` to add the fictional Atlas fixtures to
|
|
17
|
-
the configured graph. Remove them afterward with `agentgraph demo remove` or
|
|
18
|
-
`remove_demo_tool`.
|
|
19
|
-
|
|
20
16
|
## Commands that use the local server
|
|
21
17
|
|
|
22
18
|
`agentgraph poll` and connector or authentication commands that queue a poll or
|
|
@@ -24,6 +20,10 @@ historical ingest always call the local AgentGraph server. Reads (`search`, `get
|
|
|
24
20
|
`edges`, `traverse`) and `fetch`/`download` use it when it is reachable and
|
|
25
21
|
otherwise read the database directly, so they work with no server running.
|
|
26
22
|
|
|
23
|
+
Run ordinary commands without setting `AGENTGRAPH_QUERY_TRANSPORT`, even when the
|
|
24
|
+
server is stopped. The default `auto` transport tries the Unix
|
|
25
|
+
socket, then TCP, then falls back to in-process automatically.
|
|
26
|
+
|
|
27
27
|
Many agent sandboxes deny loopback TCP but allow an allowlisted Unix socket, so the
|
|
28
28
|
server listens on both and clients prefer the socket. If a command fails because the
|
|
29
29
|
sandbox blocked it:
|
|
@@ -31,9 +31,12 @@ sandbox blocked it:
|
|
|
31
31
|
- For `poll`, ingest, or auth commands there is no fallback — request permission to
|
|
32
32
|
contact the local server, or ask the user to allowlist the socket
|
|
33
33
|
(`~/.agentgraph/agentgraph.sock`), then retry.
|
|
34
|
-
- For reads, `AGENTGRAPH_QUERY_TRANSPORT=in-process`
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
- For reads, retry with `AGENTGRAPH_QUERY_TRANSPORT=in-process` only after an actual
|
|
35
|
+
sandbox connection failure. This bypasses server probes for that retry.
|
|
36
|
+
|
|
37
|
+
To diagnose server connectivity explicitly, use
|
|
38
|
+
`AGENTGRAPH_QUERY_TRANSPORT=server agentgraph search x --limit 1`; it reports whether
|
|
39
|
+
the server is reachable instead of silently falling back.
|
|
37
40
|
|
|
38
41
|
See the Coding agent sandboxes section of the configuration docs for the per-agent
|
|
39
42
|
allowlist settings.
|
|
@@ -55,12 +58,25 @@ allowlist settings.
|
|
|
55
58
|
Start with search unless the user already supplied a graph ID, platform reference, or
|
|
56
59
|
known indexed URL. `search` covers both jobs: pass a query string for ranked
|
|
57
60
|
discovery, and add or use only its filters (`--type`, `--platform`, `--filter`,
|
|
58
|
-
`--since`, `--mine`, `--has-attachments`) when the entity type or
|
|
59
|
-
already known. Omitting the query string turns it into a
|
|
60
|
-
ordered by date.
|
|
61
|
+
`--since`, `--observed-since`, `--mine`, `--has-attachments`) when the entity type or
|
|
62
|
+
constraints are already known. Omitting the query string turns it into a
|
|
63
|
+
deterministic listing ordered by date.
|
|
64
|
+
|
|
65
|
+
Use `--observed-since 2d` (MCP: `observed_since="2d"`) for entities observed in
|
|
66
|
+
the browser within the last two days. It accepts the same relative durations and
|
|
67
|
+
ISO timestamps as `--since`, which filters `updated_at`. Both cutoffs are inclusive;
|
|
68
|
+
when combined, both must match. Never-observed entities are excluded by
|
|
69
|
+
`--observed-since`. Add `--order-by observed_at` to sort by observation time.
|
|
61
70
|
|
|
62
71
|
## Context lifecycle
|
|
63
72
|
|
|
73
|
+
`agentgraph list-connectors --json` (MCP: `list_connectors_tool`) reports each
|
|
74
|
+
connector's declared resource `entity_types`, including standard graph types,
|
|
75
|
+
with their names, resource types, and source-specific descriptions. Person
|
|
76
|
+
identities are handled separately. An empty list means the connector has not
|
|
77
|
+
declared resource types; it does not mean the source has no indexed entities.
|
|
78
|
+
The CLI table displays `-` for this empty list.
|
|
79
|
+
|
|
64
80
|
- **Connect** is setup: installed connectors and their authentication/configuration
|
|
65
81
|
determine which selected sources AgentGraph can access.
|
|
66
82
|
- **Observe** records human attention to a supported browser URL and triggers a
|
{agentgraph_server-0.7.2 → agentgraph_server-0.8.0}/.agents/skills/agentgraph/references/commands.md
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## CLI
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
agentgraph search [
|
|
6
|
+
agentgraph search [--type <type>] [--platform <platform>] [--filter key=value] [--since 12h|30m|2d] [--observed-since 12h|30m|2d] [--mine] [--has-attachments] [--limit N] [--order-by created_at|updated_at|source_created_at|source_updated_at|observed_at|synced_at] [--min-score N] [--json]
|
|
7
7
|
agentgraph get <entity-id|platform/ref|url> [--resolve] [--json]
|
|
8
8
|
agentgraph edges <entity-id|platform/ref> [--type <edge-type>] [--direction in|out|both] [--json]
|
|
9
9
|
agentgraph traverse <entity-id|platform/ref> [--resolve] [--depth 0..4] [--json]
|
|
@@ -14,6 +14,11 @@ filter is applied as a hard constraint (`--limit` defaults to 10). Without one t
|
|
|
14
14
|
is no ranking: the filters select the entities, `--order-by` sorts them newest first,
|
|
15
15
|
`--min-score` is ignored, and `--limit` defaults to 50.
|
|
16
16
|
|
|
17
|
+
`--since` filters `updated_at`; `--observed-since` filters browser observation time
|
|
18
|
+
(`observed_at`), excluding never-observed entities. Both accept ISO timestamps or
|
|
19
|
+
relative durations and include the cutoff itself. When supplied together, both
|
|
20
|
+
must match. Neither flag changes ordering.
|
|
21
|
+
|
|
17
22
|
`get` and `traverse` do not resolve stubs unless `--resolve` is supplied. Depth 0
|
|
18
23
|
returns only the starting entity; depths 1 through 4 include that many relationship
|
|
19
24
|
hops.
|
|
@@ -28,7 +33,7 @@ accept full UUIDs, unambiguous UUID prefixes, and platform references.
|
|
|
28
33
|
## MCP equivalents
|
|
29
34
|
|
|
30
35
|
```text
|
|
31
|
-
agentgraph search ... -> search_entities_tool(query, entity_types, platform, filters, since, authored_by_me, has_attachments, limit, order_by, min_score, refresh)
|
|
36
|
+
agentgraph search ... -> search_entities_tool(query, entity_types, platform, filters, since, authored_by_me, has_attachments, limit, order_by, min_score, refresh, observed_since)
|
|
32
37
|
agentgraph get ... -> get_entity_tool(entity_id, resolve)
|
|
33
38
|
agentgraph edges ... -> get_edges_tool(entity_id, edge_type, direction)
|
|
34
39
|
agentgraph traverse ... -> traverse_graph_tool(entity_id, max_depth, resolve)
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
| `Task` | Tracked work items such as Jira issues. Issue key, status, and assignee live in `metadata`. |
|
|
15
15
|
| `Video` | Recorded videos such as Looms. The transcript is indexed as content and `metadata.web_url` links to the video. |
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Installed connectors may add shared entity type names such as `Project`. The complete
|
|
18
|
+
alphabetical catalog, including connector descriptions, is embedded in the
|
|
19
|
+
`search_entities_tool` MCP description. With the CLI, connector-specific declarations
|
|
20
|
+
are available from `agentgraph list-connectors --json`. Undeclared type strings remain
|
|
21
|
+
storable and searchable for compatibility, but do not receive automatic discovery or
|
|
22
|
+
reference routing.
|
|
18
23
|
|
|
19
24
|
## Relationships
|
|
20
25
|
|
|
@@ -10,8 +10,10 @@ agentgraph auth remove <provider> [--account <account-id>] [--json]
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Use `agentgraph list-connectors --json` to discover installed connector source names,
|
|
13
|
-
valid platform values, URL ownership, polling
|
|
14
|
-
|
|
13
|
+
valid platform values, connector-declared entity types, URL ownership, polling
|
|
14
|
+
delegation, and sync state. Do not rely on a hardcoded platform list. MCP clients
|
|
15
|
+
receive the full installed entity type catalog in the `search_entities_tool`
|
|
16
|
+
description during tool discovery. Use `--verify` only when a live provider check is
|
|
15
17
|
needed.
|
|
16
18
|
|
|
17
19
|
MCP equivalents are `list_connectors_tool`, `list_auth_providers_tool`,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgraph-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
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
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: aiosqlite>=0.20.0
|
|
9
|
-
Requires-Dist: agentgraph-connector-web<0.7,>=0.
|
|
9
|
+
Requires-Dist: agentgraph-connector-web<0.7,>=0.6.0
|
|
10
10
|
Requires-Dist: apscheduler>=3.11.2
|
|
11
11
|
Requires-Dist: click<8.4
|
|
12
12
|
Requires-Dist: fastapi>=0.135.2
|
|
@@ -22,21 +22,21 @@ Requires-Dist: sqlite-vec>=0.1.9
|
|
|
22
22
|
Requires-Dist: typer>=0.24.1
|
|
23
23
|
Requires-Dist: uvicorn>=0.42.0
|
|
24
24
|
Provides-Extra: google
|
|
25
|
-
Requires-Dist: agentgraph-connector-google<0.7,>=0.
|
|
25
|
+
Requires-Dist: agentgraph-connector-google<0.7,>=0.6.0; extra == "google"
|
|
26
26
|
Provides-Extra: slack
|
|
27
|
-
Requires-Dist: agentgraph-connector-slack<0.7,>=0.
|
|
27
|
+
Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.0; extra == "slack"
|
|
28
28
|
Provides-Extra: discord
|
|
29
|
-
Requires-Dist: agentgraph-connector-discord<0.7,>=0.
|
|
29
|
+
Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.0; extra == "discord"
|
|
30
30
|
Provides-Extra: rss
|
|
31
|
-
Requires-Dist: agentgraph-connector-rss<0.7,>=0.
|
|
31
|
+
Requires-Dist: agentgraph-connector-rss<0.7,>=0.6.0; extra == "rss"
|
|
32
32
|
Provides-Extra: web
|
|
33
|
-
Requires-Dist: agentgraph-connector-web<0.7,>=0.
|
|
33
|
+
Requires-Dist: agentgraph-connector-web<0.7,>=0.6.0; extra == "web"
|
|
34
34
|
Provides-Extra: all
|
|
35
|
-
Requires-Dist: agentgraph-connector-web<0.7,>=0.
|
|
36
|
-
Requires-Dist: agentgraph-connector-google<0.7,>=0.
|
|
37
|
-
Requires-Dist: agentgraph-connector-slack<0.7,>=0.
|
|
38
|
-
Requires-Dist: agentgraph-connector-discord<0.7,>=0.
|
|
39
|
-
Requires-Dist: agentgraph-connector-rss<0.7,>=0.
|
|
35
|
+
Requires-Dist: agentgraph-connector-web<0.7,>=0.6.0; extra == "all"
|
|
36
|
+
Requires-Dist: agentgraph-connector-google<0.7,>=0.6.0; extra == "all"
|
|
37
|
+
Requires-Dist: agentgraph-connector-slack<0.7,>=0.6.0; extra == "all"
|
|
38
|
+
Requires-Dist: agentgraph-connector-discord<0.7,>=0.6.0; extra == "all"
|
|
39
|
+
Requires-Dist: agentgraph-connector-rss<0.7,>=0.6.0; extra == "all"
|
|
40
40
|
Dynamic: license-file
|
|
41
41
|
|
|
42
42
|
# AgentGraph
|
|
@@ -116,6 +116,7 @@ def _entity_predicates(
|
|
|
116
116
|
since: datetime | None = None,
|
|
117
117
|
authored_by: list[str] | None = None,
|
|
118
118
|
has_attachments: bool = False,
|
|
119
|
+
observed_since: datetime | None = None,
|
|
119
120
|
) -> tuple[str, list[Any], str, list[Any]]:
|
|
120
121
|
"""Build the entity-selection predicates shared by every leg of a search.
|
|
121
122
|
|
|
@@ -143,6 +144,9 @@ def _entity_predicates(
|
|
|
143
144
|
if since:
|
|
144
145
|
clauses.append("e.updated_at >= ?")
|
|
145
146
|
params.append(_utc_stamp(since))
|
|
147
|
+
if observed_since:
|
|
148
|
+
clauses.append("e.observed_at >= ?")
|
|
149
|
+
params.append(_utc_stamp(observed_since))
|
|
146
150
|
if has_attachments:
|
|
147
151
|
clauses.append(
|
|
148
152
|
"json_extract(e.metadata, '$.attachments') IS NOT NULL"
|
|
@@ -1269,6 +1273,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1269
1273
|
has_attachments: bool = False,
|
|
1270
1274
|
order_by: str | None = None,
|
|
1271
1275
|
content_limit: int | None = None,
|
|
1276
|
+
observed_since: datetime | None = None,
|
|
1272
1277
|
) -> list[EntityResult]:
|
|
1273
1278
|
"""Select entities by predicate, ranked by relevance or ordered by date.
|
|
1274
1279
|
|
|
@@ -1286,6 +1291,7 @@ class SQLiteBackend(StorageBackend):
|
|
|
1286
1291
|
since,
|
|
1287
1292
|
authored_by,
|
|
1288
1293
|
has_attachments,
|
|
1294
|
+
observed_since=observed_since,
|
|
1289
1295
|
)
|
|
1290
1296
|
|
|
1291
1297
|
if query_text is None or query_vec is None:
|
|
@@ -484,7 +484,7 @@ def list_connectors(
|
|
|
484
484
|
False, "--verify", help="Live-check connector credentials with provider APIs"
|
|
485
485
|
),
|
|
486
486
|
) -> None:
|
|
487
|
-
"""List installed connectors and
|
|
487
|
+
"""List installed connectors, their resource entity types, and sync status."""
|
|
488
488
|
from agentgraph.connectors.registry import bootstrap, get_all_connectors
|
|
489
489
|
from agentgraph.connectors.status import connector_status_items
|
|
490
490
|
from agentgraph.core.runtime import backend_context
|
|
@@ -505,6 +505,7 @@ def list_connectors(
|
|
|
505
505
|
table = Table(title="Connectors", show_lines=True)
|
|
506
506
|
table.add_column("Connector", style="bold", no_wrap=True)
|
|
507
507
|
table.add_column("Description", ratio=1)
|
|
508
|
+
table.add_column("Types")
|
|
508
509
|
table.add_column("Auth")
|
|
509
510
|
table.add_column("Sync")
|
|
510
511
|
table.add_column("Last sync", no_wrap=True)
|
|
@@ -513,6 +514,11 @@ def list_connectors(
|
|
|
513
514
|
table.add_row(
|
|
514
515
|
str(item["source"]),
|
|
515
516
|
str(desc),
|
|
517
|
+
", ".join(
|
|
518
|
+
str(definition["name"])
|
|
519
|
+
for definition in cast(list[dict[str, object]], item["entity_types"])
|
|
520
|
+
)
|
|
521
|
+
or "-",
|
|
516
522
|
_connector_auth_label(item),
|
|
517
523
|
str(item["sync"]),
|
|
518
524
|
str(item["last_sync"]),
|
|
@@ -591,7 +597,7 @@ def search(
|
|
|
591
597
|
None,
|
|
592
598
|
"--since",
|
|
593
599
|
"-s",
|
|
594
|
-
help="Only results after this time: ISO timestamp or relative (12h, 30m, 2d)",
|
|
600
|
+
help="Only results updated at or after this time: ISO timestamp or relative (12h, 30m, 2d)",
|
|
595
601
|
),
|
|
596
602
|
mine: bool = typer.Option(False, "--mine", "-m", help="Only entities authored by me"),
|
|
597
603
|
has_attachments: bool = typer.Option(
|
|
@@ -613,6 +619,11 @@ def search(
|
|
|
613
619
|
0.03, "--min-score", help="Minimum relevance score (0–1); ignored without a query"
|
|
614
620
|
),
|
|
615
621
|
json: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
622
|
+
observed_since: str | None = typer.Option(
|
|
623
|
+
None,
|
|
624
|
+
"--observed-since",
|
|
625
|
+
help="Only results observed at or after this time: ISO timestamp or relative (12h, 30m, 2d)",
|
|
626
|
+
),
|
|
616
627
|
) -> None:
|
|
617
628
|
"""Search the knowledge graph, or list entities matching filters alone."""
|
|
618
629
|
from agentgraph.cli_query import cmd_search
|
|
@@ -623,6 +634,7 @@ def search(
|
|
|
623
634
|
platform=platform,
|
|
624
635
|
filters=dict(f.split("=", 1) for f in filter if "=" in f),
|
|
625
636
|
since=since,
|
|
637
|
+
observed_since=observed_since,
|
|
626
638
|
authored_by_me=mine,
|
|
627
639
|
has_attachments=has_attachments,
|
|
628
640
|
limit=limit,
|
|
@@ -874,7 +886,13 @@ def install_skill(
|
|
|
874
886
|
|
|
875
887
|
typer.echo(f"Installed AgentGraph skill '{result.skill}' to {result.destination}")
|
|
876
888
|
if result.claude_destination is not None:
|
|
877
|
-
|
|
889
|
+
if result.claude_linked:
|
|
890
|
+
typer.echo(f"Linked Claude skill to {result.claude_destination}")
|
|
891
|
+
else:
|
|
892
|
+
typer.echo(
|
|
893
|
+
f"Claude reads {result.claude_destination} already; "
|
|
894
|
+
"its skill directory is the same one."
|
|
895
|
+
)
|
|
878
896
|
|
|
879
897
|
|
|
880
898
|
@app.command()
|
|
@@ -109,6 +109,7 @@ def cmd_search(
|
|
|
109
109
|
authored_by_me: bool = False,
|
|
110
110
|
has_attachments: bool = False,
|
|
111
111
|
order_by: str | None = None,
|
|
112
|
+
observed_since: str | None = None,
|
|
112
113
|
) -> None:
|
|
113
114
|
# An empty or whitespace-only argument is an absent query, not a query for
|
|
114
115
|
# nothing: this keeps the limit default, the renderer, and the backend's
|
|
@@ -127,6 +128,7 @@ def cmd_search(
|
|
|
127
128
|
platform,
|
|
128
129
|
filters=filters,
|
|
129
130
|
since=since,
|
|
131
|
+
observed_since=observed_since,
|
|
130
132
|
authored_by_me=authored_by_me,
|
|
131
133
|
has_attachments=has_attachments,
|
|
132
134
|
order_by=order_by,
|
|
@@ -11,24 +11,13 @@ from dataclasses import dataclass
|
|
|
11
11
|
from datetime import UTC, datetime, timedelta
|
|
12
12
|
from typing import Any, ClassVar, Literal
|
|
13
13
|
|
|
14
|
-
from pydantic import BaseModel, Field
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
ResourceType = Literal[
|
|
19
|
-
"channel",
|
|
20
|
-
"dm",
|
|
21
|
-
"document",
|
|
22
|
-
"folder",
|
|
23
|
-
"message",
|
|
24
|
-
"spreadsheet",
|
|
25
|
-
"thread",
|
|
26
|
-
"video",
|
|
27
|
-
"work-item",
|
|
28
|
-
]
|
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
15
|
+
|
|
16
|
+
# Resource types name connector-local fetch strategies. Connectors may extend them.
|
|
17
|
+
type ResourceType = str
|
|
29
18
|
RetentionPolicy = Literal["observed", "owned", "connected", "persistent"]
|
|
30
19
|
|
|
31
|
-
#
|
|
20
|
+
# Built-in entity type vocabulary. Storage also accepts connector-defined names.
|
|
32
21
|
ENTITY_TYPES: tuple[str, ...] = (
|
|
33
22
|
"Channel",
|
|
34
23
|
"Document",
|
|
@@ -41,6 +30,18 @@ ENTITY_TYPES: tuple[str, ...] = (
|
|
|
41
30
|
"Video",
|
|
42
31
|
)
|
|
43
32
|
|
|
33
|
+
ENTITY_TYPE_DESCRIPTIONS: dict[str, str] = {
|
|
34
|
+
"Channel": "Chat channels and direct-message threads.",
|
|
35
|
+
"Document": "Text documents, web pages, files, and attachment stubs.",
|
|
36
|
+
"Email": "Email threads.",
|
|
37
|
+
"Folder": "Containers such as Drive folders and RSS feeds.",
|
|
38
|
+
"Message": "Chat messages, including chat uploads stored in metadata.",
|
|
39
|
+
"Person": "Source identities and confirmed cross-source identity merges.",
|
|
40
|
+
"Spreadsheet": "Spreadsheets and tabular workbook resources.",
|
|
41
|
+
"Task": "Tracked work items such as issues and tasks.",
|
|
42
|
+
"Video": "Recorded videos whose searchable content may include a transcript.",
|
|
43
|
+
}
|
|
44
|
+
|
|
44
45
|
# Broad URL extractor — classify_url does fine-grained matching
|
|
45
46
|
_URL_RE = re.compile(r"https?://\S+")
|
|
46
47
|
|
|
@@ -57,6 +58,27 @@ RESOURCE_TYPE_TO_ENTITY_TYPE: dict[str, str] = {
|
|
|
57
58
|
"work-item": "Task",
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
_ENTITY_TYPE_TO_RESOURCE_TYPE: dict[str, ResourceType] = {
|
|
62
|
+
"Channel": "channel",
|
|
63
|
+
"Document": "document",
|
|
64
|
+
"Email": "thread",
|
|
65
|
+
"Folder": "folder",
|
|
66
|
+
"Message": "message",
|
|
67
|
+
"Spreadsheet": "spreadsheet",
|
|
68
|
+
"Task": "work-item",
|
|
69
|
+
"Video": "video",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class EntityTypeDefinition(BaseModel):
|
|
74
|
+
"""A connector-local fetch resource mapped to a shared graph entity type."""
|
|
75
|
+
|
|
76
|
+
model_config = ConfigDict(frozen=True)
|
|
77
|
+
|
|
78
|
+
name: str = Field(pattern=r"^[A-Z][A-Za-z0-9]*$")
|
|
79
|
+
resource_type: str = Field(pattern=r"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$")
|
|
80
|
+
description: str = Field(min_length=1)
|
|
81
|
+
|
|
60
82
|
|
|
61
83
|
@dataclass(frozen=True)
|
|
62
84
|
class SourceReference:
|
|
@@ -147,6 +169,7 @@ class EntityBatch(BaseModel):
|
|
|
147
169
|
"""
|
|
148
170
|
if not entity.content:
|
|
149
171
|
return
|
|
172
|
+
from agentgraph.connectors.registry import entity_type_for_reference
|
|
150
173
|
from agentgraph.server.router import classify_url
|
|
151
174
|
|
|
152
175
|
seen: set[str] = set()
|
|
@@ -163,7 +186,7 @@ class EntityBatch(BaseModel):
|
|
|
163
186
|
seen.add(key)
|
|
164
187
|
self.entities.append(
|
|
165
188
|
EntityRecord(
|
|
166
|
-
entity_type=
|
|
189
|
+
entity_type=entity_type_for_reference(ref),
|
|
167
190
|
platform=ref.source,
|
|
168
191
|
platform_entity_id=ref.resource_id,
|
|
169
192
|
is_stub=True,
|
|
@@ -260,6 +283,13 @@ class ResourceUnavailableError(RuntimeError):
|
|
|
260
283
|
class BaseConnector(ABC):
|
|
261
284
|
source: ClassVar[str] # platform name, e.g. "slack" — must be set by subclass
|
|
262
285
|
fetch_policy: ClassVar[FetchPolicy] # staleness policy — must be set by subclass
|
|
286
|
+
entity_types: ClassVar[tuple[EntityTypeDefinition, ...]] = ()
|
|
287
|
+
"""Resource entity types exposed by this connector, including core vocabulary.
|
|
288
|
+
|
|
289
|
+
Used for capability discovery and to extend or override core resource mappings.
|
|
290
|
+
Person identities are emitted separately through EntityBatch.persons.
|
|
291
|
+
"""
|
|
292
|
+
|
|
263
293
|
is_generic_url_fallback: ClassVar[bool] = False
|
|
264
294
|
"""True for broad fallback connectors that should not claim URLs during discovery."""
|
|
265
295
|
|
|
@@ -387,21 +417,33 @@ class BaseConnector(ABC):
|
|
|
387
417
|
def normalise_fetch_id(self, resource_id: str, entity_type: str) -> tuple[str, ResourceType]:
|
|
388
418
|
"""Map a stored resource_id + entity_type to the (id, resource_type) that fetch() expects.
|
|
389
419
|
|
|
390
|
-
The default
|
|
391
|
-
returns resource_id unchanged. Connectors override this when
|
|
392
|
-
differ from their fetchable IDs (e.g. Discord message IDs
|
|
420
|
+
The default prefers connector-declared mappings, falls back to the core
|
|
421
|
+
vocabulary, and returns resource_id unchanged. Connectors override this when
|
|
422
|
+
their stored IDs differ from their fetchable IDs (e.g. Discord message IDs
|
|
423
|
+
encode the channel).
|
|
393
424
|
"""
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
425
|
+
return resource_id, type(self).resource_type_for_entity_type(entity_type)
|
|
426
|
+
|
|
427
|
+
@classmethod
|
|
428
|
+
def entity_type_for_resource_type(cls, resource_type: ResourceType) -> str:
|
|
429
|
+
"""Map this connector's fetch resource type to a stored entity type."""
|
|
430
|
+
for definition in cls.entity_types:
|
|
431
|
+
if definition.resource_type == resource_type:
|
|
432
|
+
return definition.name
|
|
433
|
+
try:
|
|
434
|
+
return RESOURCE_TYPE_TO_ENTITY_TYPE[resource_type]
|
|
435
|
+
except KeyError as exc:
|
|
436
|
+
raise ValueError(
|
|
437
|
+
f"Connector {cls.source!r} does not declare resource type {resource_type!r}"
|
|
438
|
+
) from exc
|
|
439
|
+
|
|
440
|
+
@classmethod
|
|
441
|
+
def resource_type_for_entity_type(cls, entity_type: str) -> ResourceType:
|
|
442
|
+
"""Map a stored entity type to this connector's canonical fetch resource type."""
|
|
443
|
+
for definition in cls.entity_types:
|
|
444
|
+
if definition.name == entity_type:
|
|
445
|
+
return definition.resource_type
|
|
446
|
+
return _ENTITY_TYPE_TO_RESOURCE_TYPE.get(entity_type, "document")
|
|
405
447
|
|
|
406
448
|
@classmethod
|
|
407
449
|
def current_user_id(cls) -> str | None:
|
|
@@ -13,11 +13,7 @@ from uuid import UUID, uuid4
|
|
|
13
13
|
|
|
14
14
|
from pydantic import BaseModel, Field
|
|
15
15
|
|
|
16
|
-
from agentgraph.connectors.base import
|
|
17
|
-
RESOURCE_TYPE_TO_ENTITY_TYPE,
|
|
18
|
-
BaseConnector,
|
|
19
|
-
ResourceType,
|
|
20
|
-
)
|
|
16
|
+
from agentgraph.connectors.base import BaseConnector, ResourceType
|
|
21
17
|
|
|
22
18
|
logger = logging.getLogger(__name__)
|
|
23
19
|
|
|
@@ -201,11 +197,19 @@ def mutation_target_from_reference(
|
|
|
201
197
|
url: str,
|
|
202
198
|
) -> MutationTarget:
|
|
203
199
|
"""Build a target from a connector-owned source reference."""
|
|
200
|
+
from agentgraph.connectors.base import SourceReference
|
|
201
|
+
from agentgraph.connectors.registry import entity_type_for_reference
|
|
204
202
|
|
|
205
203
|
return MutationTarget(
|
|
206
204
|
platform=platform,
|
|
207
205
|
platform_entity_id=platform_entity_id,
|
|
208
|
-
entity_type=
|
|
206
|
+
entity_type=entity_type_for_reference(
|
|
207
|
+
SourceReference(
|
|
208
|
+
source=platform,
|
|
209
|
+
resource_type=resource_type,
|
|
210
|
+
resource_id=platform_entity_id,
|
|
211
|
+
)
|
|
212
|
+
),
|
|
209
213
|
resource_type=resource_type,
|
|
210
214
|
url=url,
|
|
211
215
|
)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Connector registry: maps source names to connector instances.
|
|
2
|
+
|
|
3
|
+
Built-in connectors are discovered via Python entry points
|
|
4
|
+
(``agentgraph.connectors`` group). Third-party connectors can be installed
|
|
5
|
+
as separate packages that declare the same entry point group.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib.metadata
|
|
11
|
+
import logging
|
|
12
|
+
from collections.abc import Iterable, Sequence
|
|
13
|
+
from typing import cast
|
|
14
|
+
|
|
15
|
+
from agentgraph.connectors.base import (
|
|
16
|
+
ENTITY_TYPE_DESCRIPTIONS,
|
|
17
|
+
ENTITY_TYPES,
|
|
18
|
+
RESOURCE_TYPE_TO_ENTITY_TYPE,
|
|
19
|
+
BaseConnector,
|
|
20
|
+
EntityTypeDefinition,
|
|
21
|
+
SourceReference,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
_registry: dict[str, BaseConnector] = {}
|
|
27
|
+
_entry_points: dict[str, importlib.metadata.EntryPoint] = {}
|
|
28
|
+
_load_errors: dict[str, str] = {}
|
|
29
|
+
_bootstrapped = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def register(connector: BaseConnector) -> None:
|
|
33
|
+
_validate_entity_types(connector)
|
|
34
|
+
_registry[connector.source] = connector
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _validate_entity_types(connector: BaseConnector) -> None:
|
|
38
|
+
raw_definitions: object = getattr(type(connector), "entity_types", None)
|
|
39
|
+
if not isinstance(raw_definitions, tuple):
|
|
40
|
+
raise TypeError(f"Connector {connector.source!r} entity_types must be a tuple")
|
|
41
|
+
definitions = cast(tuple[object, ...], raw_definitions)
|
|
42
|
+
|
|
43
|
+
names: set[str] = set()
|
|
44
|
+
resource_types: set[str] = set()
|
|
45
|
+
for definition in definitions:
|
|
46
|
+
if not isinstance(definition, EntityTypeDefinition):
|
|
47
|
+
raise TypeError(
|
|
48
|
+
f"Connector {connector.source!r} entity_types must contain "
|
|
49
|
+
"EntityTypeDefinition values"
|
|
50
|
+
)
|
|
51
|
+
if definition.name in names:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Connector {connector.source!r} declares entity type "
|
|
54
|
+
f"{definition.name!r} more than once"
|
|
55
|
+
)
|
|
56
|
+
if definition.resource_type in resource_types:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Connector {connector.source!r} declares resource type "
|
|
59
|
+
f"{definition.resource_type!r} more than once"
|
|
60
|
+
)
|
|
61
|
+
names.add(definition.name)
|
|
62
|
+
resource_types.add(definition.resource_type)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_entity_type_names(connectors: Sequence[BaseConnector] | None = None) -> list[str]:
|
|
66
|
+
"""Return the sorted union of core and installed connector entity type names."""
|
|
67
|
+
resolved = list(connectors) if connectors is not None else get_all_connectors()
|
|
68
|
+
names = set(ENTITY_TYPES)
|
|
69
|
+
for connector in resolved:
|
|
70
|
+
names.update(definition.name for definition in _entity_type_definitions(connector))
|
|
71
|
+
return sorted(names)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_entity_type_catalog(
|
|
75
|
+
connectors: Sequence[BaseConnector] | None = None,
|
|
76
|
+
) -> list[tuple[str, list[tuple[str, str]]]]:
|
|
77
|
+
"""Return entity types with their core or connector-labelled descriptions."""
|
|
78
|
+
resolved = list(connectors) if connectors is not None else get_all_connectors()
|
|
79
|
+
descriptions: dict[str, list[tuple[str, str]]] = {
|
|
80
|
+
name: [("core", ENTITY_TYPE_DESCRIPTIONS[name])] for name in ENTITY_TYPES
|
|
81
|
+
}
|
|
82
|
+
for connector in resolved:
|
|
83
|
+
for definition in _entity_type_definitions(connector):
|
|
84
|
+
entries = descriptions.setdefault(definition.name, [])
|
|
85
|
+
detail = (connector.source, definition.description)
|
|
86
|
+
if detail not in entries:
|
|
87
|
+
entries.append(detail)
|
|
88
|
+
catalog: list[tuple[str, list[tuple[str, str]]]] = []
|
|
89
|
+
for name in sorted(descriptions):
|
|
90
|
+
entries = descriptions[name]
|
|
91
|
+
entries.sort(key=lambda item: (item[0] != "core", item[0], item[1]))
|
|
92
|
+
catalog.append((name, entries))
|
|
93
|
+
return catalog
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _entity_type_definitions(connector: BaseConnector) -> tuple[EntityTypeDefinition, ...]:
|
|
97
|
+
return cast(
|
|
98
|
+
tuple[EntityTypeDefinition, ...],
|
|
99
|
+
getattr(type(connector), "entity_types", ()),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def entity_type_for_reference(reference: SourceReference) -> str:
|
|
104
|
+
"""Resolve a source-scoped resource type to the stored graph entity type."""
|
|
105
|
+
connector = get_connector(reference.source)
|
|
106
|
+
if connector is not None:
|
|
107
|
+
return type(connector).entity_type_for_resource_type(reference.resource_type)
|
|
108
|
+
try:
|
|
109
|
+
return RESOURCE_TYPE_TO_ENTITY_TYPE[reference.resource_type]
|
|
110
|
+
except KeyError as exc:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"No connector registered for source {reference.source!r} and resource type "
|
|
113
|
+
f"{reference.resource_type!r} is not a core resource type"
|
|
114
|
+
) from exc
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_connector(source: str) -> BaseConnector | None:
|
|
118
|
+
bootstrap()
|
|
119
|
+
if source not in _registry and source in _entry_points:
|
|
120
|
+
_load_entry_point(source, _entry_points[source])
|
|
121
|
+
return _registry.get(source)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_connector_load_error(source: str) -> str | None:
|
|
125
|
+
"""Return the recorded entry-point import error for a connector, if any."""
|
|
126
|
+
return _load_errors.get(source)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def registered_sources() -> list[str]:
|
|
130
|
+
bootstrap()
|
|
131
|
+
return list(dict.fromkeys([*_registry.keys(), *_entry_points.keys()]))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_all_connectors() -> list[BaseConnector]:
|
|
135
|
+
bootstrap()
|
|
136
|
+
for name, ep in list(_entry_points.items()):
|
|
137
|
+
if name not in _registry:
|
|
138
|
+
_load_entry_point(name, ep)
|
|
139
|
+
return list(_registry.values())
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def bootstrap() -> None:
|
|
143
|
+
"""Discover connector entry points without importing connector packages."""
|
|
144
|
+
global _bootstrapped
|
|
145
|
+
if _bootstrapped:
|
|
146
|
+
return
|
|
147
|
+
_bootstrapped = True
|
|
148
|
+
|
|
149
|
+
for ep in _connector_entry_points():
|
|
150
|
+
_entry_points.setdefault(ep.name, ep)
|
|
151
|
+
|
|
152
|
+
if not _entry_points and not _registry:
|
|
153
|
+
logger.warning(
|
|
154
|
+
"No connectors discovered. Install connector packages (e.g. pip install agentgraph[all]) "
|
|
155
|
+
"or declare entry points in the 'agentgraph.connectors' group."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _connector_entry_points() -> Iterable[importlib.metadata.EntryPoint]:
|
|
160
|
+
return importlib.metadata.entry_points(group="agentgraph.connectors")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _load_entry_point(name: str, ep: importlib.metadata.EntryPoint) -> None:
|
|
164
|
+
try:
|
|
165
|
+
connector_class: type[BaseConnector] = ep.load()
|
|
166
|
+
connector = connector_class()
|
|
167
|
+
register(connector)
|
|
168
|
+
_load_errors.pop(name, None)
|
|
169
|
+
logger.debug("Loaded connector %r from %s", ep.name, ep.value)
|
|
170
|
+
if connector.source != name:
|
|
171
|
+
_entry_points.pop(name, None)
|
|
172
|
+
except Exception as exc:
|
|
173
|
+
_entry_points.pop(name, None)
|
|
174
|
+
_load_errors[name] = str(exc)
|
|
175
|
+
logger.warning("Failed to load connector %r: %s", ep.name, exc)
|