cortexdb-connectors 0.2.6__tar.gz → 0.2.8__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.
Files changed (46) hide show
  1. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/PKG-INFO +1 -1
  2. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/__init__.py +40 -40
  3. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/base.py +38 -9
  4. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/cli.py +2 -1
  5. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/confluence/__init__.py +359 -359
  6. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/discord/__init__.py +646 -646
  7. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/gitlab/__init__.py +749 -749
  8. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/hubspot/__init__.py +489 -489
  9. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/intercom/__init__.py +631 -631
  10. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/linear/__init__.py +705 -705
  11. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/pagerduty/__init__.py +298 -298
  12. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/salesforce/__init__.py +399 -399
  13. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/servicenow/__init__.py +460 -460
  14. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/slack/__init__.py +103 -11
  15. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/state.py +145 -145
  16. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/teams/__init__.py +656 -656
  17. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/webhooks.py +7 -0
  18. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/zendesk/__init__.py +496 -496
  19. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors.egg-info/PKG-INFO +1 -1
  20. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/pyproject.toml +87 -87
  21. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_base.py +114 -0
  22. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_slack.py +104 -7
  23. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/README.md +0 -0
  24. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/freshdesk/__init__.py +0 -0
  25. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/github/__init__.py +0 -0
  26. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/google_workspace/__init__.py +0 -0
  27. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/insights/__init__.py +0 -0
  28. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/insights/api.py +0 -0
  29. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/insights/detectors.py +0 -0
  30. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/insights/engine.py +0 -0
  31. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/jira/__init__.py +0 -0
  32. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/notion/__init__.py +0 -0
  33. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/tldv/__init__.py +0 -0
  34. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors/worker.py +0 -0
  35. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors.egg-info/SOURCES.txt +0 -0
  36. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors.egg-info/dependency_links.txt +0 -0
  37. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors.egg-info/entry_points.txt +0 -0
  38. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors.egg-info/requires.txt +0 -0
  39. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/cortexdb_connectors.egg-info/top_level.txt +0 -0
  40. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/setup.cfg +0 -0
  41. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_connector_coverage.py +0 -0
  42. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_github.py +0 -0
  43. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_ingest_e2e.py +0 -0
  44. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_streaming_cursor.py +0 -0
  45. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_webhooks.py +0 -0
  46. {cortexdb_connectors-0.2.6 → cortexdb_connectors-0.2.8}/tests/test_webhooks_replay.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cortexdb-connectors
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Data connectors for CortexDB — ingest from Slack, GitHub, GitLab, Jira, Linear, Confluence, Notion, PagerDuty, Discord, Teams, Google Workspace, Salesforce, HubSpot, Zendesk, Intercom, and ServiceNow into the v1 memory API.
5
5
  Author-email: CortexDB Team <team@cortexdb.ai>
6
6
  License-Expression: Apache-2.0
@@ -1,40 +1,40 @@
1
- """cortexdb_connectors — data connector SDK for the CortexDB v1 API.
2
-
3
- Each connector subclass implements `fetch_events`, `normalize`, and
4
- `map_permissions`; the base class POSTs every normalised Episode to
5
- `/v1/experience` with the required Authorization + X-Cortex-Actor
6
- headers. Use the `cortexdb-sync` CLI to run a connector once or in a
7
- poll loop; or `from cortexdb_connectors.slack import SlackConnector`
8
- to drive it programmatically.
9
- """
10
-
11
- __version__ = "0.2.6"
12
-
13
- from cortexdb_connectors.base import (
14
- Actor,
15
- CortexConnector,
16
- EntityRef,
17
- Episode,
18
- EpisodeType,
19
- RawEvent,
20
- Source,
21
- SyncResult,
22
- SyncState,
23
- Visibility,
24
- VisibilityLevel,
25
- )
26
-
27
- __all__ = [
28
- "__version__",
29
- "Actor",
30
- "CortexConnector",
31
- "EntityRef",
32
- "Episode",
33
- "EpisodeType",
34
- "RawEvent",
35
- "Source",
36
- "SyncResult",
37
- "SyncState",
38
- "Visibility",
39
- "VisibilityLevel",
40
- ]
1
+ """cortexdb_connectors — data connector SDK for the CortexDB v1 API.
2
+
3
+ Each connector subclass implements `fetch_events`, `normalize`, and
4
+ `map_permissions`; the base class POSTs every normalised Episode to
5
+ `/v1/experience` with the required Authorization + X-Cortex-Actor
6
+ headers. Use the `cortexdb-sync` CLI to run a connector once or in a
7
+ poll loop; or `from cortexdb_connectors.slack import SlackConnector`
8
+ to drive it programmatically.
9
+ """
10
+
11
+ __version__ = "0.2.8"
12
+
13
+ from cortexdb_connectors.base import (
14
+ Actor,
15
+ CortexConnector,
16
+ EntityRef,
17
+ Episode,
18
+ EpisodeType,
19
+ RawEvent,
20
+ Source,
21
+ SyncResult,
22
+ SyncState,
23
+ Visibility,
24
+ VisibilityLevel,
25
+ )
26
+
27
+ __all__ = [
28
+ "__version__",
29
+ "Actor",
30
+ "CortexConnector",
31
+ "EntityRef",
32
+ "Episode",
33
+ "EpisodeType",
34
+ "RawEvent",
35
+ "Source",
36
+ "SyncResult",
37
+ "SyncState",
38
+ "Visibility",
39
+ "VisibilityLevel",
40
+ ]
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
  import hashlib
14
14
  import logging
15
15
  import os
16
+ import re
16
17
  import uuid
17
18
  from abc import ABC, abstractmethod
18
19
  from dataclasses import dataclass, field
@@ -24,6 +25,11 @@ DEFAULT_API_URL = "https://api-v1.cortexdb.ai"
24
25
 
25
26
  logger = logging.getLogger("cortexdb_connectors")
26
27
 
28
+ # v1 ActorRef grammar (server: cortex-types identity.rs): `kind:local` where
29
+ # kind ∈ _ACTOR_KINDS and local matches [A-Za-z0-9_.@-]+ (no colons!).
30
+ _ACTOR_KINDS = frozenset({"user", "agent", "service", "system"})
31
+ _ACTOR_LOCAL_ILLEGAL = re.compile(r"[^A-Za-z0-9_.@-]")
32
+
27
33
  # Hard ceiling on content size sent to the server. Real items never approach
28
34
  # this; it exists only to stop a pathological payload from exceeding the request
29
35
  # body limit. When it trips we truncate on a UTF-8 boundary and log LOUDLY — we
@@ -158,6 +164,13 @@ class Episode:
158
164
  class SyncResult:
159
165
  episodes_fetched: int = 0
160
166
  episodes_ingested: int = 0
167
+ # Items the connector's content policy deliberately dropped (`normalize`
168
+ # returned None: empty/attachment-only text, emoji-only, noise subtypes).
169
+ # Counted separately so operators can tell "filtered by design" from
170
+ # "failed"; skipped items ADVANCE the cursor like successes — a skip is
171
+ # a decision, not an error, and treating it as one would stall the sync
172
+ # at the first attachment-only message.
173
+ episodes_skipped: int = 0
161
174
  errors: list[str] = field(default_factory=list)
162
175
  duration_seconds: float = 0.0
163
176
  # Watermarks for cursor-safe syncing: the latest source timestamp we
@@ -237,7 +250,13 @@ class CortexConnector(ABC):
237
250
  ...
238
251
 
239
252
  @abstractmethod
240
- def normalize(self, raw: RawEvent) -> Episode:
253
+ def normalize(self, raw: RawEvent) -> Episode | None:
254
+ """Convert a RawEvent into an Episode, or return None to SKIP it.
255
+
256
+ None means the item is dropped by content policy (nothing sent to
257
+ the server); the sync loop counts it in `episodes_skipped` and the
258
+ cursor advances past it.
259
+ """
241
260
  ...
242
261
 
243
262
  @abstractmethod
@@ -257,6 +276,13 @@ class CortexConnector(ABC):
257
276
  for raw in raw_events:
258
277
  try:
259
278
  episode = self.normalize(raw)
279
+ if episode is None:
280
+ # Content-policy skip: advance the cursor exactly like a
281
+ # success so the next incremental run never re-fetches it.
282
+ result.episodes_skipped += 1
283
+ if result.latest_success is None or raw.timestamp > result.latest_success:
284
+ result.latest_success = raw.timestamp
285
+ continue
260
286
  episode.visibility = self.map_permissions(raw)
261
287
  await self.ingest_episode(episode)
262
288
  result.episodes_ingested += 1
@@ -356,14 +382,17 @@ class CortexConnector(ABC):
356
382
  ),
357
383
  }
358
384
  if episode.actor and episode.actor.id:
359
- # v1 ActorRef requires a `kind:id` form (the server rejects a bare
360
- # id with 422: "actor id missing ':' delimiter"). Source actor ids
361
- # (Slack "U123", Jira accountId, …) are bare, so default the kind to
362
- # `user:` unless the connector already supplied a delimiter.
363
- aid = episode.actor.id
364
- if ":" not in aid:
365
- aid = f"user:{aid}"
366
- envelope["observed_actor"] = {"id": aid}
385
+ # Source actor ids are not guaranteed bare: Jira Cloud accountIds
386
+ # are `712020:<uuid>`, so a colon does NOT mean the connector
387
+ # already supplied a kind. Treat the prefix as a kind only when it
388
+ # is one the server accepts; otherwise the whole source id is the
389
+ # local part under `user:`, sanitized to the server's charset
390
+ # (which forbids ':' anywhere in the local part).
391
+ kind, sep, local = episode.actor.id.partition(":")
392
+ if not sep or kind not in _ACTOR_KINDS or not local:
393
+ kind, local = "user", episode.actor.id
394
+ local = _ACTOR_LOCAL_ILLEGAL.sub("_", local)
395
+ envelope["observed_actor"] = {"id": f"{kind}:{local}"}
367
396
 
368
397
  return envelope
369
398
 
@@ -414,9 +414,10 @@ def _cmd_sync(args: argparse.Namespace) -> None:
414
414
  state_store.update_last_synced(args.connector, args.scope, timestamp=new_cursor)
415
415
 
416
416
  logger.info(
417
- "sync done fetched=%d ingested=%d errors=%d duration=%.1fs",
417
+ "sync done fetched=%d ingested=%d skipped=%d errors=%d duration=%.1fs",
418
418
  result.episodes_fetched,
419
419
  result.episodes_ingested,
420
+ result.episodes_skipped,
420
421
  len(result.errors),
421
422
  result.duration_seconds,
422
423
  )