cogspace 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cogspace/__init__.py ADDED
@@ -0,0 +1,203 @@
1
+ """
2
+ Cogspace SDK — add a knowledge layer to any AI agent.
3
+
4
+ Quickstart (sync):
5
+ from cogspace import Cogspace
6
+
7
+ cog = Cogspace(api_key="cs-...")
8
+ space = cog.space("my-agent")
9
+ results = space.search("retry logic with backoff")
10
+ space.write("expertise/retry.md", "# Retry patterns\\n...")
11
+ context = space.hot_layer()
12
+
13
+ Quickstart (async):
14
+ from cogspace import AsyncCogspace
15
+
16
+ cog = AsyncCogspace(api_key="cs-...")
17
+ space = cog.space("my-agent")
18
+ results = await space.search("retry logic with backoff")
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Optional
24
+
25
+ from cogspace._async_client import AsyncClient
26
+ from cogspace._async_space import AsyncSpaceClient
27
+ from cogspace._client import SyncClient
28
+ from cogspace._space import SpaceClient
29
+ from cogspace.exceptions import (
30
+ AuthError,
31
+ CogspaceError,
32
+ NotFoundError,
33
+ RateLimitError,
34
+ ServerError,
35
+ TimeoutError,
36
+ )
37
+ from cogspace.types import (
38
+ ContextFile,
39
+ GraphEdge,
40
+ GraphNode,
41
+ GraphTraverseResponse,
42
+ HealthResponse,
43
+ ListResponse,
44
+ ReadContextResponse,
45
+ ReadResponse,
46
+ SearchItem,
47
+ SearchResponse,
48
+ Space,
49
+ WriteResponse,
50
+ )
51
+
52
+ DEFAULT_BASE_URL = "https://platform.cogspace.ai"
53
+
54
+
55
+ class Cogspace:
56
+ """
57
+ Sync Cogspace client.
58
+
59
+ Args:
60
+ api_key: Your Cogspace API key (starts with 'cs-').
61
+ base_url: Platform URL. Defaults to https://platform.cogspace.ai.
62
+ timeout: Request timeout in seconds. Default 30.
63
+ max_retries: Number of retries on 429/5xx. Default 3.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ api_key: str,
69
+ base_url: str = DEFAULT_BASE_URL,
70
+ timeout: float = 30.0,
71
+ max_retries: int = 3,
72
+ ) -> None:
73
+ self._client = SyncClient(
74
+ api_key=api_key,
75
+ base_url=base_url,
76
+ timeout=timeout,
77
+ max_retries=max_retries,
78
+ )
79
+ self._space_cache: dict[str, str] = {} # name -> id
80
+
81
+ def _resolve_space_id(self, space_name_or_id: str) -> str:
82
+ """Resolve a space name to its ID, caching the result."""
83
+ if space_name_or_id in self._space_cache:
84
+ return self._space_cache[space_name_or_id]
85
+ spaces = self._client.get("/spaces")
86
+ for s in spaces:
87
+ if s["name"] == space_name_or_id or s["id"] == space_name_or_id:
88
+ self._space_cache[s["name"]] = s["id"]
89
+ self._space_cache[s["id"]] = s["id"]
90
+ return s["id"]
91
+ raise NotFoundError(f"Space not found: {space_name_or_id!r}")
92
+
93
+ def space(self, name_or_id: str) -> SpaceClient:
94
+ """Get a space-scoped client by space name or ID."""
95
+ space_id = self._resolve_space_id(name_or_id)
96
+ return SpaceClient(self._client, space_id)
97
+
98
+ def list_spaces(self) -> list[Space]:
99
+ """List all your spaces."""
100
+ data = self._client.get("/spaces")
101
+ return [Space.model_validate(s) for s in data]
102
+
103
+ def create_space(self, name: str) -> Space:
104
+ """Create a new space."""
105
+ data = self._client.post("/spaces", json={"name": name})
106
+ return Space.model_validate(data)
107
+
108
+ def close(self) -> None:
109
+ self._client.close()
110
+
111
+ def __enter__(self) -> "Cogspace":
112
+ return self
113
+
114
+ def __exit__(self, *_) -> None:
115
+ self.close()
116
+
117
+
118
+ class AsyncCogspace:
119
+ """
120
+ Async Cogspace client.
121
+
122
+ Args:
123
+ api_key: Your Cogspace API key (starts with 'cs-').
124
+ base_url: Platform URL. Defaults to https://platform.cogspace.ai.
125
+ timeout: Request timeout in seconds. Default 30.
126
+ max_retries: Number of retries on 429/5xx. Default 3.
127
+ """
128
+
129
+ def __init__(
130
+ self,
131
+ api_key: str,
132
+ base_url: str = DEFAULT_BASE_URL,
133
+ timeout: float = 30.0,
134
+ max_retries: int = 3,
135
+ ) -> None:
136
+ self._client = AsyncClient(
137
+ api_key=api_key,
138
+ base_url=base_url,
139
+ timeout=timeout,
140
+ max_retries=max_retries,
141
+ )
142
+ self._space_cache: dict[str, str] = {}
143
+
144
+ async def _resolve_space_id(self, space_name_or_id: str) -> str:
145
+ if space_name_or_id in self._space_cache:
146
+ return self._space_cache[space_name_or_id]
147
+ spaces = await self._client.get("/spaces")
148
+ for s in spaces:
149
+ if s["name"] == space_name_or_id or s["id"] == space_name_or_id:
150
+ self._space_cache[s["name"]] = s["id"]
151
+ self._space_cache[s["id"]] = s["id"]
152
+ return s["id"]
153
+ raise NotFoundError(f"Space not found: {space_name_or_id!r}")
154
+
155
+ async def space(self, name_or_id: str) -> AsyncSpaceClient:
156
+ """Get a space-scoped async client by space name or ID."""
157
+ space_id = await self._resolve_space_id(name_or_id)
158
+ return AsyncSpaceClient(self._client, space_id)
159
+
160
+ async def list_spaces(self) -> list[Space]:
161
+ data = await self._client.get("/spaces")
162
+ return [Space.model_validate(s) for s in data]
163
+
164
+ async def create_space(self, name: str) -> Space:
165
+ data = await self._client.post("/spaces", json={"name": name})
166
+ return Space.model_validate(data)
167
+
168
+ async def aclose(self) -> None:
169
+ await self._client.aclose()
170
+
171
+ async def __aenter__(self) -> "AsyncCogspace":
172
+ return self
173
+
174
+ async def __aexit__(self, *_) -> None:
175
+ await self.aclose()
176
+
177
+
178
+ __all__ = [
179
+ "Cogspace",
180
+ "AsyncCogspace",
181
+ "SpaceClient",
182
+ "AsyncSpaceClient",
183
+ "CogspaceError",
184
+ "AuthError",
185
+ "NotFoundError",
186
+ "RateLimitError",
187
+ "ServerError",
188
+ "TimeoutError",
189
+ "SearchResponse",
190
+ "SearchItem",
191
+ "ReadResponse",
192
+ "WriteResponse",
193
+ "ListResponse",
194
+ "ReadContextResponse",
195
+ "ContextFile",
196
+ "GraphTraverseResponse",
197
+ "GraphNode",
198
+ "GraphEdge",
199
+ "HealthResponse",
200
+ "Space",
201
+ ]
202
+
203
+ __version__ = "0.1.0"
@@ -0,0 +1,93 @@
1
+ """Async HTTP client for the Cogspace SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from cogspace.exceptions import RateLimitError, TimeoutError, _raise_for_status
11
+
12
+ DEFAULT_BASE_URL = "https://platform.cogspace.ai"
13
+ DEFAULT_TIMEOUT = 30.0
14
+ DEFAULT_MAX_RETRIES = 3
15
+ _RETRY_STATUSES = {429, 500, 502, 503, 504}
16
+
17
+
18
+ class AsyncClient:
19
+ def __init__(
20
+ self,
21
+ api_key: str,
22
+ base_url: str = DEFAULT_BASE_URL,
23
+ timeout: float = DEFAULT_TIMEOUT,
24
+ max_retries: int = DEFAULT_MAX_RETRIES,
25
+ ) -> None:
26
+ self._api_key = api_key
27
+ self._base_url = base_url.rstrip("/")
28
+ self._timeout = timeout
29
+ self._max_retries = max_retries
30
+ self._http = httpx.AsyncClient(
31
+ base_url=self._base_url,
32
+ headers={"Authorization": f"Bearer {api_key}", "User-Agent": "cogspace-python/0.1.0"},
33
+ timeout=timeout,
34
+ )
35
+
36
+ async def aclose(self) -> None:
37
+ await self._http.aclose()
38
+
39
+ async def __aenter__(self) -> "AsyncClient":
40
+ return self
41
+
42
+ async def __aexit__(self, *_: Any) -> None:
43
+ await self.aclose()
44
+
45
+ async def _request(self, method: str, path: str, **kwargs: Any) -> dict:
46
+ last_exc: Exception | None = None
47
+ for attempt in range(self._max_retries):
48
+ try:
49
+ response = await self._http.request(method, path, **kwargs)
50
+ except httpx.TimeoutException as exc:
51
+ raise TimeoutError(f"Request timed out: {path}") from exc
52
+
53
+ if response.status_code not in _RETRY_STATUSES:
54
+ if response.status_code >= 400:
55
+ try:
56
+ body = response.json()
57
+ except Exception:
58
+ body = {}
59
+ _raise_for_status(response.status_code, body)
60
+ return response.json() if response.content else {}
61
+
62
+ if attempt < self._max_retries - 1:
63
+ wait = 2 ** attempt
64
+ if response.status_code == 429:
65
+ retry_after = response.headers.get("Retry-After")
66
+ if retry_after:
67
+ wait = float(retry_after)
68
+ await asyncio.sleep(wait)
69
+ try:
70
+ body = response.json()
71
+ except Exception:
72
+ body = {}
73
+ last_exc = RateLimitError(
74
+ body.get("detail", f"HTTP {response.status_code}"),
75
+ status=response.status_code,
76
+ )
77
+ else:
78
+ try:
79
+ body = response.json()
80
+ except Exception:
81
+ body = {}
82
+ _raise_for_status(response.status_code, body)
83
+
84
+ raise last_exc or RateLimitError("Max retries exceeded")
85
+
86
+ async def get(self, path: str, params: dict | None = None) -> dict:
87
+ return await self._request("GET", path, params=params)
88
+
89
+ async def post(self, path: str, json: dict | None = None) -> dict:
90
+ return await self._request("POST", path, json=json)
91
+
92
+ async def delete(self, path: str) -> dict:
93
+ return await self._request("DELETE", path)
@@ -0,0 +1,105 @@
1
+ """AsyncSpaceClient — async space-scoped operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, Optional
6
+
7
+ from cogspace._async_client import AsyncClient
8
+ from cogspace.types import (
9
+ GraphTraverseResponse,
10
+ HealthResponse,
11
+ ListResponse,
12
+ ReadContextResponse,
13
+ ReadResponse,
14
+ SearchResponse,
15
+ WriteResponse,
16
+ )
17
+
18
+
19
+ class AsyncSpaceClient:
20
+ """Async client scoped to a single Cogspace space."""
21
+
22
+ def __init__(self, client: AsyncClient, space_id: str) -> None:
23
+ self._c = client
24
+ self._space_id = space_id
25
+
26
+ def _path(self, endpoint: str) -> str:
27
+ return f"/spaces/{self._space_id}/{endpoint}"
28
+
29
+ async def search(
30
+ self,
31
+ query: str,
32
+ mode: Literal["hybrid", "vector", "bm25"] = "hybrid",
33
+ top_k: int = 5,
34
+ layer: Optional[str] = None,
35
+ min_score: float = 0.0,
36
+ ) -> SearchResponse:
37
+ endpoint = {
38
+ "hybrid": "search/semantic",
39
+ "vector": "search/semantic",
40
+ "bm25": "search/keyword",
41
+ }[mode]
42
+ data = await self._c.post(self._path(endpoint), json={
43
+ "query": query,
44
+ "top_k": top_k,
45
+ "layer": layer,
46
+ "min_score": min_score,
47
+ "mode": mode,
48
+ })
49
+ return SearchResponse.model_validate(data)
50
+
51
+ async def read(self, path: str) -> ReadResponse:
52
+ data = await self._c.get(self._path("read"), params={"path": path})
53
+ return ReadResponse.model_validate(data)
54
+
55
+ async def write(
56
+ self,
57
+ path: str,
58
+ content: str,
59
+ metadata: Optional[dict] = None,
60
+ agent_id: str = "default",
61
+ ) -> WriteResponse:
62
+ data = await self._c.post(self._path("write"), json={
63
+ "path": path,
64
+ "content": content,
65
+ "metadata": metadata,
66
+ "agent_id": agent_id,
67
+ })
68
+ return WriteResponse.model_validate(data)
69
+
70
+ async def list(self, folder: str = "") -> ListResponse:
71
+ data = await self._c.get(self._path("list"), params={"folder": folder})
72
+ return ListResponse.model_validate(data)
73
+
74
+ async def read_memory(self) -> ReadResponse:
75
+ data = await self._c.get(self._path("memory"))
76
+ return ReadResponse.model_validate(data)
77
+
78
+ async def write_memory(self, content: str, confidence: float = 0.9) -> WriteResponse:
79
+ data = await self._c.post(self._path("memory"), json={"content": content, "confidence": confidence})
80
+ return WriteResponse.model_validate(data)
81
+
82
+ async def read_context(self) -> ReadContextResponse:
83
+ data = await self._c.get(self._path("memory/context"))
84
+ return ReadContextResponse.model_validate(data)
85
+
86
+ async def graph_traverse(
87
+ self,
88
+ path: str,
89
+ depth: int = 2,
90
+ rel_type: Optional[str] = None,
91
+ ) -> GraphTraverseResponse:
92
+ data = await self._c.post(self._path("graph/traverse"), json={
93
+ "path": path,
94
+ "depth": depth,
95
+ "rel_type": rel_type,
96
+ })
97
+ return GraphTraverseResponse.model_validate(data)
98
+
99
+ async def hot_layer(self) -> str:
100
+ data = await self._c.get(self._path("hot-layer"))
101
+ return data["hot_layer"]
102
+
103
+ async def health(self) -> HealthResponse:
104
+ data = await self._c.get(self._path("health"))
105
+ return HealthResponse.model_validate(data)
cogspace/_client.py ADDED
@@ -0,0 +1,96 @@
1
+ """Sync HTTP client for the Cogspace SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import Any, TypeVar
7
+
8
+ import httpx
9
+
10
+ from cogspace.exceptions import RateLimitError, TimeoutError, _raise_for_status
11
+
12
+ T = TypeVar("T")
13
+
14
+ DEFAULT_BASE_URL = "https://platform.cogspace.ai"
15
+ DEFAULT_TIMEOUT = 30.0
16
+ DEFAULT_MAX_RETRIES = 3
17
+ _RETRY_STATUSES = {429, 500, 502, 503, 504}
18
+
19
+
20
+ class SyncClient:
21
+ def __init__(
22
+ self,
23
+ api_key: str,
24
+ base_url: str = DEFAULT_BASE_URL,
25
+ timeout: float = DEFAULT_TIMEOUT,
26
+ max_retries: int = DEFAULT_MAX_RETRIES,
27
+ ) -> None:
28
+ self._api_key = api_key
29
+ self._base_url = base_url.rstrip("/")
30
+ self._timeout = timeout
31
+ self._max_retries = max_retries
32
+ self._http = httpx.Client(
33
+ base_url=self._base_url,
34
+ headers={"Authorization": f"Bearer {api_key}", "User-Agent": "cogspace-python/0.1.0"},
35
+ timeout=timeout,
36
+ )
37
+
38
+ def close(self) -> None:
39
+ self._http.close()
40
+
41
+ def __enter__(self) -> "SyncClient":
42
+ return self
43
+
44
+ def __exit__(self, *_: Any) -> None:
45
+ self.close()
46
+
47
+ def _request(self, method: str, path: str, **kwargs: Any) -> dict:
48
+ last_exc: Exception | None = None
49
+ for attempt in range(self._max_retries):
50
+ try:
51
+ response = self._http.request(method, path, **kwargs)
52
+ except httpx.TimeoutException as exc:
53
+ raise TimeoutError(f"Request timed out: {path}") from exc
54
+
55
+ if response.status_code not in _RETRY_STATUSES:
56
+ if response.status_code >= 400:
57
+ try:
58
+ body = response.json()
59
+ except Exception:
60
+ body = {}
61
+ _raise_for_status(response.status_code, body)
62
+ return response.json() if response.content else {}
63
+
64
+ # Retryable status
65
+ if attempt < self._max_retries - 1:
66
+ wait = 2 ** attempt
67
+ if response.status_code == 429:
68
+ retry_after = response.headers.get("Retry-After")
69
+ if retry_after:
70
+ wait = float(retry_after)
71
+ time.sleep(wait)
72
+ try:
73
+ body = response.json()
74
+ except Exception:
75
+ body = {}
76
+ last_exc = RateLimitError(
77
+ body.get("detail", f"HTTP {response.status_code}"),
78
+ status=response.status_code,
79
+ )
80
+ else:
81
+ try:
82
+ body = response.json()
83
+ except Exception:
84
+ body = {}
85
+ _raise_for_status(response.status_code, body)
86
+
87
+ raise last_exc or RateLimitError("Max retries exceeded")
88
+
89
+ def get(self, path: str, params: dict | None = None) -> dict:
90
+ return self._request("GET", path, params=params)
91
+
92
+ def post(self, path: str, json: dict | None = None) -> dict:
93
+ return self._request("POST", path, json=json)
94
+
95
+ def delete(self, path: str) -> dict:
96
+ return self._request("DELETE", path)
cogspace/_space.py ADDED
@@ -0,0 +1,127 @@
1
+ """SpaceClient — sync space-scoped operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, Optional
6
+
7
+ from cogspace._client import SyncClient
8
+ from cogspace.types import (
9
+ GraphTraverseResponse,
10
+ HealthResponse,
11
+ ListResponse,
12
+ ReadContextResponse,
13
+ ReadResponse,
14
+ SearchResponse,
15
+ WriteResponse,
16
+ )
17
+
18
+
19
+ class SpaceClient:
20
+ """Sync client scoped to a single Cogspace space."""
21
+
22
+ def __init__(self, client: SyncClient, space_id: str) -> None:
23
+ self._c = client
24
+ self._space_id = space_id
25
+
26
+ def _path(self, endpoint: str) -> str:
27
+ return f"/spaces/{self._space_id}/{endpoint}"
28
+
29
+ # ── Search ────────────────────────────────────────────────────────────────
30
+
31
+ def search(
32
+ self,
33
+ query: str,
34
+ mode: Literal["hybrid", "vector", "bm25"] = "hybrid",
35
+ top_k: int = 5,
36
+ layer: Optional[str] = None,
37
+ min_score: float = 0.0,
38
+ ) -> SearchResponse:
39
+ """Search the knowledge space."""
40
+ endpoint = {
41
+ "hybrid": "search/semantic",
42
+ "vector": "search/semantic",
43
+ "bm25": "search/keyword",
44
+ }[mode]
45
+ data = self._c.post(self._path(endpoint), json={
46
+ "query": query,
47
+ "top_k": top_k,
48
+ "layer": layer,
49
+ "min_score": min_score,
50
+ "mode": mode,
51
+ })
52
+ return SearchResponse.model_validate(data)
53
+
54
+ # ── Core ──────────────────────────────────────────────────────────────────
55
+
56
+ def read(self, path: str) -> ReadResponse:
57
+ """Read a file by path."""
58
+ data = self._c.get(self._path("read"), params={"path": path})
59
+ return ReadResponse.model_validate(data)
60
+
61
+ def write(
62
+ self,
63
+ path: str,
64
+ content: str,
65
+ metadata: Optional[dict] = None,
66
+ agent_id: str = "default",
67
+ ) -> WriteResponse:
68
+ """Write (create or update) a file."""
69
+ data = self._c.post(self._path("write"), json={
70
+ "path": path,
71
+ "content": content,
72
+ "metadata": metadata,
73
+ "agent_id": agent_id,
74
+ })
75
+ return WriteResponse.model_validate(data)
76
+
77
+ def list(self, folder: str = "") -> ListResponse:
78
+ """List files in a folder."""
79
+ data = self._c.get(self._path("list"), params={"folder": folder})
80
+ return ListResponse.model_validate(data)
81
+
82
+ # ── Memory ────────────────────────────────────────────────────────────────
83
+
84
+ def read_memory(self) -> ReadResponse:
85
+ """Read the agent's memory.md."""
86
+ data = self._c.get(self._path("memory"))
87
+ return ReadResponse.model_validate(data)
88
+
89
+ def write_memory(self, content: str, confidence: float = 0.9) -> WriteResponse:
90
+ """Update the agent's memory."""
91
+ data = self._c.post(self._path("memory"), json={"content": content, "confidence": confidence})
92
+ return WriteResponse.model_validate(data)
93
+
94
+ def read_context(self) -> ReadContextResponse:
95
+ """Read user context files (memory/user/)."""
96
+ data = self._c.get(self._path("memory/context"))
97
+ return ReadContextResponse.model_validate(data)
98
+
99
+ # ── Graph ─────────────────────────────────────────────────────────────────
100
+
101
+ def graph_traverse(
102
+ self,
103
+ path: str,
104
+ depth: int = 2,
105
+ rel_type: Optional[str] = None,
106
+ ) -> GraphTraverseResponse:
107
+ """Traverse the knowledge graph from a file."""
108
+ data = self._c.post(self._path("graph/traverse"), json={
109
+ "path": path,
110
+ "depth": depth,
111
+ "rel_type": rel_type,
112
+ })
113
+ return GraphTraverseResponse.model_validate(data)
114
+
115
+ # ── Hot layer ─────────────────────────────────────────────────────────────
116
+
117
+ def hot_layer(self) -> str:
118
+ """Get the full hot layer injection string (always-injected context)."""
119
+ data = self._c.get(self._path("hot-layer"))
120
+ return data["hot_layer"]
121
+
122
+ # ── Health ────────────────────────────────────────────────────────────────
123
+
124
+ def health(self) -> HealthResponse:
125
+ """Get space health and storage layer stats."""
126
+ data = self._c.get(self._path("health"))
127
+ return HealthResponse.model_validate(data)
cogspace/exceptions.py ADDED
@@ -0,0 +1,46 @@
1
+ """Cogspace SDK exception hierarchy."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class CogspaceError(Exception):
7
+ """Base exception for all Cogspace SDK errors."""
8
+
9
+ def __init__(self, message: str, status: int | None = None, code: str | None = None) -> None:
10
+ super().__init__(message)
11
+ self.status = status
12
+ self.code = code
13
+
14
+
15
+ class AuthError(CogspaceError):
16
+ """Invalid or revoked API key."""
17
+
18
+
19
+ class NotFoundError(CogspaceError):
20
+ """Requested resource does not exist."""
21
+
22
+
23
+ class RateLimitError(CogspaceError):
24
+ """Rate limit exceeded. The SDK will retry automatically."""
25
+
26
+
27
+ class ServerError(CogspaceError):
28
+ """Unexpected server-side error (5xx)."""
29
+
30
+
31
+ class TimeoutError(CogspaceError):
32
+ """Request timed out."""
33
+
34
+
35
+ def _raise_for_status(status: int, body: dict) -> None:
36
+ message = body.get("detail") or body.get("message") or f"HTTP {status}"
37
+ code = body.get("error")
38
+ if status == 401:
39
+ raise AuthError(message, status=status, code=code)
40
+ if status == 404:
41
+ raise NotFoundError(message, status=status, code=code)
42
+ if status == 429:
43
+ raise RateLimitError(message, status=status, code=code)
44
+ if status >= 500:
45
+ raise ServerError(message, status=status, code=code)
46
+ raise CogspaceError(message, status=status, code=code)
cogspace/types.py ADDED
@@ -0,0 +1,113 @@
1
+ """Response types for the Cogspace SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal, Optional
6
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class SearchItem(BaseModel):
11
+ file_path: str
12
+ content: str
13
+ score: float
14
+ layer: str
15
+ file_type: str
16
+ topic: str
17
+ confidence: float
18
+
19
+
20
+ class SearchResponse(BaseModel):
21
+ results: list[SearchItem]
22
+ total: int
23
+ query: str
24
+ mode: str
25
+
26
+
27
+ class ReadResponse(BaseModel):
28
+ path: str
29
+ frontmatter: dict[str, Any]
30
+ content: str
31
+ layer: str
32
+ size_bytes: int
33
+
34
+
35
+ class WriteResponse(BaseModel):
36
+ path: str
37
+ status: Literal["created", "updated"]
38
+ reindex_queued: bool
39
+
40
+
41
+ class FileTreeNode(BaseModel):
42
+ model_config = {"extra": "allow"}
43
+
44
+
45
+ class ListResponse(BaseModel):
46
+ tree: dict[str, Any]
47
+ file_count: int
48
+ folder: str
49
+
50
+
51
+ class ContextFile(BaseModel):
52
+ path: str
53
+ content: str
54
+ frontmatter: dict[str, Any]
55
+ confidence: float
56
+
57
+
58
+ class ReadContextResponse(BaseModel):
59
+ files: list[ContextFile]
60
+ count: int
61
+ folder: str
62
+
63
+
64
+ class GraphNode(BaseModel):
65
+ path: str
66
+ file_type: str
67
+ topic: str
68
+ summary: Optional[str] = None
69
+
70
+
71
+ class GraphEdge(BaseModel):
72
+ from_: str
73
+ to: str
74
+ rel_type: str
75
+
76
+ class Config:
77
+ populate_by_name = True
78
+ fields = {"from_": "from"}
79
+
80
+
81
+ class GraphTraverseResponse(BaseModel):
82
+ source: str
83
+ depth: int
84
+ rel_type: Optional[str] = None
85
+ nodes: list[GraphNode]
86
+ edges: list[GraphEdge]
87
+ graph_available: bool
88
+
89
+
90
+ class HealthResponse(BaseModel):
91
+ status: str
92
+ space_id: str
93
+ vector_count: int
94
+ bm25_doc_count: int
95
+ graph_available: bool
96
+ graph_node_count: int = 0
97
+ graph_edge_count: int = 0
98
+ hot_layer_files: dict[str, bool] = {}
99
+
100
+
101
+ class Space(BaseModel):
102
+ id: str
103
+ user_id: str
104
+ name: str
105
+ created_at: str
106
+
107
+
108
+ class ApiKey(BaseModel):
109
+ id: str
110
+ name: str
111
+ key_prefix: str
112
+ created_at: str
113
+ last_used: Optional[str] = None
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: cogspace
3
+ Version: 0.1.0
4
+ Summary: Official Cogspace SDK — add a knowledge layer to any AI agent
5
+ Project-URL: Homepage, https://cogspace.ai
6
+ Project-URL: Documentation, https://docs.cogspace.ai
7
+ Project-URL: Repository, https://github.com/Jack-Pision/cogspace-ai
8
+ Project-URL: Issues, https://github.com/Jack-Pision/cogspace-ai/issues
9
+ Author-email: Cogspace <sdk@cogspace.ai>
10
+ License: MIT
11
+ Keywords: agents,ai,knowledge,memory,rag,sdk
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: pydantic>=2.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
26
+ Requires-Dist: pytest>=8; extra == 'dev'
27
+ Requires-Dist: respx>=0.21; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # cogspace
31
+
32
+ Official Python SDK for [Cogspace](https://cogspace.ai) — add a persistent knowledge layer to any AI agent.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install cogspace
38
+ ```
39
+
40
+ ## Quickstart
41
+
42
+ ```python
43
+ from cogspace import Cogspace
44
+
45
+ cog = Cogspace(api_key="cs-...")
46
+
47
+ # Get a space-scoped client
48
+ space = cog.space("my-agent")
49
+
50
+ # Search
51
+ results = space.search("retry logic with backoff")
52
+ for r in results.results:
53
+ print(r.file_path, r.score)
54
+
55
+ # Write knowledge
56
+ space.write("expertise/retry.md", """
57
+ # Retry patterns
58
+ Always use exponential backoff with jitter.
59
+ """)
60
+
61
+ # Read the hot layer (always-injected context)
62
+ context = space.hot_layer()
63
+
64
+ # Memory
65
+ space.write_memory("Currently working on the payment service refactor.")
66
+ memory = space.read_memory()
67
+ ```
68
+
69
+ ## Async
70
+
71
+ ```python
72
+ from cogspace import AsyncCogspace
73
+
74
+ async def main():
75
+ cog = AsyncCogspace(api_key="cs-...")
76
+ space = await cog.space("my-agent")
77
+ results = await space.search("retry logic")
78
+ await cog.aclose()
79
+ ```
80
+
81
+ Or use as a context manager:
82
+
83
+ ```python
84
+ async with AsyncCogspace(api_key="cs-...") as cog:
85
+ space = await cog.space("my-agent")
86
+ await space.write("expertise/notes.md", "...")
87
+ ```
88
+
89
+ ## API Reference
90
+
91
+ ### `Cogspace(api_key, base_url, timeout, max_retries)`
92
+
93
+ | Method | Description |
94
+ |---|---|
95
+ | `cog.space(name_or_id)` | Get a space client |
96
+ | `cog.list_spaces()` | List all your spaces |
97
+ | `cog.create_space(name)` | Create a new space |
98
+
99
+ ### `SpaceClient`
100
+
101
+ | Method | Description |
102
+ |---|---|
103
+ | `space.search(query, mode, top_k, layer)` | Hybrid / vector / keyword search |
104
+ | `space.read(path)` | Read a file by path |
105
+ | `space.write(path, content, metadata)` | Write or update a file |
106
+ | `space.list(folder)` | List files in a folder |
107
+ | `space.read_memory()` | Read `memory.md` |
108
+ | `space.write_memory(content, confidence)` | Update `memory.md` |
109
+ | `space.read_context()` | Read `memory/user/` context files |
110
+ | `space.hot_layer()` | Get full hot layer injection string |
111
+ | `space.graph_traverse(path, depth, rel_type)` | Traverse knowledge graph |
112
+ | `space.health()` | Space health and storage stats |
113
+
114
+ ## Errors
115
+
116
+ All exceptions inherit from `CogspaceError`:
117
+
118
+ ```python
119
+ from cogspace.exceptions import AuthError, NotFoundError, RateLimitError
120
+
121
+ try:
122
+ space.search("query")
123
+ except AuthError:
124
+ print("Invalid API key")
125
+ except NotFoundError:
126
+ print("Space not found")
127
+ ```
128
+
129
+ ## Get an API key
130
+
131
+ Sign in at [platform.cogspace.ai](https://platform.cogspace.ai), go to **Settings → API keys**, and create a key.
@@ -0,0 +1,10 @@
1
+ cogspace/__init__.py,sha256=CebMSK2BU5iCT2wYDFXmjrgWPgcXxvwMnftjJONFcD4,5843
2
+ cogspace/_async_client.py,sha256=g01gz6hV74OUdFYR6saKjbubojq6sry3VTQnAc-SRGs,3217
3
+ cogspace/_async_space.py,sha256=ERhvMlKvO5Rzl3JqebdqDkmj9jJJ4RrBBgYtZpPbKEI,3380
4
+ cogspace/_client.py,sha256=c99BFCE8ZxZPvG-0wwg6vPG4S31p6A7-6Y_yqYzsOGQ,3172
5
+ cogspace/_space.py,sha256=NOgWW53aOL25lOH2sUEX_0SW23hcXOa9ce8RX1g4X_o,5016
6
+ cogspace/exceptions.py,sha256=wv03Dn6WouEhViucQnW3YRQIqRDrnWZu-69I1F16_8k,1315
7
+ cogspace/types.py,sha256=l9J612EqUoXe_PA35h913w1-8bKMgy-DeeLbk9HngC4,1968
8
+ cogspace-0.1.0.dist-info/METADATA,sha256=97wFXYhup6cPSbMhGfxOIKBseoZFjJA7qLbe9TpiX24,3679
9
+ cogspace-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
10
+ cogspace-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any