copass-core 0.1.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.
- copass_core-0.1.0/.gitignore +38 -0
- copass_core-0.1.0/PKG-INFO +102 -0
- copass_core-0.1.0/README.md +78 -0
- copass_core-0.1.0/pyproject.toml +52 -0
- copass_core-0.1.0/src/copass_core/__init__.py +99 -0
- copass_core-0.1.0/src/copass_core/auth/__init__.py +12 -0
- copass_core-0.1.0/src/copass_core/auth/api_key.py +27 -0
- copass_core-0.1.0/src/copass_core/auth/bearer.py +36 -0
- copass_core-0.1.0/src/copass_core/auth/types.py +47 -0
- copass_core-0.1.0/src/copass_core/client.py +119 -0
- copass_core-0.1.0/src/copass_core/http/__init__.py +31 -0
- copass_core-0.1.0/src/copass_core/http/errors.py +60 -0
- copass_core-0.1.0/src/copass_core/http/http_client.py +171 -0
- copass_core-0.1.0/src/copass_core/http/retry.py +77 -0
- copass_core-0.1.0/src/copass_core/resources/__init__.py +12 -0
- copass_core-0.1.0/src/copass_core/resources/base.py +78 -0
- copass_core-0.1.0/src/copass_core/resources/context.py +74 -0
- copass_core-0.1.0/src/copass_core/resources/retrieval.py +147 -0
- copass_core-0.1.0/src/copass_core/types.py +77 -0
- copass_core-0.1.0/tests/test_auth.py +45 -0
- copass_core-0.1.0/tests/test_http_client.py +141 -0
- copass_core-0.1.0/tests/test_retry.py +72 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
*.tsbuildinfo
|
|
7
|
+
|
|
8
|
+
# Environment
|
|
9
|
+
.env
|
|
10
|
+
.env.*
|
|
11
|
+
|
|
12
|
+
# IDE
|
|
13
|
+
.vscode/
|
|
14
|
+
.idea/
|
|
15
|
+
*.swp
|
|
16
|
+
*.swo
|
|
17
|
+
*~
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# Test
|
|
24
|
+
coverage/
|
|
25
|
+
|
|
26
|
+
# Lerna
|
|
27
|
+
lerna-debug.log
|
|
28
|
+
.nx/cache
|
|
29
|
+
.nx/workspace-data
|
|
30
|
+
|
|
31
|
+
# Python
|
|
32
|
+
__pycache__/
|
|
33
|
+
*.pyc
|
|
34
|
+
*.pyo
|
|
35
|
+
*.egg-info/
|
|
36
|
+
.venv/
|
|
37
|
+
venv/
|
|
38
|
+
.olane
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: copass-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core client SDK for the Copass platform (Python mirror of @copass/core)
|
|
5
|
+
Project-URL: Homepage, https://github.com/olane-labs/copass-harness
|
|
6
|
+
Project-URL: Repository, https://github.com/olane-labs/copass-harness.git
|
|
7
|
+
Author: Olane Inc.
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: client,copass,knowledge-graph,retrieval,sdk
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# copass-core
|
|
26
|
+
|
|
27
|
+
Core client SDK for the Copass platform. Python mirror of [`@copass/core`](../../typescript/packages/core) — shared foundation for every Python Copass adapter.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install copass-core
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires `httpx>=0.27`. Python ≥ 3.10.
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import asyncio
|
|
41
|
+
from copass_core import CopassClient, ApiKeyAuth
|
|
42
|
+
|
|
43
|
+
async def main():
|
|
44
|
+
client = CopassClient(auth=ApiKeyAuth(key="olk_..."))
|
|
45
|
+
|
|
46
|
+
# Retrieval
|
|
47
|
+
menu = await client.retrieval.discover(
|
|
48
|
+
sandbox_id="sb_...",
|
|
49
|
+
query="How does auth work?",
|
|
50
|
+
)
|
|
51
|
+
print(menu["items"])
|
|
52
|
+
|
|
53
|
+
# Context for agent
|
|
54
|
+
context = await client.context.for_agent(
|
|
55
|
+
sandbox_id="sb_...",
|
|
56
|
+
tier="adaptive",
|
|
57
|
+
query="auth flow",
|
|
58
|
+
)
|
|
59
|
+
print(context)
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Auth options
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from copass_core import CopassClient, ApiKeyAuth, BearerAuth, ProviderAuth
|
|
68
|
+
|
|
69
|
+
# Long-lived API key (olk_ prefix)
|
|
70
|
+
CopassClient(auth=ApiKeyAuth(key="olk_..."))
|
|
71
|
+
|
|
72
|
+
# Raw Bearer JWT (caller owns refresh)
|
|
73
|
+
CopassClient(auth=BearerAuth(token="eyJ..."))
|
|
74
|
+
|
|
75
|
+
# Custom AuthProvider implementation
|
|
76
|
+
class MyProvider:
|
|
77
|
+
async def get_session(self):
|
|
78
|
+
from copass_core import SessionContext
|
|
79
|
+
return SessionContext(access_token=await _mint_token())
|
|
80
|
+
|
|
81
|
+
CopassClient(auth=ProviderAuth(provider=MyProvider()))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## v0.1.0 scope
|
|
85
|
+
|
|
86
|
+
**Shipped:**
|
|
87
|
+
- `CopassClient` top-level entry point
|
|
88
|
+
- Auth providers: `ApiKeyAuthProvider`, `BearerAuthProvider`
|
|
89
|
+
- `HttpClient` with retry + request/response middleware
|
|
90
|
+
- `RetrievalResource` — `discover` / `interpret` / `search`
|
|
91
|
+
- `ContextResource` — `/context/for-agent/{minimal,adaptive,comprehensive}`
|
|
92
|
+
|
|
93
|
+
**Deferred to a future release:**
|
|
94
|
+
- Crypto primitives (HKDF, AES-GCM, session tokens, DEK) + Supabase auth provider
|
|
95
|
+
- `ContextWindow` + `SourcesResource` + `IngestResource`
|
|
96
|
+
- `MatrixResource`, `SandboxesResource`, `ProjectsResource`, `EntitiesResource`, `VaultResource`, `UsageResource`, `ApiKeysResource`, `UsersResource`
|
|
97
|
+
|
|
98
|
+
Add incrementally when a concrete Python consumer needs each. Opening a PR with a scoped addition is the expected path.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# copass-core
|
|
2
|
+
|
|
3
|
+
Core client SDK for the Copass platform. Python mirror of [`@copass/core`](../../typescript/packages/core) — shared foundation for every Python Copass adapter.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install copass-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires `httpx>=0.27`. Python ≥ 3.10.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import asyncio
|
|
17
|
+
from copass_core import CopassClient, ApiKeyAuth
|
|
18
|
+
|
|
19
|
+
async def main():
|
|
20
|
+
client = CopassClient(auth=ApiKeyAuth(key="olk_..."))
|
|
21
|
+
|
|
22
|
+
# Retrieval
|
|
23
|
+
menu = await client.retrieval.discover(
|
|
24
|
+
sandbox_id="sb_...",
|
|
25
|
+
query="How does auth work?",
|
|
26
|
+
)
|
|
27
|
+
print(menu["items"])
|
|
28
|
+
|
|
29
|
+
# Context for agent
|
|
30
|
+
context = await client.context.for_agent(
|
|
31
|
+
sandbox_id="sb_...",
|
|
32
|
+
tier="adaptive",
|
|
33
|
+
query="auth flow",
|
|
34
|
+
)
|
|
35
|
+
print(context)
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Auth options
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from copass_core import CopassClient, ApiKeyAuth, BearerAuth, ProviderAuth
|
|
44
|
+
|
|
45
|
+
# Long-lived API key (olk_ prefix)
|
|
46
|
+
CopassClient(auth=ApiKeyAuth(key="olk_..."))
|
|
47
|
+
|
|
48
|
+
# Raw Bearer JWT (caller owns refresh)
|
|
49
|
+
CopassClient(auth=BearerAuth(token="eyJ..."))
|
|
50
|
+
|
|
51
|
+
# Custom AuthProvider implementation
|
|
52
|
+
class MyProvider:
|
|
53
|
+
async def get_session(self):
|
|
54
|
+
from copass_core import SessionContext
|
|
55
|
+
return SessionContext(access_token=await _mint_token())
|
|
56
|
+
|
|
57
|
+
CopassClient(auth=ProviderAuth(provider=MyProvider()))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## v0.1.0 scope
|
|
61
|
+
|
|
62
|
+
**Shipped:**
|
|
63
|
+
- `CopassClient` top-level entry point
|
|
64
|
+
- Auth providers: `ApiKeyAuthProvider`, `BearerAuthProvider`
|
|
65
|
+
- `HttpClient` with retry + request/response middleware
|
|
66
|
+
- `RetrievalResource` — `discover` / `interpret` / `search`
|
|
67
|
+
- `ContextResource` — `/context/for-agent/{minimal,adaptive,comprehensive}`
|
|
68
|
+
|
|
69
|
+
**Deferred to a future release:**
|
|
70
|
+
- Crypto primitives (HKDF, AES-GCM, session tokens, DEK) + Supabase auth provider
|
|
71
|
+
- `ContextWindow` + `SourcesResource` + `IngestResource`
|
|
72
|
+
- `MatrixResource`, `SandboxesResource`, `ProjectsResource`, `EntitiesResource`, `VaultResource`, `UsageResource`, `ApiKeysResource`, `UsersResource`
|
|
73
|
+
|
|
74
|
+
Add incrementally when a concrete Python consumer needs each. Opening a PR with a scoped addition is the expected path.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "copass-core"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Core client SDK for the Copass platform (Python mirror of @copass/core)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Olane Inc." }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["copass", "knowledge-graph", "sdk", "client", "retrieval"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"httpx>=0.27",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=8.0",
|
|
28
|
+
"pytest-asyncio>=0.23",
|
|
29
|
+
"respx>=0.21",
|
|
30
|
+
"mypy>=1.10",
|
|
31
|
+
"ruff>=0.5",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/olane-labs/copass-harness"
|
|
36
|
+
Repository = "https://github.com/olane-labs/copass-harness.git"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["src/copass_core"]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
asyncio_mode = "auto"
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
line-length = 100
|
|
47
|
+
target-version = "py310"
|
|
48
|
+
|
|
49
|
+
[tool.mypy]
|
|
50
|
+
python_version = "3.10"
|
|
51
|
+
strict = true
|
|
52
|
+
packages = ["copass_core"]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Copass Python client SDK.
|
|
2
|
+
|
|
3
|
+
Python mirror of `@copass/core`_. v0.1.0 scope:
|
|
4
|
+
|
|
5
|
+
- ``CopassClient`` top-level entry point
|
|
6
|
+
- Auth providers: ``ApiKeyAuthProvider``, ``BearerAuthProvider``
|
|
7
|
+
- ``HttpClient`` with retry + middleware
|
|
8
|
+
- Retrieval resource: ``discover`` / ``interpret`` / ``search``
|
|
9
|
+
- Context resource: ``/context/for-agent/{minimal,adaptive,comprehensive}``
|
|
10
|
+
|
|
11
|
+
Deferred to a future release (``v0.2+``): crypto primitives + Supabase
|
|
12
|
+
auth provider, ContextWindow / SourcesResource / IngestResource,
|
|
13
|
+
full resource coverage (matrix, sandboxes, projects, entities, vault,
|
|
14
|
+
usage, api-keys, users).
|
|
15
|
+
|
|
16
|
+
.. _`@copass/core`: https://github.com/olane-labs/copass-harness/tree/main/typescript/packages/core
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from copass_core.auth import (
|
|
20
|
+
ApiKeyAuthProvider,
|
|
21
|
+
AuthProvider,
|
|
22
|
+
BearerAuthProvider,
|
|
23
|
+
SessionContext,
|
|
24
|
+
)
|
|
25
|
+
from copass_core.client import (
|
|
26
|
+
DEFAULT_API_URL,
|
|
27
|
+
ApiKeyAuth,
|
|
28
|
+
AuthConfig,
|
|
29
|
+
BearerAuth,
|
|
30
|
+
CopassClient,
|
|
31
|
+
ProviderAuth,
|
|
32
|
+
)
|
|
33
|
+
from copass_core.http import (
|
|
34
|
+
CopassApiError,
|
|
35
|
+
CopassNetworkError,
|
|
36
|
+
CopassValidationError,
|
|
37
|
+
HttpClient,
|
|
38
|
+
HttpClientOptions,
|
|
39
|
+
RequestContext,
|
|
40
|
+
RequestMiddleware,
|
|
41
|
+
RequestOptions,
|
|
42
|
+
ResponseContext,
|
|
43
|
+
ResponseMiddleware,
|
|
44
|
+
retry_with_backoff,
|
|
45
|
+
)
|
|
46
|
+
from copass_core.resources import (
|
|
47
|
+
BaseResource,
|
|
48
|
+
ContextResource,
|
|
49
|
+
ContextTier,
|
|
50
|
+
RetrievalResource,
|
|
51
|
+
)
|
|
52
|
+
from copass_core.types import (
|
|
53
|
+
ChatMessage,
|
|
54
|
+
ChatRole,
|
|
55
|
+
RetryConfig,
|
|
56
|
+
SearchPreset,
|
|
57
|
+
WindowLike,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
__version__ = "0.1.0"
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"__version__",
|
|
64
|
+
# Client
|
|
65
|
+
"CopassClient",
|
|
66
|
+
"AuthConfig",
|
|
67
|
+
"ApiKeyAuth",
|
|
68
|
+
"BearerAuth",
|
|
69
|
+
"ProviderAuth",
|
|
70
|
+
"DEFAULT_API_URL",
|
|
71
|
+
# Auth
|
|
72
|
+
"AuthProvider",
|
|
73
|
+
"SessionContext",
|
|
74
|
+
"ApiKeyAuthProvider",
|
|
75
|
+
"BearerAuthProvider",
|
|
76
|
+
# HTTP
|
|
77
|
+
"HttpClient",
|
|
78
|
+
"HttpClientOptions",
|
|
79
|
+
"RequestOptions",
|
|
80
|
+
"RequestContext",
|
|
81
|
+
"ResponseContext",
|
|
82
|
+
"RequestMiddleware",
|
|
83
|
+
"ResponseMiddleware",
|
|
84
|
+
"CopassApiError",
|
|
85
|
+
"CopassNetworkError",
|
|
86
|
+
"CopassValidationError",
|
|
87
|
+
"retry_with_backoff",
|
|
88
|
+
# Resources
|
|
89
|
+
"BaseResource",
|
|
90
|
+
"RetrievalResource",
|
|
91
|
+
"ContextResource",
|
|
92
|
+
"ContextTier",
|
|
93
|
+
# Types
|
|
94
|
+
"RetryConfig",
|
|
95
|
+
"ChatMessage",
|
|
96
|
+
"ChatRole",
|
|
97
|
+
"WindowLike",
|
|
98
|
+
"SearchPreset",
|
|
99
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Authentication providers."""
|
|
2
|
+
|
|
3
|
+
from copass_core.auth.api_key import ApiKeyAuthProvider
|
|
4
|
+
from copass_core.auth.bearer import BearerAuthProvider
|
|
5
|
+
from copass_core.auth.types import AuthProvider, SessionContext
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AuthProvider",
|
|
9
|
+
"SessionContext",
|
|
10
|
+
"ApiKeyAuthProvider",
|
|
11
|
+
"BearerAuthProvider",
|
|
12
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""API key auth provider.
|
|
2
|
+
|
|
3
|
+
Hand-ported from ``typescript/packages/core/src/auth/api-key.ts``.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from copass_core.auth.types import AuthProvider, SessionContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ApiKeyAuthProvider:
|
|
12
|
+
"""Sends a long-lived API key (``olk_...``) as a bearer token.
|
|
13
|
+
|
|
14
|
+
No session token; API keys don't support DEK wrapping in this
|
|
15
|
+
release.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, key: str) -> None:
|
|
19
|
+
if not key:
|
|
20
|
+
raise ValueError("ApiKeyAuthProvider requires a non-empty key")
|
|
21
|
+
self._key = key
|
|
22
|
+
|
|
23
|
+
async def get_session(self) -> SessionContext:
|
|
24
|
+
return SessionContext(access_token=self._key)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ["ApiKeyAuthProvider"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Bearer JWT auth provider.
|
|
2
|
+
|
|
3
|
+
Hand-ported from ``typescript/packages/core/src/auth/bearer.ts``. In
|
|
4
|
+
v0.1.0 the encryption-key path is deferred — the provider simply
|
|
5
|
+
forwards the caller-supplied JWT. A later release will add
|
|
6
|
+
``createSessionToken`` integration once the crypto module lands.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from copass_core.auth.types import AuthProvider, SessionContext
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BearerAuthProvider:
|
|
17
|
+
"""Forwards a caller-managed JWT as the bearer token.
|
|
18
|
+
|
|
19
|
+
The caller is responsible for refreshing the JWT when it expires —
|
|
20
|
+
mint a new provider with the new token or wrap your own refresh
|
|
21
|
+
logic in a custom :class:`AuthProvider` implementation.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, token: str, encryption_key: Optional[str] = None) -> None:
|
|
25
|
+
if not token:
|
|
26
|
+
raise ValueError("BearerAuthProvider requires a non-empty token")
|
|
27
|
+
self._token = token
|
|
28
|
+
# Stored but unused in v0.1.0 — reserved for the forthcoming
|
|
29
|
+
# crypto module (session-token derivation for vault access).
|
|
30
|
+
self._encryption_key = encryption_key
|
|
31
|
+
|
|
32
|
+
async def get_session(self) -> SessionContext:
|
|
33
|
+
return SessionContext(access_token=self._token)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["BearerAuthProvider"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Authentication interfaces.
|
|
2
|
+
|
|
3
|
+
Hand-ported from ``typescript/packages/core/src/auth/types.ts``. The
|
|
4
|
+
Python port drops the session-token / encryption fields that aren't
|
|
5
|
+
needed by v0.1.0 consumers; add them back when the crypto module
|
|
6
|
+
lands in a later release.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Optional, Protocol, runtime_checkable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class SessionContext:
|
|
17
|
+
"""Active session context with resolved credentials.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
access_token: The JWT or API key for the ``Authorization``
|
|
21
|
+
header.
|
|
22
|
+
session_token: Optional wrapped DEK for the
|
|
23
|
+
``X-Encryption-Token`` header. ``None`` in v0.1.0 — the
|
|
24
|
+
crypto module is deferred.
|
|
25
|
+
user_id: User id extracted from the token. ``None`` for API
|
|
26
|
+
keys (not decoded).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
access_token: str
|
|
30
|
+
session_token: Optional[str] = None
|
|
31
|
+
user_id: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@runtime_checkable
|
|
35
|
+
class AuthProvider(Protocol):
|
|
36
|
+
"""Structural contract for authentication providers.
|
|
37
|
+
|
|
38
|
+
Each auth strategy (API key, bearer JWT, future Supabase) exposes
|
|
39
|
+
:meth:`get_session`. The HTTP client calls it before each request
|
|
40
|
+
to obtain fresh credentials; providers are free to cache + refresh
|
|
41
|
+
under the hood.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
async def get_session(self) -> SessionContext: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = ["AuthProvider", "SessionContext"]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""CopassClient — top-level entry point.
|
|
2
|
+
|
|
3
|
+
Hand-ported from ``typescript/packages/core/src/client.ts``. v0.1.0
|
|
4
|
+
exposes the two resources that v0.1.0 consumers need:
|
|
5
|
+
:class:`RetrievalResource` (``discover`` / ``interpret`` / ``search``)
|
|
6
|
+
and :class:`ContextResource` (``/context/for-agent/*``). Additional
|
|
7
|
+
resources (``sandboxes``, ``sources``, ``ingest``, ``vault``, etc.)
|
|
8
|
+
land incrementally.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import List, Optional, Union
|
|
15
|
+
|
|
16
|
+
from copass_core.auth.api_key import ApiKeyAuthProvider
|
|
17
|
+
from copass_core.auth.bearer import BearerAuthProvider
|
|
18
|
+
from copass_core.auth.types import AuthProvider
|
|
19
|
+
from copass_core.http.http_client import (
|
|
20
|
+
HttpClient,
|
|
21
|
+
HttpClientOptions,
|
|
22
|
+
RequestMiddleware,
|
|
23
|
+
ResponseMiddleware,
|
|
24
|
+
)
|
|
25
|
+
from copass_core.resources.context import ContextResource
|
|
26
|
+
from copass_core.resources.retrieval import RetrievalResource
|
|
27
|
+
from copass_core.types import RetryConfig
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
DEFAULT_API_URL = "https://ai.copass.id"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ApiKeyAuth:
|
|
35
|
+
"""``auth=ApiKeyAuth(key="olk_...")``."""
|
|
36
|
+
|
|
37
|
+
key: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class BearerAuth:
|
|
42
|
+
"""``auth=BearerAuth(token="eyJ...")``. Caller owns refresh."""
|
|
43
|
+
|
|
44
|
+
token: str
|
|
45
|
+
encryption_key: Optional[str] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class ProviderAuth:
|
|
50
|
+
"""``auth=ProviderAuth(provider=MyCustomAuthProvider(...))``."""
|
|
51
|
+
|
|
52
|
+
provider: AuthProvider
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
AuthConfig = Union[ApiKeyAuth, BearerAuth, ProviderAuth]
|
|
56
|
+
"""Discriminated-union of auth configurations. Supabase OTP auth is
|
|
57
|
+
deferred to a later release."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _build_auth_provider(auth: AuthConfig) -> AuthProvider:
|
|
61
|
+
if isinstance(auth, ApiKeyAuth):
|
|
62
|
+
return ApiKeyAuthProvider(auth.key)
|
|
63
|
+
if isinstance(auth, BearerAuth):
|
|
64
|
+
return BearerAuthProvider(auth.token, auth.encryption_key)
|
|
65
|
+
if isinstance(auth, ProviderAuth):
|
|
66
|
+
return auth.provider
|
|
67
|
+
raise TypeError(f"Unsupported AuthConfig type: {type(auth).__name__}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class CopassClient:
|
|
71
|
+
"""Main entry point for the Copass Python SDK.
|
|
72
|
+
|
|
73
|
+
Resources are accessed as instance attributes (Stripe-style).
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> client = CopassClient(auth=ApiKeyAuth(key="olk_..."))
|
|
77
|
+
>>> menu = await client.retrieval.discover(
|
|
78
|
+
... sandbox_id="sb-1",
|
|
79
|
+
... query="How does auth work?",
|
|
80
|
+
... )
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
retrieval: RetrievalResource
|
|
84
|
+
context: ContextResource
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
*,
|
|
89
|
+
auth: AuthConfig,
|
|
90
|
+
api_url: str = DEFAULT_API_URL,
|
|
91
|
+
retry: Optional[RetryConfig] = None,
|
|
92
|
+
on_request: Optional[List[RequestMiddleware]] = None,
|
|
93
|
+
on_response: Optional[List[ResponseMiddleware]] = None,
|
|
94
|
+
timeout: float = 30.0,
|
|
95
|
+
) -> None:
|
|
96
|
+
auth_provider = _build_auth_provider(auth)
|
|
97
|
+
http = HttpClient(
|
|
98
|
+
HttpClientOptions(
|
|
99
|
+
api_url=api_url,
|
|
100
|
+
auth_provider=auth_provider,
|
|
101
|
+
retry=retry,
|
|
102
|
+
on_request=list(on_request or []),
|
|
103
|
+
on_response=list(on_response or []),
|
|
104
|
+
timeout=timeout,
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
self._http = http
|
|
108
|
+
self.retrieval = RetrievalResource(http)
|
|
109
|
+
self.context = ContextResource(http)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = [
|
|
113
|
+
"CopassClient",
|
|
114
|
+
"AuthConfig",
|
|
115
|
+
"ApiKeyAuth",
|
|
116
|
+
"BearerAuth",
|
|
117
|
+
"ProviderAuth",
|
|
118
|
+
"DEFAULT_API_URL",
|
|
119
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""HTTP client primitives."""
|
|
2
|
+
|
|
3
|
+
from copass_core.http.errors import (
|
|
4
|
+
CopassApiError,
|
|
5
|
+
CopassNetworkError,
|
|
6
|
+
CopassValidationError,
|
|
7
|
+
)
|
|
8
|
+
from copass_core.http.http_client import (
|
|
9
|
+
HttpClient,
|
|
10
|
+
HttpClientOptions,
|
|
11
|
+
RequestContext,
|
|
12
|
+
RequestMiddleware,
|
|
13
|
+
RequestOptions,
|
|
14
|
+
ResponseContext,
|
|
15
|
+
ResponseMiddleware,
|
|
16
|
+
)
|
|
17
|
+
from copass_core.http.retry import retry_with_backoff
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"HttpClient",
|
|
21
|
+
"HttpClientOptions",
|
|
22
|
+
"RequestOptions",
|
|
23
|
+
"RequestContext",
|
|
24
|
+
"ResponseContext",
|
|
25
|
+
"RequestMiddleware",
|
|
26
|
+
"ResponseMiddleware",
|
|
27
|
+
"CopassApiError",
|
|
28
|
+
"CopassNetworkError",
|
|
29
|
+
"CopassValidationError",
|
|
30
|
+
"retry_with_backoff",
|
|
31
|
+
]
|