diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index e01283d8..7aad6f44 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -14,7 +14,7 @@
"extensions": [
"ms-python.python"
],
- "settings": {
+ "settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": ".venv/bin/python",
"python.defaultInterpreterPath": ".venv/bin/python",
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8a899fe0..74c07c03 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ubuntu-latest
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -40,8 +40,8 @@ jobs:
name: build
permissions:
contents: read
- id-token: write
- runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -55,41 +55,3 @@ jobs:
- name: Run build
run: uv build
-
- - name: Get GitHub OIDC Token
- if: |-
- github.repository == 'stainless-sdks/hyperspell-python' &&
- !startsWith(github.ref, 'refs/heads/stl/')
- id: github-oidc
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
- with:
- script: core.setOutput('github_token', await core.getIDToken());
-
- - name: Upload tarball
- if: |-
- github.repository == 'stainless-sdks/hyperspell-python' &&
- !startsWith(github.ref, 'refs/heads/stl/')
- env:
- URL: https://pkg.stainless.com/s
- AUTH: ${{ steps.github-oidc.outputs.github_token }}
- SHA: ${{ github.sha }}
- run: ./scripts/utils/upload-artifact.sh
-
- test:
- timeout-minutes: 10
- name: test
- runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
- if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
- - name: Install uv
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
- with:
- version: '0.10.2'
-
- - name: Bootstrap
- run: ./scripts/bootstrap
-
- - name: Run tests
- run: ./scripts/test
diff --git a/.github/workflows/sdk-tests.yml b/.github/workflows/sdk-tests.yml
new file mode 100644
index 00000000..7fd9cac6
--- /dev/null
+++ b/.github/workflows/sdk-tests.yml
@@ -0,0 +1,25 @@
+name: SDK tests
+
+on:
+ push:
+ branches: ['**']
+ pull_request:
+
+jobs:
+ test:
+ name: test
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
+ with:
+ version: '0.10.2'
+
+ - name: Bootstrap
+ run: ./scripts/bootstrap
+
+ - name: Run tests
+ run: ./scripts/test
diff --git a/.stats.yml b/.stats.yml
index 423e2f9a..1fd29914 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 31
+configured_endpoints: 41
diff --git a/README.md b/README.md
index 9870d52f..592361c0 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ import os
from hyperspell import Hyperspell
client = Hyperspell(
+ user_id="My User ID",
api_key=os.environ.get("HYPERSPELL_API_KEY"), # This is the default and can be omitted
)
@@ -62,6 +63,7 @@ import asyncio
from hyperspell import AsyncHyperspell
client = AsyncHyperspell(
+ user_id="My User ID",
api_key=os.environ.get("HYPERSPELL_API_KEY"), # This is the default and can be omitted
)
@@ -100,6 +102,7 @@ from hyperspell import AsyncHyperspell
async def main() -> None:
async with AsyncHyperspell(
+ user_id="My User ID",
api_key=os.environ.get("HYPERSPELL_API_KEY"), # This is the default and can be omitted
http_client=DefaultAioHttpClient(),
) as client:
@@ -130,14 +133,16 @@ This library provides auto-paginating iterators with each list response, so you
```python
from hyperspell import Hyperspell
-client = Hyperspell()
+client = Hyperspell(
+ user_id="My User ID",
+)
-all_memories = []
+all_context_documents = []
# Automatically fetches more pages as needed.
-for memory in client.memories.list():
- # Do something with memory here
- all_memories.append(memory)
-print(all_memories)
+for context_document in client.context_documents.list():
+ # Do something with context_document here
+ all_context_documents.append(context_document)
+print(all_context_documents)
```
Or, asynchronously:
@@ -146,15 +151,17 @@ Or, asynchronously:
import asyncio
from hyperspell import AsyncHyperspell
-client = AsyncHyperspell()
+client = AsyncHyperspell(
+ user_id="My User ID",
+)
async def main() -> None:
- all_memories = []
+ all_context_documents = []
# Iterate through items across all pages, issuing requests as needed.
- async for memory in client.memories.list():
- all_memories.append(memory)
- print(all_memories)
+ async for context_document in client.context_documents.list():
+ all_context_documents.append(context_document)
+ print(all_context_documents)
asyncio.run(main())
@@ -163,11 +170,11 @@ asyncio.run(main())
Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
```python
-first_page = await client.memories.list()
+first_page = await client.context_documents.list()
if first_page.has_next_page():
print(f"will fetch next page using these details: {first_page.next_page_info()}")
next_page = await first_page.get_next_page()
- print(f"number of items we just fetched: {len(next_page.items)}")
+ print(f"number of items we just fetched: {len(next_page.documents)}")
# Remove `await` for non-async usage.
```
@@ -175,11 +182,11 @@ if first_page.has_next_page():
Or just work directly with the returned data:
```python
-first_page = await client.memories.list()
+first_page = await client.context_documents.list()
print(f"next page cursor: {first_page.next_cursor}") # => "next page cursor: ..."
-for memory in first_page.items:
- print(memory.resource_id)
+for context_document in first_page.documents:
+ print(context_document.document_id)
# Remove `await` for non-async usage.
```
@@ -191,13 +198,14 @@ Nested parameters are dictionaries, typed using `TypedDict`, for example:
```python
from hyperspell import Hyperspell
-client = Hyperspell()
+client = Hyperspell(
+ user_id="My User ID",
+)
-query_result = client.memories.search(
- query="What does Hyperspell do?",
- options={"filter": {}},
+config = client.context_documents.config.update(
+ structure={},
)
-print(query_result.options)
+print(config.structure)
```
## File uploads
@@ -208,7 +216,9 @@ Request parameters that correspond to file uploads can be passed as `bytes`, or
from pathlib import Path
from hyperspell import Hyperspell
-client = Hyperspell()
+client = Hyperspell(
+ user_id="My User ID",
+)
client.memories.upload(
file=Path("/path/to/file"),
@@ -230,7 +240,9 @@ All errors inherit from `hyperspell.APIError`.
import hyperspell
from hyperspell import Hyperspell
-client = Hyperspell()
+client = Hyperspell(
+ user_id="My User ID",
+)
try:
client.memories.add(
@@ -273,6 +285,7 @@ from hyperspell import Hyperspell
# Configure the default for all requests:
client = Hyperspell(
+ user_id="My User ID",
# default is 2
max_retries=0,
)
@@ -293,12 +306,14 @@ from hyperspell import Hyperspell
# Configure the default for all requests:
client = Hyperspell(
+ user_id="My User ID",
# 20 seconds (default is 1 minute)
timeout=20.0,
)
# More granular control:
client = Hyperspell(
+ user_id="My User ID",
timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
)
@@ -345,7 +360,9 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to
```py
from hyperspell import Hyperspell
-client = Hyperspell()
+client = Hyperspell(
+ user_id="My User ID",
+)
response = client.memories.with_raw_response.add(
text="...",
)
@@ -424,6 +441,7 @@ import httpx
from hyperspell import Hyperspell, DefaultHttpxClient
client = Hyperspell(
+ user_id="My User ID",
# Or use the `HYPERSPELL_BASE_URL` env var
base_url="http://my.test.server.example.com:8083",
http_client=DefaultHttpxClient(
@@ -446,7 +464,9 @@ By default the library closes underlying HTTP connections whenever the client is
```py
from hyperspell import Hyperspell
-with Hyperspell() as client:
+with Hyperspell(
+ user_id="My User ID",
+) as client:
# make requests here
...
diff --git a/api.md b/api.md
index a17e0cfa..d8e50312 100644
--- a/api.md
+++ b/api.md
@@ -24,6 +24,7 @@ from hyperspell.types import (
ListItem,
Message,
Metadata,
+ Page,
Paragraph,
Person,
Provenance,
@@ -95,35 +96,86 @@ Methods:
- client.integrations.list() -> IntegrationListResponse
- client.integrations.connect(integration_id, \*\*params) -> IntegrationConnectResponse
-## GoogleCalendar
+## WebCrawler
Types:
```python
-from hyperspell.types.integrations import Calendar
+from hyperspell.types.integrations import WebCrawlerIndexResponse
```
Methods:
-- client.integrations.google_calendar.list() -> Calendar
+- client.integrations.web_crawler.index(\*\*params) -> WebCrawlerIndexResponse
-## WebCrawler
+# ContextDocuments
Types:
```python
-from hyperspell.types.integrations import WebCrawlerIndexResponse
+from hyperspell.types import (
+ ContextDocumentListResponse,
+ ContextDocumentGenerateResponse,
+ ContextDocumentGetResponse,
+)
```
Methods:
-- client.integrations.web_crawler.index(\*\*params) -> WebCrawlerIndexResponse
+- client.context_documents.list(\*\*params) -> SyncContextDocumentsCursorPage[ContextDocumentListResponse]
+- client.context_documents.generate(\*\*params) -> ContextDocumentGenerateResponse
+- client.context_documents.get(document_id) -> ContextDocumentGetResponse
+
+## Trees
+
+Types:
+
+```python
+from hyperspell.types.context_documents import (
+ TreeGenerateResponse,
+ TreeGetResponse,
+ TreeGetLatestResponse,
+ TreeProgressResponse,
+)
+```
+
+Methods:
-## Slack
+- client.context_documents.trees.generate(\*\*params) -> TreeGenerateResponse
+- client.context_documents.trees.get(tree_id) -> TreeGetResponse
+- client.context_documents.trees.get_latest(\*\*params) -> TreeGetLatestResponse
+- client.context_documents.trees.progress(tree_id) -> TreeProgressResponse
+
+## Digests
+
+Types:
+
+```python
+from hyperspell.types.context_documents import DigestListResponse, DigestGenerateResponse
+```
+
+Methods:
+
+- client.context_documents.digests.list(\*\*params) -> DigestListResponse
+- client.context_documents.digests.generate(\*\*params) -> DigestGenerateResponse
+
+## Config
+
+Types:
+
+```python
+from hyperspell.types.context_documents import (
+ ConfigUpdateResponse,
+ ConfigGetResponse,
+ ConfigResetResponse,
+)
+```
Methods:
-- client.integrations.slack.list(\*\*params) -> object
+- client.context_documents.config.update(\*\*params) -> ConfigUpdateResponse
+- client.context_documents.config.get() -> ConfigGetResponse
+- client.context_documents.config.reset() -> ConfigResetResponse
# Memories
@@ -147,7 +199,7 @@ Methods:
- client.memories.delete(resource_id, \*, source) -> MemoryDeleteResponse
- client.memories.add(\*\*params) -> MemoryStatus
- client.memories.add_bulk(\*\*params) -> MemoryAddBulkResponse
-- client.memories.get(resource_id, \*, source) -> MemoryGetResponse
+- client.memories.get(resource_id, \*, source, \*\*params) -> MemoryGetResponse
- client.memories.search(\*\*params) -> QueryResult
- client.memories.status() -> MemoryStatusResponse
- client.memories.upload(\*\*params) -> MemoryStatus
diff --git a/scripts/mock b/scripts/mock
index 1df910b9..342ec614 100755
--- a/scripts/mock
+++ b/scripts/mock
@@ -11,7 +11,7 @@ elif [ -n "$STAINLESS_OPENAPI_SPEC_URL" ]; then
URL="$STAINLESS_OPENAPI_SPEC_URL"
else
# Embedded OpenAPI spec (base64-encoded, gzipped)
- EMBEDDED_SPEC="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"
+ EMBEDDED_SPEC="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"
SPEC_TMPFILE="$(mktemp)"
trap 'rm -f "$SPEC_TMPFILE"' EXIT
echo "$EMBEDDED_SPEC" | base64 --decode | gunzip > "$SPEC_TMPFILE"
diff --git a/src/hyperspell/_client.py b/src/hyperspell/_client.py
index 20dbdf4a..d05ded7a 100644
--- a/src/hyperspell/_client.py
+++ b/src/hyperspell/_client.py
@@ -35,7 +35,18 @@
)
if TYPE_CHECKING:
- from .resources import auth, vaults, actions, folders, evaluate, memories, sessions, connections, integrations
+ from .resources import (
+ auth,
+ vaults,
+ actions,
+ folders,
+ evaluate,
+ memories,
+ sessions,
+ connections,
+ integrations,
+ context_documents,
+ )
from .resources.auth import AuthResource, AsyncAuthResource
from .resources.vaults import VaultsResource, AsyncVaultsResource
from .resources.actions import ActionsResource, AsyncActionsResource
@@ -45,6 +56,7 @@
from .resources.sessions import SessionsResource, AsyncSessionsResource
from .resources.connections import ConnectionsResource, AsyncConnectionsResource
from .resources.integrations.integrations import IntegrationsResource, AsyncIntegrationsResource
+ from .resources.context_documents.context_documents import ContextDocumentsResource, AsyncContextDocumentsResource
__all__ = [
"Timeout",
@@ -144,6 +156,12 @@ def integrations(self) -> IntegrationsResource:
return IntegrationsResource(self)
+ @cached_property
+ def context_documents(self) -> ContextDocumentsResource:
+ from .resources.context_documents import ContextDocumentsResource
+
+ return ContextDocumentsResource(self)
+
@cached_property
def memories(self) -> MemoriesResource:
from .resources.memories import MemoriesResource
@@ -196,26 +214,16 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- return {**self._api_key, **self._as_user}
-
- @property
- def _api_key(self) -> dict[str, str]:
api_key = self.api_key
return {"Authorization": f"Bearer {api_key}"}
- @property
- def _as_user(self) -> dict[str, str]:
- user_id = self.user_id
- if user_id is None:
- return {}
- return {"X-As-User": user_id}
-
@property
@override
def default_headers(self) -> dict[str, str | Omit]:
return {
**super().default_headers,
"X-Stainless-Async": "false",
+ "X-As-User": self.user_id if self.user_id is not None else Omit(),
**self._custom_headers,
}
@@ -392,6 +400,12 @@ def integrations(self) -> AsyncIntegrationsResource:
return AsyncIntegrationsResource(self)
+ @cached_property
+ def context_documents(self) -> AsyncContextDocumentsResource:
+ from .resources.context_documents import AsyncContextDocumentsResource
+
+ return AsyncContextDocumentsResource(self)
+
@cached_property
def memories(self) -> AsyncMemoriesResource:
from .resources.memories import AsyncMemoriesResource
@@ -444,26 +458,16 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- return {**self._api_key, **self._as_user}
-
- @property
- def _api_key(self) -> dict[str, str]:
api_key = self.api_key
return {"Authorization": f"Bearer {api_key}"}
- @property
- def _as_user(self) -> dict[str, str]:
- user_id = self.user_id
- if user_id is None:
- return {}
- return {"X-As-User": user_id}
-
@property
@override
def default_headers(self) -> dict[str, str | Omit]:
return {
**super().default_headers,
"X-Stainless-Async": f"async:{get_async_library()}",
+ "X-As-User": self.user_id if self.user_id is not None else Omit(),
**self._custom_headers,
}
@@ -578,6 +582,12 @@ def integrations(self) -> integrations.IntegrationsResourceWithRawResponse:
return IntegrationsResourceWithRawResponse(self._client.integrations)
+ @cached_property
+ def context_documents(self) -> context_documents.ContextDocumentsResourceWithRawResponse:
+ from .resources.context_documents import ContextDocumentsResourceWithRawResponse
+
+ return ContextDocumentsResourceWithRawResponse(self._client.context_documents)
+
@cached_property
def memories(self) -> memories.MemoriesResourceWithRawResponse:
from .resources.memories import MemoriesResourceWithRawResponse
@@ -639,6 +649,12 @@ def integrations(self) -> integrations.AsyncIntegrationsResourceWithRawResponse:
return AsyncIntegrationsResourceWithRawResponse(self._client.integrations)
+ @cached_property
+ def context_documents(self) -> context_documents.AsyncContextDocumentsResourceWithRawResponse:
+ from .resources.context_documents import AsyncContextDocumentsResourceWithRawResponse
+
+ return AsyncContextDocumentsResourceWithRawResponse(self._client.context_documents)
+
@cached_property
def memories(self) -> memories.AsyncMemoriesResourceWithRawResponse:
from .resources.memories import AsyncMemoriesResourceWithRawResponse
@@ -700,6 +716,12 @@ def integrations(self) -> integrations.IntegrationsResourceWithStreamingResponse
return IntegrationsResourceWithStreamingResponse(self._client.integrations)
+ @cached_property
+ def context_documents(self) -> context_documents.ContextDocumentsResourceWithStreamingResponse:
+ from .resources.context_documents import ContextDocumentsResourceWithStreamingResponse
+
+ return ContextDocumentsResourceWithStreamingResponse(self._client.context_documents)
+
@cached_property
def memories(self) -> memories.MemoriesResourceWithStreamingResponse:
from .resources.memories import MemoriesResourceWithStreamingResponse
@@ -761,6 +783,12 @@ def integrations(self) -> integrations.AsyncIntegrationsResourceWithStreamingRes
return AsyncIntegrationsResourceWithStreamingResponse(self._client.integrations)
+ @cached_property
+ def context_documents(self) -> context_documents.AsyncContextDocumentsResourceWithStreamingResponse:
+ from .resources.context_documents import AsyncContextDocumentsResourceWithStreamingResponse
+
+ return AsyncContextDocumentsResourceWithStreamingResponse(self._client.context_documents)
+
@cached_property
def memories(self) -> memories.AsyncMemoriesResourceWithStreamingResponse:
from .resources.memories import AsyncMemoriesResourceWithStreamingResponse
diff --git a/src/hyperspell/pagination.py b/src/hyperspell/pagination.py
index 622bf83d..5da3aa10 100644
--- a/src/hyperspell/pagination.py
+++ b/src/hyperspell/pagination.py
@@ -5,7 +5,7 @@
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
-__all__ = ["SyncCursorPage", "AsyncCursorPage"]
+__all__ = ["SyncCursorPage", "AsyncCursorPage", "SyncContextDocumentsCursorPage", "AsyncContextDocumentsCursorPage"]
_T = TypeVar("_T")
@@ -48,3 +48,43 @@ def next_page_info(self) -> Optional[PageInfo]:
return None
return PageInfo(params={"cursor": next_cursor})
+
+
+class SyncContextDocumentsCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+ documents: List[_T]
+ next_cursor: Optional[str] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ documents = self.documents
+ if not documents:
+ return []
+ return documents
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next_cursor = self.next_cursor
+ if not next_cursor:
+ return None
+
+ return PageInfo(params={"cursor": next_cursor})
+
+
+class AsyncContextDocumentsCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+ documents: List[_T]
+ next_cursor: Optional[str] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ documents = self.documents
+ if not documents:
+ return []
+ return documents
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next_cursor = self.next_cursor
+ if not next_cursor:
+ return None
+
+ return PageInfo(params={"cursor": next_cursor})
diff --git a/src/hyperspell/resources/__init__.py b/src/hyperspell/resources/__init__.py
index 77ef955a..2553e993 100644
--- a/src/hyperspell/resources/__init__.py
+++ b/src/hyperspell/resources/__init__.py
@@ -72,6 +72,14 @@
IntegrationsResourceWithStreamingResponse,
AsyncIntegrationsResourceWithStreamingResponse,
)
+from .context_documents import (
+ ContextDocumentsResource,
+ AsyncContextDocumentsResource,
+ ContextDocumentsResourceWithRawResponse,
+ AsyncContextDocumentsResourceWithRawResponse,
+ ContextDocumentsResourceWithStreamingResponse,
+ AsyncContextDocumentsResourceWithStreamingResponse,
+)
__all__ = [
"ConnectionsResource",
@@ -92,6 +100,12 @@
"AsyncIntegrationsResourceWithRawResponse",
"IntegrationsResourceWithStreamingResponse",
"AsyncIntegrationsResourceWithStreamingResponse",
+ "ContextDocumentsResource",
+ "AsyncContextDocumentsResource",
+ "ContextDocumentsResourceWithRawResponse",
+ "AsyncContextDocumentsResourceWithRawResponse",
+ "ContextDocumentsResourceWithStreamingResponse",
+ "AsyncContextDocumentsResourceWithStreamingResponse",
"MemoriesResource",
"AsyncMemoriesResource",
"MemoriesResourceWithRawResponse",
diff --git a/src/hyperspell/resources/actions.py b/src/hyperspell/resources/actions.py
index 68fe5927..a5025a18 100644
--- a/src/hyperspell/resources/actions.py
+++ b/src/hyperspell/resources/actions.py
@@ -50,31 +50,7 @@ def add_reaction(
*,
channel: str,
name: str,
- provider: Literal[
- "reddit",
- "notion",
- "slack",
- "google_calendar",
- "google_mail",
- "box",
- "dropbox",
- "github",
- "google_drive",
- "vault",
- "web_crawler",
- "trace",
- "microsoft_teams",
- "gmail_actions",
- "granola",
- "fathom",
- "fireflies",
- "linear",
- "hubspot",
- "salesforce",
- "coda",
- "lightfield",
- "gong",
- ],
+ provider: Literal["slack"],
timestamp: str,
connection: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -92,7 +68,7 @@ def add_reaction(
name: Emoji name without colons (e.g., thumbsup)
- provider: Integration provider (e.g., slack)
+ provider: Integration provider.
timestamp: Message timestamp to react to
@@ -127,31 +103,7 @@ def add_reaction(
def send_message(
self,
*,
- provider: Literal[
- "reddit",
- "notion",
- "slack",
- "google_calendar",
- "google_mail",
- "box",
- "dropbox",
- "github",
- "google_drive",
- "vault",
- "web_crawler",
- "trace",
- "microsoft_teams",
- "gmail_actions",
- "granola",
- "fathom",
- "fireflies",
- "linear",
- "hubspot",
- "salesforce",
- "coda",
- "lightfield",
- "gong",
- ],
+ provider: Literal["slack"],
text: str,
channel: Optional[str] | Omit = omit,
connection: Optional[str] | Omit = omit,
@@ -167,7 +119,7 @@ def send_message(
Send a message to a channel or conversation on a connected integration.
Args:
- provider: Integration provider (e.g., slack)
+ provider: Integration provider.
text: Message text
@@ -229,31 +181,7 @@ async def add_reaction(
*,
channel: str,
name: str,
- provider: Literal[
- "reddit",
- "notion",
- "slack",
- "google_calendar",
- "google_mail",
- "box",
- "dropbox",
- "github",
- "google_drive",
- "vault",
- "web_crawler",
- "trace",
- "microsoft_teams",
- "gmail_actions",
- "granola",
- "fathom",
- "fireflies",
- "linear",
- "hubspot",
- "salesforce",
- "coda",
- "lightfield",
- "gong",
- ],
+ provider: Literal["slack"],
timestamp: str,
connection: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -271,7 +199,7 @@ async def add_reaction(
name: Emoji name without colons (e.g., thumbsup)
- provider: Integration provider (e.g., slack)
+ provider: Integration provider.
timestamp: Message timestamp to react to
@@ -306,31 +234,7 @@ async def add_reaction(
async def send_message(
self,
*,
- provider: Literal[
- "reddit",
- "notion",
- "slack",
- "google_calendar",
- "google_mail",
- "box",
- "dropbox",
- "github",
- "google_drive",
- "vault",
- "web_crawler",
- "trace",
- "microsoft_teams",
- "gmail_actions",
- "granola",
- "fathom",
- "fireflies",
- "linear",
- "hubspot",
- "salesforce",
- "coda",
- "lightfield",
- "gong",
- ],
+ provider: Literal["slack"],
text: str,
channel: Optional[str] | Omit = omit,
connection: Optional[str] | Omit = omit,
@@ -346,7 +250,7 @@ async def send_message(
Send a message to a channel or conversation on a connected integration.
Args:
- provider: Integration provider (e.g., slack)
+ provider: Integration provider.
text: Message text
diff --git a/src/hyperspell/resources/auth.py b/src/hyperspell/resources/auth.py
index 7fd0152e..9764a7a5 100644
--- a/src/hyperspell/resources/auth.py
+++ b/src/hyperspell/resources/auth.py
@@ -55,7 +55,12 @@ def delete_user(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuthDeleteUserResponse:
- """Endpoint to delete user."""
+ """
+ Delete the calling user's data (GDPR erasure).
+
+ Deletion is processed asynchronously. The endpoint returns `202 Accepted` with
+ an identifier for the deletion request.
+ """
return self._delete(
"/auth/delete",
options=make_request_options(
@@ -103,6 +108,7 @@ def user_token(
Args:
expires_in: Token lifetime, e.g., '30m', '2h', '1d'. Defaults to 24 hours if not provided.
+ Maximum 30 days.
origin: Origin of the request, used for CSRF protection. If set, the token will only be
valid for requests originating from this origin.
@@ -162,7 +168,12 @@ async def delete_user(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuthDeleteUserResponse:
- """Endpoint to delete user."""
+ """
+ Delete the calling user's data (GDPR erasure).
+
+ Deletion is processed asynchronously. The endpoint returns `202 Accepted` with
+ an identifier for the deletion request.
+ """
return await self._delete(
"/auth/delete",
options=make_request_options(
@@ -210,6 +221,7 @@ async def user_token(
Args:
expires_in: Token lifetime, e.g., '30m', '2h', '1d'. Defaults to 24 hours if not provided.
+ Maximum 30 days.
origin: Origin of the request, used for CSRF protection. If set, the token will only be
valid for requests originating from this origin.
diff --git a/src/hyperspell/resources/connections.py b/src/hyperspell/resources/connections.py
index a31a48ad..a5141a67 100644
--- a/src/hyperspell/resources/connections.py
+++ b/src/hyperspell/resources/connections.py
@@ -74,12 +74,9 @@ def revoke(
"""
Revoke Hyperspell's access to a provider and delete this user's stored data.
- The external OAuth/Unified revoke and the (potentially large) data purge run in
- a background Temporal workflow; this returns `202 Accepted` immediately. A heavy
- provider — a Gmail account can carry hundreds of thousands of chunks — plus a
- slow third-party revoke would otherwise outrun the request timeout: the old
- synchronous path "timed out" for the caller while still finishing server-side,
- making the outcome invisible. Idempotent per (app, user, provider).
+ Revocation and deletion are processed asynchronously, so the endpoint returns
+ `202 Accepted` immediately. Repeated requests for the same app, user, and
+ provider are safe.
Args:
extra_headers: Send extra headers
@@ -154,12 +151,9 @@ async def revoke(
"""
Revoke Hyperspell's access to a provider and delete this user's stored data.
- The external OAuth/Unified revoke and the (potentially large) data purge run in
- a background Temporal workflow; this returns `202 Accepted` immediately. A heavy
- provider — a Gmail account can carry hundreds of thousands of chunks — plus a
- slow third-party revoke would otherwise outrun the request timeout: the old
- synchronous path "timed out" for the caller while still finishing server-side,
- making the outcome invisible. Idempotent per (app, user, provider).
+ Revocation and deletion are processed asynchronously, so the endpoint returns
+ `202 Accepted` immediately. Repeated requests for the same app, user, and
+ provider are safe.
Args:
extra_headers: Send extra headers
diff --git a/src/hyperspell/resources/context_documents/__init__.py b/src/hyperspell/resources/context_documents/__init__.py
new file mode 100644
index 00000000..34942511
--- /dev/null
+++ b/src/hyperspell/resources/context_documents/__init__.py
@@ -0,0 +1,61 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .trees import (
+ TreesResource,
+ AsyncTreesResource,
+ TreesResourceWithRawResponse,
+ AsyncTreesResourceWithRawResponse,
+ TreesResourceWithStreamingResponse,
+ AsyncTreesResourceWithStreamingResponse,
+)
+from .config import (
+ ConfigResource,
+ AsyncConfigResource,
+ ConfigResourceWithRawResponse,
+ AsyncConfigResourceWithRawResponse,
+ ConfigResourceWithStreamingResponse,
+ AsyncConfigResourceWithStreamingResponse,
+)
+from .digests import (
+ DigestsResource,
+ AsyncDigestsResource,
+ DigestsResourceWithRawResponse,
+ AsyncDigestsResourceWithRawResponse,
+ DigestsResourceWithStreamingResponse,
+ AsyncDigestsResourceWithStreamingResponse,
+)
+from .context_documents import (
+ ContextDocumentsResource,
+ AsyncContextDocumentsResource,
+ ContextDocumentsResourceWithRawResponse,
+ AsyncContextDocumentsResourceWithRawResponse,
+ ContextDocumentsResourceWithStreamingResponse,
+ AsyncContextDocumentsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "TreesResource",
+ "AsyncTreesResource",
+ "TreesResourceWithRawResponse",
+ "AsyncTreesResourceWithRawResponse",
+ "TreesResourceWithStreamingResponse",
+ "AsyncTreesResourceWithStreamingResponse",
+ "DigestsResource",
+ "AsyncDigestsResource",
+ "DigestsResourceWithRawResponse",
+ "AsyncDigestsResourceWithRawResponse",
+ "DigestsResourceWithStreamingResponse",
+ "AsyncDigestsResourceWithStreamingResponse",
+ "ConfigResource",
+ "AsyncConfigResource",
+ "ConfigResourceWithRawResponse",
+ "AsyncConfigResourceWithRawResponse",
+ "ConfigResourceWithStreamingResponse",
+ "AsyncConfigResourceWithStreamingResponse",
+ "ContextDocumentsResource",
+ "AsyncContextDocumentsResource",
+ "ContextDocumentsResourceWithRawResponse",
+ "AsyncContextDocumentsResourceWithRawResponse",
+ "ContextDocumentsResourceWithStreamingResponse",
+ "AsyncContextDocumentsResourceWithStreamingResponse",
+]
diff --git a/src/hyperspell/resources/context_documents/config.py b/src/hyperspell/resources/context_documents/config.py
new file mode 100644
index 00000000..6078d950
--- /dev/null
+++ b/src/hyperspell/resources/context_documents/config.py
@@ -0,0 +1,323 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.context_documents import config_update_params
+from ...types.context_documents.config_get_response import ConfigGetResponse
+from ...types.context_documents.config_reset_response import ConfigResetResponse
+from ...types.context_documents.config_update_response import ConfigUpdateResponse
+
+__all__ = ["ConfigResource", "AsyncConfigResource"]
+
+
+class ConfigResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ConfigResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return ConfigResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ConfigResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return ConfigResourceWithStreamingResponse(self)
+
+ def update(
+ self,
+ *,
+ company_prompts: Optional[Dict[str, str]] | Omit = omit,
+ detection_prompt: Optional[str] | Omit = omit,
+ domain: Optional[str] | Omit = omit,
+ personal_prompt: Optional[str] | Omit = omit,
+ source_weights: Optional[Dict[str, str]] | Omit = omit,
+ structure: Optional[config_update_params.Structure] | Omit = omit,
+ workstream_prompts: Optional[Dict[str, str]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigUpdateResponse:
+ """Update the supplied generation settings.
+
+ Changes apply to the next generation.
+
+ This endpoint does not start a generation
+ or modify existing context documents.
+
+ Args:
+ structure: Per-tier document definitions for custom generation.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._patch(
+ "/context-documents/config",
+ body=maybe_transform(
+ {
+ "company_prompts": company_prompts,
+ "detection_prompt": detection_prompt,
+ "domain": domain,
+ "personal_prompt": personal_prompt,
+ "source_weights": source_weights,
+ "structure": structure,
+ "workstream_prompts": workstream_prompts,
+ },
+ config_update_params.ConfigUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigUpdateResponse,
+ )
+
+ def get(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigGetResponse:
+ """Read the customer-editable generation config for the authenticated app."""
+ return self._get(
+ "/context-documents/config",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigGetResponse,
+ )
+
+ def reset(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigResetResponse:
+ """
+ Reset customer-editable generation settings to their defaults.
+
+ Existing context documents remain unchanged. `detected_domain` is retained and
+ used for future generations unless a new domain override is set.
+ """
+ return self._post(
+ "/context-documents/config/reset",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigResetResponse,
+ )
+
+
+class AsyncConfigResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncConfigResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncConfigResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncConfigResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return AsyncConfigResourceWithStreamingResponse(self)
+
+ async def update(
+ self,
+ *,
+ company_prompts: Optional[Dict[str, str]] | Omit = omit,
+ detection_prompt: Optional[str] | Omit = omit,
+ domain: Optional[str] | Omit = omit,
+ personal_prompt: Optional[str] | Omit = omit,
+ source_weights: Optional[Dict[str, str]] | Omit = omit,
+ structure: Optional[config_update_params.Structure] | Omit = omit,
+ workstream_prompts: Optional[Dict[str, str]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigUpdateResponse:
+ """Update the supplied generation settings.
+
+ Changes apply to the next generation.
+
+ This endpoint does not start a generation
+ or modify existing context documents.
+
+ Args:
+ structure: Per-tier document definitions for custom generation.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._patch(
+ "/context-documents/config",
+ body=await async_maybe_transform(
+ {
+ "company_prompts": company_prompts,
+ "detection_prompt": detection_prompt,
+ "domain": domain,
+ "personal_prompt": personal_prompt,
+ "source_weights": source_weights,
+ "structure": structure,
+ "workstream_prompts": workstream_prompts,
+ },
+ config_update_params.ConfigUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigUpdateResponse,
+ )
+
+ async def get(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigGetResponse:
+ """Read the customer-editable generation config for the authenticated app."""
+ return await self._get(
+ "/context-documents/config",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigGetResponse,
+ )
+
+ async def reset(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigResetResponse:
+ """
+ Reset customer-editable generation settings to their defaults.
+
+ Existing context documents remain unchanged. `detected_domain` is retained and
+ used for future generations unless a new domain override is set.
+ """
+ return await self._post(
+ "/context-documents/config/reset",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigResetResponse,
+ )
+
+
+class ConfigResourceWithRawResponse:
+ def __init__(self, config: ConfigResource) -> None:
+ self._config = config
+
+ self.update = to_raw_response_wrapper(
+ config.update,
+ )
+ self.get = to_raw_response_wrapper(
+ config.get,
+ )
+ self.reset = to_raw_response_wrapper(
+ config.reset,
+ )
+
+
+class AsyncConfigResourceWithRawResponse:
+ def __init__(self, config: AsyncConfigResource) -> None:
+ self._config = config
+
+ self.update = async_to_raw_response_wrapper(
+ config.update,
+ )
+ self.get = async_to_raw_response_wrapper(
+ config.get,
+ )
+ self.reset = async_to_raw_response_wrapper(
+ config.reset,
+ )
+
+
+class ConfigResourceWithStreamingResponse:
+ def __init__(self, config: ConfigResource) -> None:
+ self._config = config
+
+ self.update = to_streamed_response_wrapper(
+ config.update,
+ )
+ self.get = to_streamed_response_wrapper(
+ config.get,
+ )
+ self.reset = to_streamed_response_wrapper(
+ config.reset,
+ )
+
+
+class AsyncConfigResourceWithStreamingResponse:
+ def __init__(self, config: AsyncConfigResource) -> None:
+ self._config = config
+
+ self.update = async_to_streamed_response_wrapper(
+ config.update,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ config.get,
+ )
+ self.reset = async_to_streamed_response_wrapper(
+ config.reset,
+ )
diff --git a/src/hyperspell/resources/context_documents/context_documents.py b/src/hyperspell/resources/context_documents/context_documents.py
new file mode 100644
index 00000000..ef19f965
--- /dev/null
+++ b/src/hyperspell/resources/context_documents/context_documents.py
@@ -0,0 +1,491 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal
+
+import httpx
+
+from .trees import (
+ TreesResource,
+ AsyncTreesResource,
+ TreesResourceWithRawResponse,
+ AsyncTreesResourceWithRawResponse,
+ TreesResourceWithStreamingResponse,
+ AsyncTreesResourceWithStreamingResponse,
+)
+from .config import (
+ ConfigResource,
+ AsyncConfigResource,
+ ConfigResourceWithRawResponse,
+ AsyncConfigResourceWithRawResponse,
+ ConfigResourceWithStreamingResponse,
+ AsyncConfigResourceWithStreamingResponse,
+)
+from ...types import context_document_list_params, context_document_generate_params
+from .digests import (
+ DigestsResource,
+ AsyncDigestsResource,
+ DigestsResourceWithRawResponse,
+ AsyncDigestsResourceWithRawResponse,
+ DigestsResourceWithStreamingResponse,
+ AsyncDigestsResourceWithStreamingResponse,
+)
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncContextDocumentsCursorPage, AsyncContextDocumentsCursorPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.context_document_get_response import ContextDocumentGetResponse
+from ...types.context_document_list_response import ContextDocumentListResponse
+from ...types.context_document_generate_response import ContextDocumentGenerateResponse
+
+__all__ = ["ContextDocumentsResource", "AsyncContextDocumentsResource"]
+
+
+class ContextDocumentsResource(SyncAPIResource):
+ @cached_property
+ def trees(self) -> TreesResource:
+ return TreesResource(self._client)
+
+ @cached_property
+ def digests(self) -> DigestsResource:
+ return DigestsResource(self._client)
+
+ @cached_property
+ def config(self) -> ConfigResource:
+ return ConfigResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> ContextDocumentsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return ContextDocumentsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ContextDocumentsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return ContextDocumentsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ cursor: Optional[str] | Omit = omit,
+ limit: int | Omit = omit,
+ status: Optional[Literal["processing", "completed", "failed"]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncContextDocumentsCursorPage[ContextDocumentListResponse]:
+ """
+ List context documents for the authenticated app, most recent first.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/context-documents",
+ page=SyncContextDocumentsCursorPage[ContextDocumentListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "status": status,
+ },
+ context_document_list_params.ContextDocumentListParams,
+ ),
+ ),
+ model=ContextDocumentListResponse,
+ )
+
+ def generate(
+ self,
+ *,
+ model: str | Omit = omit,
+ prompt: Optional[str] | Omit = omit,
+ sources: Optional[SequenceNotStr[str]] | Omit = omit,
+ user_id: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ContextDocumentGenerateResponse:
+ """
+ Generate an LLM-synthesized context document from the app's synced data.
+
+ Generation runs asynchronously. The endpoint returns immediately with status
+ `PROCESSING`; synthesis time depends on the amount of source data.
+
+ Args:
+ model: Model used for final synthesis.
+
+ prompt: Custom prompt template. Replaces the standard summary prompt.
+
+ sources: Integration sources to include (e.g., ['gmail', 'slack']). Defaults to all
+ connected integrations.
+
+ user_id: Scope generation to a specific user's data.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/context-documents/generate",
+ body=maybe_transform(
+ {
+ "model": model,
+ "prompt": prompt,
+ "sources": sources,
+ "user_id": user_id,
+ },
+ context_document_generate_params.ContextDocumentGenerateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ContextDocumentGenerateResponse,
+ )
+
+ def get(
+ self,
+ document_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ContextDocumentGetResponse:
+ """
+ Get a specific context document by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not document_id:
+ raise ValueError(f"Expected a non-empty value for `document_id` but received {document_id!r}")
+ return self._get(
+ path_template("/context-documents/{document_id}", document_id=document_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ContextDocumentGetResponse,
+ )
+
+
+class AsyncContextDocumentsResource(AsyncAPIResource):
+ @cached_property
+ def trees(self) -> AsyncTreesResource:
+ return AsyncTreesResource(self._client)
+
+ @cached_property
+ def digests(self) -> AsyncDigestsResource:
+ return AsyncDigestsResource(self._client)
+
+ @cached_property
+ def config(self) -> AsyncConfigResource:
+ return AsyncConfigResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncContextDocumentsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncContextDocumentsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncContextDocumentsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return AsyncContextDocumentsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ cursor: Optional[str] | Omit = omit,
+ limit: int | Omit = omit,
+ status: Optional[Literal["processing", "completed", "failed"]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[ContextDocumentListResponse, AsyncContextDocumentsCursorPage[ContextDocumentListResponse]]:
+ """
+ List context documents for the authenticated app, most recent first.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/context-documents",
+ page=AsyncContextDocumentsCursorPage[ContextDocumentListResponse],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "status": status,
+ },
+ context_document_list_params.ContextDocumentListParams,
+ ),
+ ),
+ model=ContextDocumentListResponse,
+ )
+
+ async def generate(
+ self,
+ *,
+ model: str | Omit = omit,
+ prompt: Optional[str] | Omit = omit,
+ sources: Optional[SequenceNotStr[str]] | Omit = omit,
+ user_id: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ContextDocumentGenerateResponse:
+ """
+ Generate an LLM-synthesized context document from the app's synced data.
+
+ Generation runs asynchronously. The endpoint returns immediately with status
+ `PROCESSING`; synthesis time depends on the amount of source data.
+
+ Args:
+ model: Model used for final synthesis.
+
+ prompt: Custom prompt template. Replaces the standard summary prompt.
+
+ sources: Integration sources to include (e.g., ['gmail', 'slack']). Defaults to all
+ connected integrations.
+
+ user_id: Scope generation to a specific user's data.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/context-documents/generate",
+ body=await async_maybe_transform(
+ {
+ "model": model,
+ "prompt": prompt,
+ "sources": sources,
+ "user_id": user_id,
+ },
+ context_document_generate_params.ContextDocumentGenerateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ContextDocumentGenerateResponse,
+ )
+
+ async def get(
+ self,
+ document_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ContextDocumentGetResponse:
+ """
+ Get a specific context document by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not document_id:
+ raise ValueError(f"Expected a non-empty value for `document_id` but received {document_id!r}")
+ return await self._get(
+ path_template("/context-documents/{document_id}", document_id=document_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ContextDocumentGetResponse,
+ )
+
+
+class ContextDocumentsResourceWithRawResponse:
+ def __init__(self, context_documents: ContextDocumentsResource) -> None:
+ self._context_documents = context_documents
+
+ self.list = to_raw_response_wrapper(
+ context_documents.list,
+ )
+ self.generate = to_raw_response_wrapper(
+ context_documents.generate,
+ )
+ self.get = to_raw_response_wrapper(
+ context_documents.get,
+ )
+
+ @cached_property
+ def trees(self) -> TreesResourceWithRawResponse:
+ return TreesResourceWithRawResponse(self._context_documents.trees)
+
+ @cached_property
+ def digests(self) -> DigestsResourceWithRawResponse:
+ return DigestsResourceWithRawResponse(self._context_documents.digests)
+
+ @cached_property
+ def config(self) -> ConfigResourceWithRawResponse:
+ return ConfigResourceWithRawResponse(self._context_documents.config)
+
+
+class AsyncContextDocumentsResourceWithRawResponse:
+ def __init__(self, context_documents: AsyncContextDocumentsResource) -> None:
+ self._context_documents = context_documents
+
+ self.list = async_to_raw_response_wrapper(
+ context_documents.list,
+ )
+ self.generate = async_to_raw_response_wrapper(
+ context_documents.generate,
+ )
+ self.get = async_to_raw_response_wrapper(
+ context_documents.get,
+ )
+
+ @cached_property
+ def trees(self) -> AsyncTreesResourceWithRawResponse:
+ return AsyncTreesResourceWithRawResponse(self._context_documents.trees)
+
+ @cached_property
+ def digests(self) -> AsyncDigestsResourceWithRawResponse:
+ return AsyncDigestsResourceWithRawResponse(self._context_documents.digests)
+
+ @cached_property
+ def config(self) -> AsyncConfigResourceWithRawResponse:
+ return AsyncConfigResourceWithRawResponse(self._context_documents.config)
+
+
+class ContextDocumentsResourceWithStreamingResponse:
+ def __init__(self, context_documents: ContextDocumentsResource) -> None:
+ self._context_documents = context_documents
+
+ self.list = to_streamed_response_wrapper(
+ context_documents.list,
+ )
+ self.generate = to_streamed_response_wrapper(
+ context_documents.generate,
+ )
+ self.get = to_streamed_response_wrapper(
+ context_documents.get,
+ )
+
+ @cached_property
+ def trees(self) -> TreesResourceWithStreamingResponse:
+ return TreesResourceWithStreamingResponse(self._context_documents.trees)
+
+ @cached_property
+ def digests(self) -> DigestsResourceWithStreamingResponse:
+ return DigestsResourceWithStreamingResponse(self._context_documents.digests)
+
+ @cached_property
+ def config(self) -> ConfigResourceWithStreamingResponse:
+ return ConfigResourceWithStreamingResponse(self._context_documents.config)
+
+
+class AsyncContextDocumentsResourceWithStreamingResponse:
+ def __init__(self, context_documents: AsyncContextDocumentsResource) -> None:
+ self._context_documents = context_documents
+
+ self.list = async_to_streamed_response_wrapper(
+ context_documents.list,
+ )
+ self.generate = async_to_streamed_response_wrapper(
+ context_documents.generate,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ context_documents.get,
+ )
+
+ @cached_property
+ def trees(self) -> AsyncTreesResourceWithStreamingResponse:
+ return AsyncTreesResourceWithStreamingResponse(self._context_documents.trees)
+
+ @cached_property
+ def digests(self) -> AsyncDigestsResourceWithStreamingResponse:
+ return AsyncDigestsResourceWithStreamingResponse(self._context_documents.digests)
+
+ @cached_property
+ def config(self) -> AsyncConfigResourceWithStreamingResponse:
+ return AsyncConfigResourceWithStreamingResponse(self._context_documents.config)
diff --git a/src/hyperspell/resources/context_documents/digests.py b/src/hyperspell/resources/context_documents/digests.py
new file mode 100644
index 00000000..a3a983aa
--- /dev/null
+++ b/src/hyperspell/resources/context_documents/digests.py
@@ -0,0 +1,319 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.context_documents import digest_list_params, digest_generate_params
+from ...types.context_documents.digest_list_response import DigestListResponse
+from ...types.context_documents.digest_generate_response import DigestGenerateResponse
+
+__all__ = ["DigestsResource", "AsyncDigestsResource"]
+
+
+class DigestsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> DigestsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return DigestsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> DigestsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return DigestsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ period: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> DigestListResponse:
+ """
+ List recent digest summaries, newest first.
+
+ Filter by cadence with `period=daily` or `period=weekly`. Fetch full content
+ with `GET /context-documents/tree/by-id/{tree_id}`.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/context-documents/digest/list",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "period": period,
+ },
+ digest_list_params.DigestListParams,
+ ),
+ ),
+ cast_to=DigestListResponse,
+ )
+
+ def generate(
+ self,
+ *,
+ period: str | Omit = omit,
+ sources: Optional[SequenceNotStr[str]] | Omit = omit,
+ window_end: Union[str, datetime, None] | Omit = omit,
+ window_start: Union[str, datetime, None] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> DigestGenerateResponse:
+ """
+ Generate a date-windowed "what the company did today" digest.
+
+ Returns immediately with a tree ID. Poll
+ `GET /context-documents/tree/{tree_id}/progress` for completion or fetch the
+ result with `GET /context-documents/tree/by-id/{tree_id}`.
+
+ Args:
+ period: Digest cadence: 'daily' or 'weekly'. Sets the default window when none is given.
+
+ sources: Integration sources to include (e.g., ['slack', 'github']). Defaults to all.
+
+ window_end: Exclusive upper bound of the digest window. Defaults to now.
+
+ window_start: Inclusive lower bound of the digest window. Defaults to midnight UTC today
+ (paired with window_end=now) when omitted. Both bounds must be supplied
+ together.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/context-documents/digest",
+ body=maybe_transform(
+ {
+ "period": period,
+ "sources": sources,
+ "window_end": window_end,
+ "window_start": window_start,
+ },
+ digest_generate_params.DigestGenerateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=DigestGenerateResponse,
+ )
+
+
+class AsyncDigestsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncDigestsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncDigestsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncDigestsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return AsyncDigestsResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ period: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> DigestListResponse:
+ """
+ List recent digest summaries, newest first.
+
+ Filter by cadence with `period=daily` or `period=weekly`. Fetch full content
+ with `GET /context-documents/tree/by-id/{tree_id}`.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/context-documents/digest/list",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "limit": limit,
+ "period": period,
+ },
+ digest_list_params.DigestListParams,
+ ),
+ ),
+ cast_to=DigestListResponse,
+ )
+
+ async def generate(
+ self,
+ *,
+ period: str | Omit = omit,
+ sources: Optional[SequenceNotStr[str]] | Omit = omit,
+ window_end: Union[str, datetime, None] | Omit = omit,
+ window_start: Union[str, datetime, None] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> DigestGenerateResponse:
+ """
+ Generate a date-windowed "what the company did today" digest.
+
+ Returns immediately with a tree ID. Poll
+ `GET /context-documents/tree/{tree_id}/progress` for completion or fetch the
+ result with `GET /context-documents/tree/by-id/{tree_id}`.
+
+ Args:
+ period: Digest cadence: 'daily' or 'weekly'. Sets the default window when none is given.
+
+ sources: Integration sources to include (e.g., ['slack', 'github']). Defaults to all.
+
+ window_end: Exclusive upper bound of the digest window. Defaults to now.
+
+ window_start: Inclusive lower bound of the digest window. Defaults to midnight UTC today
+ (paired with window_end=now) when omitted. Both bounds must be supplied
+ together.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/context-documents/digest",
+ body=await async_maybe_transform(
+ {
+ "period": period,
+ "sources": sources,
+ "window_end": window_end,
+ "window_start": window_start,
+ },
+ digest_generate_params.DigestGenerateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=DigestGenerateResponse,
+ )
+
+
+class DigestsResourceWithRawResponse:
+ def __init__(self, digests: DigestsResource) -> None:
+ self._digests = digests
+
+ self.list = to_raw_response_wrapper(
+ digests.list,
+ )
+ self.generate = to_raw_response_wrapper(
+ digests.generate,
+ )
+
+
+class AsyncDigestsResourceWithRawResponse:
+ def __init__(self, digests: AsyncDigestsResource) -> None:
+ self._digests = digests
+
+ self.list = async_to_raw_response_wrapper(
+ digests.list,
+ )
+ self.generate = async_to_raw_response_wrapper(
+ digests.generate,
+ )
+
+
+class DigestsResourceWithStreamingResponse:
+ def __init__(self, digests: DigestsResource) -> None:
+ self._digests = digests
+
+ self.list = to_streamed_response_wrapper(
+ digests.list,
+ )
+ self.generate = to_streamed_response_wrapper(
+ digests.generate,
+ )
+
+
+class AsyncDigestsResourceWithStreamingResponse:
+ def __init__(self, digests: AsyncDigestsResource) -> None:
+ self._digests = digests
+
+ self.list = async_to_streamed_response_wrapper(
+ digests.list,
+ )
+ self.generate = async_to_streamed_response_wrapper(
+ digests.generate,
+ )
diff --git a/src/hyperspell/resources/context_documents/trees.py b/src/hyperspell/resources/context_documents/trees.py
new file mode 100644
index 00000000..3ae75cf3
--- /dev/null
+++ b/src/hyperspell/resources/context_documents/trees.py
@@ -0,0 +1,470 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.context_documents import tree_generate_params, tree_get_latest_params
+from ...types.context_documents.tree_get_response import TreeGetResponse
+from ...types.context_documents.tree_generate_response import TreeGenerateResponse
+from ...types.context_documents.tree_progress_response import TreeProgressResponse
+from ...types.context_documents.tree_get_latest_response import TreeGetLatestResponse
+
+__all__ = ["TreesResource", "AsyncTreesResource"]
+
+
+class TreesResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> TreesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return TreesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> TreesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return TreesResourceWithStreamingResponse(self)
+
+ def generate(
+ self,
+ *,
+ sources: Optional[SequenceNotStr[str]] | Omit = omit,
+ user_id: Optional[str] | Omit = omit,
+ workstream_name: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeGenerateResponse:
+ """
+ Generate a three-tier context document tree for local push delivery.
+
+ Creates company, workstream, and personal context documents from the app's
+ synced data. Returns immediately with a tree ID; use
+ `GET /context-documents/tree/latest` to retrieve the result.
+
+ Args:
+ sources: Integration sources to include (e.g., ['gmail', 'slack']). Defaults to all.
+
+ user_id: User ID for personal tier scoping. When set, personal/context.md is generated
+ from this user's data only. Company and workstream tiers still use all data.
+
+ workstream_name: Generate docs for this workstream only (skip auto-detection).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/context-documents/tree",
+ body=maybe_transform(
+ {
+ "sources": sources,
+ "user_id": user_id,
+ "workstream_name": workstream_name,
+ },
+ tree_generate_params.TreeGenerateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TreeGenerateResponse,
+ )
+
+ def get(
+ self,
+ tree_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeGetResponse:
+ """
+ Fetch a specific tree by its tree ID instead of selecting the latest one.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not tree_id:
+ raise ValueError(f"Expected a non-empty value for `tree_id` but received {tree_id!r}")
+ return self._get(
+ path_template("/context-documents/tree/by-id/{tree_id}", tree_id=tree_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TreeGetResponse,
+ )
+
+ def get_latest(
+ self,
+ *,
+ status: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeGetLatestResponse:
+ """
+ Get the most recent context document tree for the authenticated app.
+
+ By default, the endpoint returns the latest ready tree. Readiness depends on
+ whether the app has `require_review` enabled:
+
+ - `require_review=False` (default): return the latest completed tree.
+ - `require_review=True`: return the latest published tree.
+
+ `status` filters to a specific status (case-insensitive). When no ready tree
+ exists yet, the endpoint returns the newest available generation state.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/context-documents/tree/latest",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"status": status}, tree_get_latest_params.TreeGetLatestParams),
+ ),
+ cast_to=TreeGetLatestResponse,
+ )
+
+ def progress(
+ self,
+ tree_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeProgressResponse:
+ """
+ Return the generation progress for a single tree.
+
+ Active generations include phase and counter data. Completed generations, and
+ generations without detailed progress data, return status only.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not tree_id:
+ raise ValueError(f"Expected a non-empty value for `tree_id` but received {tree_id!r}")
+ return self._get(
+ path_template("/context-documents/tree/{tree_id}/progress", tree_id=tree_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TreeProgressResponse,
+ )
+
+
+class AsyncTreesResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncTreesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
+ """
+ return AsyncTreesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncTreesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
+ """
+ return AsyncTreesResourceWithStreamingResponse(self)
+
+ async def generate(
+ self,
+ *,
+ sources: Optional[SequenceNotStr[str]] | Omit = omit,
+ user_id: Optional[str] | Omit = omit,
+ workstream_name: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeGenerateResponse:
+ """
+ Generate a three-tier context document tree for local push delivery.
+
+ Creates company, workstream, and personal context documents from the app's
+ synced data. Returns immediately with a tree ID; use
+ `GET /context-documents/tree/latest` to retrieve the result.
+
+ Args:
+ sources: Integration sources to include (e.g., ['gmail', 'slack']). Defaults to all.
+
+ user_id: User ID for personal tier scoping. When set, personal/context.md is generated
+ from this user's data only. Company and workstream tiers still use all data.
+
+ workstream_name: Generate docs for this workstream only (skip auto-detection).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/context-documents/tree",
+ body=await async_maybe_transform(
+ {
+ "sources": sources,
+ "user_id": user_id,
+ "workstream_name": workstream_name,
+ },
+ tree_generate_params.TreeGenerateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TreeGenerateResponse,
+ )
+
+ async def get(
+ self,
+ tree_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeGetResponse:
+ """
+ Fetch a specific tree by its tree ID instead of selecting the latest one.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not tree_id:
+ raise ValueError(f"Expected a non-empty value for `tree_id` but received {tree_id!r}")
+ return await self._get(
+ path_template("/context-documents/tree/by-id/{tree_id}", tree_id=tree_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TreeGetResponse,
+ )
+
+ async def get_latest(
+ self,
+ *,
+ status: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeGetLatestResponse:
+ """
+ Get the most recent context document tree for the authenticated app.
+
+ By default, the endpoint returns the latest ready tree. Readiness depends on
+ whether the app has `require_review` enabled:
+
+ - `require_review=False` (default): return the latest completed tree.
+ - `require_review=True`: return the latest published tree.
+
+ `status` filters to a specific status (case-insensitive). When no ready tree
+ exists yet, the endpoint returns the newest available generation state.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/context-documents/tree/latest",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"status": status}, tree_get_latest_params.TreeGetLatestParams),
+ ),
+ cast_to=TreeGetLatestResponse,
+ )
+
+ async def progress(
+ self,
+ tree_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> TreeProgressResponse:
+ """
+ Return the generation progress for a single tree.
+
+ Active generations include phase and counter data. Completed generations, and
+ generations without detailed progress data, return status only.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not tree_id:
+ raise ValueError(f"Expected a non-empty value for `tree_id` but received {tree_id!r}")
+ return await self._get(
+ path_template("/context-documents/tree/{tree_id}/progress", tree_id=tree_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=TreeProgressResponse,
+ )
+
+
+class TreesResourceWithRawResponse:
+ def __init__(self, trees: TreesResource) -> None:
+ self._trees = trees
+
+ self.generate = to_raw_response_wrapper(
+ trees.generate,
+ )
+ self.get = to_raw_response_wrapper(
+ trees.get,
+ )
+ self.get_latest = to_raw_response_wrapper(
+ trees.get_latest,
+ )
+ self.progress = to_raw_response_wrapper(
+ trees.progress,
+ )
+
+
+class AsyncTreesResourceWithRawResponse:
+ def __init__(self, trees: AsyncTreesResource) -> None:
+ self._trees = trees
+
+ self.generate = async_to_raw_response_wrapper(
+ trees.generate,
+ )
+ self.get = async_to_raw_response_wrapper(
+ trees.get,
+ )
+ self.get_latest = async_to_raw_response_wrapper(
+ trees.get_latest,
+ )
+ self.progress = async_to_raw_response_wrapper(
+ trees.progress,
+ )
+
+
+class TreesResourceWithStreamingResponse:
+ def __init__(self, trees: TreesResource) -> None:
+ self._trees = trees
+
+ self.generate = to_streamed_response_wrapper(
+ trees.generate,
+ )
+ self.get = to_streamed_response_wrapper(
+ trees.get,
+ )
+ self.get_latest = to_streamed_response_wrapper(
+ trees.get_latest,
+ )
+ self.progress = to_streamed_response_wrapper(
+ trees.progress,
+ )
+
+
+class AsyncTreesResourceWithStreamingResponse:
+ def __init__(self, trees: AsyncTreesResource) -> None:
+ self._trees = trees
+
+ self.generate = async_to_streamed_response_wrapper(
+ trees.generate,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ trees.get,
+ )
+ self.get_latest = async_to_streamed_response_wrapper(
+ trees.get_latest,
+ )
+ self.progress = async_to_streamed_response_wrapper(
+ trees.progress,
+ )
diff --git a/src/hyperspell/resources/evaluate.py b/src/hyperspell/resources/evaluate.py
index 5dee99db..96f3f335 100644
--- a/src/hyperspell/resources/evaluate.py
+++ b/src/hyperspell/resources/evaluate.py
@@ -96,8 +96,8 @@ def list_queries(
"""
Paginate through all prior queries for the app, newest first.
- User tokens only see their own queries; admin tokens see every query in the app
- and can narrow to a single user with the `user_id` filter.
+ Requests scoped to a user only see that user's queries. App-wide requests see
+ every query in the app and can filter by `user_id`.
Args:
user_id: Filter queries by the user that issued them.
@@ -283,8 +283,8 @@ def list_queries(
"""
Paginate through all prior queries for the app, newest first.
- User tokens only see their own queries; admin tokens see every query in the app
- and can narrow to a single user with the `user_id` filter.
+ Requests scoped to a user only see that user's queries. App-wide requests see
+ every query in the app and can filter by `user_id`.
Args:
user_id: Filter queries by the user that issued them.
diff --git a/src/hyperspell/resources/integrations/__init__.py b/src/hyperspell/resources/integrations/__init__.py
index f72f59aa..b483edf1 100644
--- a/src/hyperspell/resources/integrations/__init__.py
+++ b/src/hyperspell/resources/integrations/__init__.py
@@ -1,13 +1,5 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from .slack import (
- SlackResource,
- AsyncSlackResource,
- SlackResourceWithRawResponse,
- AsyncSlackResourceWithRawResponse,
- SlackResourceWithStreamingResponse,
- AsyncSlackResourceWithStreamingResponse,
-)
from .web_crawler import (
WebCrawlerResource,
AsyncWebCrawlerResource,
@@ -24,34 +16,14 @@
IntegrationsResourceWithStreamingResponse,
AsyncIntegrationsResourceWithStreamingResponse,
)
-from .google_calendar import (
- GoogleCalendarResource,
- AsyncGoogleCalendarResource,
- GoogleCalendarResourceWithRawResponse,
- AsyncGoogleCalendarResourceWithRawResponse,
- GoogleCalendarResourceWithStreamingResponse,
- AsyncGoogleCalendarResourceWithStreamingResponse,
-)
__all__ = [
- "GoogleCalendarResource",
- "AsyncGoogleCalendarResource",
- "GoogleCalendarResourceWithRawResponse",
- "AsyncGoogleCalendarResourceWithRawResponse",
- "GoogleCalendarResourceWithStreamingResponse",
- "AsyncGoogleCalendarResourceWithStreamingResponse",
"WebCrawlerResource",
"AsyncWebCrawlerResource",
"WebCrawlerResourceWithRawResponse",
"AsyncWebCrawlerResourceWithRawResponse",
"WebCrawlerResourceWithStreamingResponse",
"AsyncWebCrawlerResourceWithStreamingResponse",
- "SlackResource",
- "AsyncSlackResource",
- "SlackResourceWithRawResponse",
- "AsyncSlackResourceWithRawResponse",
- "SlackResourceWithStreamingResponse",
- "AsyncSlackResourceWithStreamingResponse",
"IntegrationsResource",
"AsyncIntegrationsResource",
"IntegrationsResourceWithRawResponse",
diff --git a/src/hyperspell/resources/integrations/google_calendar.py b/src/hyperspell/resources/integrations/google_calendar.py
deleted file mode 100644
index fd8fef10..00000000
--- a/src/hyperspell/resources/integrations/google_calendar.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import httpx
-
-from ..._types import Body, Query, Headers, NotGiven, not_given
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ..._base_client import make_request_options
-from ...types.integrations.calendar import Calendar
-
-__all__ = ["GoogleCalendarResource", "AsyncGoogleCalendarResource"]
-
-
-class GoogleCalendarResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> GoogleCalendarResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
- """
- return GoogleCalendarResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> GoogleCalendarResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
- """
- return GoogleCalendarResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Calendar:
- """List available calendars for a user.
-
- This can be used to ie. populate a dropdown
- for the user to select a calendar.
- """
- return self._get(
- "/integrations/google_calendar/list",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Calendar,
- )
-
-
-class AsyncGoogleCalendarResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncGoogleCalendarResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
- """
- return AsyncGoogleCalendarResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncGoogleCalendarResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
- """
- return AsyncGoogleCalendarResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> Calendar:
- """List available calendars for a user.
-
- This can be used to ie. populate a dropdown
- for the user to select a calendar.
- """
- return await self._get(
- "/integrations/google_calendar/list",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
- ),
- cast_to=Calendar,
- )
-
-
-class GoogleCalendarResourceWithRawResponse:
- def __init__(self, google_calendar: GoogleCalendarResource) -> None:
- self._google_calendar = google_calendar
-
- self.list = to_raw_response_wrapper(
- google_calendar.list,
- )
-
-
-class AsyncGoogleCalendarResourceWithRawResponse:
- def __init__(self, google_calendar: AsyncGoogleCalendarResource) -> None:
- self._google_calendar = google_calendar
-
- self.list = async_to_raw_response_wrapper(
- google_calendar.list,
- )
-
-
-class GoogleCalendarResourceWithStreamingResponse:
- def __init__(self, google_calendar: GoogleCalendarResource) -> None:
- self._google_calendar = google_calendar
-
- self.list = to_streamed_response_wrapper(
- google_calendar.list,
- )
-
-
-class AsyncGoogleCalendarResourceWithStreamingResponse:
- def __init__(self, google_calendar: AsyncGoogleCalendarResource) -> None:
- self._google_calendar = google_calendar
-
- self.list = async_to_streamed_response_wrapper(
- google_calendar.list,
- )
diff --git a/src/hyperspell/resources/integrations/integrations.py b/src/hyperspell/resources/integrations/integrations.py
index 72f1d33d..2138d681 100644
--- a/src/hyperspell/resources/integrations/integrations.py
+++ b/src/hyperspell/resources/integrations/integrations.py
@@ -6,14 +6,6 @@
import httpx
-from .slack import (
- SlackResource,
- AsyncSlackResource,
- SlackResourceWithRawResponse,
- AsyncSlackResourceWithRawResponse,
- SlackResourceWithStreamingResponse,
- AsyncSlackResourceWithStreamingResponse,
-)
from ...types import integration_connect_params
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
@@ -34,14 +26,6 @@
AsyncWebCrawlerResourceWithStreamingResponse,
)
from ..._base_client import make_request_options
-from .google_calendar import (
- GoogleCalendarResource,
- AsyncGoogleCalendarResource,
- GoogleCalendarResourceWithRawResponse,
- AsyncGoogleCalendarResourceWithRawResponse,
- GoogleCalendarResourceWithStreamingResponse,
- AsyncGoogleCalendarResourceWithStreamingResponse,
-)
from ...types.integration_list_response import IntegrationListResponse
from ...types.integration_connect_response import IntegrationConnectResponse
@@ -49,18 +33,10 @@
class IntegrationsResource(SyncAPIResource):
- @cached_property
- def google_calendar(self) -> GoogleCalendarResource:
- return GoogleCalendarResource(self._client)
-
@cached_property
def web_crawler(self) -> WebCrawlerResource:
return WebCrawlerResource(self._client)
- @cached_property
- def slack(self) -> SlackResource:
- return SlackResource(self._client)
-
@cached_property
def with_raw_response(self) -> IntegrationsResourceWithRawResponse:
"""
@@ -141,18 +117,10 @@ def connect(
class AsyncIntegrationsResource(AsyncAPIResource):
- @cached_property
- def google_calendar(self) -> AsyncGoogleCalendarResource:
- return AsyncGoogleCalendarResource(self._client)
-
@cached_property
def web_crawler(self) -> AsyncWebCrawlerResource:
return AsyncWebCrawlerResource(self._client)
- @cached_property
- def slack(self) -> AsyncSlackResource:
- return AsyncSlackResource(self._client)
-
@cached_property
def with_raw_response(self) -> AsyncIntegrationsResourceWithRawResponse:
"""
@@ -243,18 +211,10 @@ def __init__(self, integrations: IntegrationsResource) -> None:
integrations.connect,
)
- @cached_property
- def google_calendar(self) -> GoogleCalendarResourceWithRawResponse:
- return GoogleCalendarResourceWithRawResponse(self._integrations.google_calendar)
-
@cached_property
def web_crawler(self) -> WebCrawlerResourceWithRawResponse:
return WebCrawlerResourceWithRawResponse(self._integrations.web_crawler)
- @cached_property
- def slack(self) -> SlackResourceWithRawResponse:
- return SlackResourceWithRawResponse(self._integrations.slack)
-
class AsyncIntegrationsResourceWithRawResponse:
def __init__(self, integrations: AsyncIntegrationsResource) -> None:
@@ -267,18 +227,10 @@ def __init__(self, integrations: AsyncIntegrationsResource) -> None:
integrations.connect,
)
- @cached_property
- def google_calendar(self) -> AsyncGoogleCalendarResourceWithRawResponse:
- return AsyncGoogleCalendarResourceWithRawResponse(self._integrations.google_calendar)
-
@cached_property
def web_crawler(self) -> AsyncWebCrawlerResourceWithRawResponse:
return AsyncWebCrawlerResourceWithRawResponse(self._integrations.web_crawler)
- @cached_property
- def slack(self) -> AsyncSlackResourceWithRawResponse:
- return AsyncSlackResourceWithRawResponse(self._integrations.slack)
-
class IntegrationsResourceWithStreamingResponse:
def __init__(self, integrations: IntegrationsResource) -> None:
@@ -291,18 +243,10 @@ def __init__(self, integrations: IntegrationsResource) -> None:
integrations.connect,
)
- @cached_property
- def google_calendar(self) -> GoogleCalendarResourceWithStreamingResponse:
- return GoogleCalendarResourceWithStreamingResponse(self._integrations.google_calendar)
-
@cached_property
def web_crawler(self) -> WebCrawlerResourceWithStreamingResponse:
return WebCrawlerResourceWithStreamingResponse(self._integrations.web_crawler)
- @cached_property
- def slack(self) -> SlackResourceWithStreamingResponse:
- return SlackResourceWithStreamingResponse(self._integrations.slack)
-
class AsyncIntegrationsResourceWithStreamingResponse:
def __init__(self, integrations: AsyncIntegrationsResource) -> None:
@@ -315,14 +259,6 @@ def __init__(self, integrations: AsyncIntegrationsResource) -> None:
integrations.connect,
)
- @cached_property
- def google_calendar(self) -> AsyncGoogleCalendarResourceWithStreamingResponse:
- return AsyncGoogleCalendarResourceWithStreamingResponse(self._integrations.google_calendar)
-
@cached_property
def web_crawler(self) -> AsyncWebCrawlerResourceWithStreamingResponse:
return AsyncWebCrawlerResourceWithStreamingResponse(self._integrations.web_crawler)
-
- @cached_property
- def slack(self) -> AsyncSlackResourceWithStreamingResponse:
- return AsyncSlackResourceWithStreamingResponse(self._integrations.slack)
diff --git a/src/hyperspell/resources/integrations/slack.py b/src/hyperspell/resources/integrations/slack.py
deleted file mode 100644
index 10144dc6..00000000
--- a/src/hyperspell/resources/integrations/slack.py
+++ /dev/null
@@ -1,228 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-
-import httpx
-
-from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given
-from ..._utils import maybe_transform, async_maybe_transform
-from ..._compat import cached_property
-from ..._resource import SyncAPIResource, AsyncAPIResource
-from ..._response import (
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
-)
-from ..._base_client import make_request_options
-from ...types.integrations import slack_list_params
-
-__all__ = ["SlackResource", "AsyncSlackResource"]
-
-
-class SlackResource(SyncAPIResource):
- @cached_property
- def with_raw_response(self) -> SlackResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
- """
- return SlackResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SlackResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
- """
- return SlackResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- channels: SequenceNotStr[str] | Omit = omit,
- exclude_archived: Optional[bool] | Omit = omit,
- include_dms: bool | Omit = omit,
- include_group_dms: bool | Omit = omit,
- include_private: bool | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> object:
- """
- List Slack conversations accessible to the user via the live Nango connection.
-
- Returns minimal channel metadata suitable for selection UIs. If required scopes
- are missing, Slack's error is propagated with details.
-
- Supports filtering by channels, including/excluding private channels, DMs, group
- DMs, and archived channels based on the provided search options.
-
- Args:
- channels: List of Slack channels to include (by id, name, or #name).
-
- exclude_archived: If set, pass 'exclude_archived' to Slack. If None, omit the param.
-
- include_dms: Include direct messages (im) when listing conversations.
-
- include_group_dms: Include group DMs (mpim) when listing conversations.
-
- include_private: Include private channels when constructing Slack 'types'.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return self._get(
- "/integrations/slack/list",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform(
- {
- "channels": channels,
- "exclude_archived": exclude_archived,
- "include_dms": include_dms,
- "include_group_dms": include_group_dms,
- "include_private": include_private,
- },
- slack_list_params.SlackListParams,
- ),
- ),
- cast_to=object,
- )
-
-
-class AsyncSlackResource(AsyncAPIResource):
- @cached_property
- def with_raw_response(self) -> AsyncSlackResourceWithRawResponse:
- """
- This property can be used as a prefix for any HTTP method call to return
- the raw response object instead of the parsed content.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers
- """
- return AsyncSlackResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSlackResourceWithStreamingResponse:
- """
- An alternative to `.with_raw_response` that doesn't eagerly read the response body.
-
- For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response
- """
- return AsyncSlackResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- channels: SequenceNotStr[str] | Omit = omit,
- exclude_archived: Optional[bool] | Omit = omit,
- include_dms: bool | Omit = omit,
- include_group_dms: bool | Omit = omit,
- include_private: bool | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> object:
- """
- List Slack conversations accessible to the user via the live Nango connection.
-
- Returns minimal channel metadata suitable for selection UIs. If required scopes
- are missing, Slack's error is propagated with details.
-
- Supports filtering by channels, including/excluding private channels, DMs, group
- DMs, and archived channels based on the provided search options.
-
- Args:
- channels: List of Slack channels to include (by id, name, or #name).
-
- exclude_archived: If set, pass 'exclude_archived' to Slack. If None, omit the param.
-
- include_dms: Include direct messages (im) when listing conversations.
-
- include_group_dms: Include group DMs (mpim) when listing conversations.
-
- include_private: Include private channels when constructing Slack 'types'.
-
- extra_headers: Send extra headers
-
- extra_query: Add additional query parameters to the request
-
- extra_body: Add additional JSON properties to the request
-
- timeout: Override the client-level default timeout for this request, in seconds
- """
- return await self._get(
- "/integrations/slack/list",
- options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {
- "channels": channels,
- "exclude_archived": exclude_archived,
- "include_dms": include_dms,
- "include_group_dms": include_group_dms,
- "include_private": include_private,
- },
- slack_list_params.SlackListParams,
- ),
- ),
- cast_to=object,
- )
-
-
-class SlackResourceWithRawResponse:
- def __init__(self, slack: SlackResource) -> None:
- self._slack = slack
-
- self.list = to_raw_response_wrapper(
- slack.list,
- )
-
-
-class AsyncSlackResourceWithRawResponse:
- def __init__(self, slack: AsyncSlackResource) -> None:
- self._slack = slack
-
- self.list = async_to_raw_response_wrapper(
- slack.list,
- )
-
-
-class SlackResourceWithStreamingResponse:
- def __init__(self, slack: SlackResource) -> None:
- self._slack = slack
-
- self.list = to_streamed_response_wrapper(
- slack.list,
- )
-
-
-class AsyncSlackResourceWithStreamingResponse:
- def __init__(self, slack: AsyncSlackResource) -> None:
- self._slack = slack
-
- self.list = async_to_streamed_response_wrapper(
- slack.list,
- )
diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py
index 21fbf295..7e30f42d 100644
--- a/src/hyperspell/resources/memories.py
+++ b/src/hyperspell/resources/memories.py
@@ -10,6 +10,7 @@
from ..types import (
memory_add_params,
+ memory_get_params,
memory_list_params,
memory_search_params,
memory_update_params,
@@ -70,24 +71,36 @@ def update(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
],
collection: Union[str, object, None] | Omit = omit,
date: Union[Union[str, datetime], object, None] | Omit = omit,
@@ -159,6 +172,7 @@ def list(
collection: Optional[str] | Omit = omit,
cursor: Optional[str] | Omit = omit,
filter: Optional[str] | Omit = omit,
+ include_chunks: int | Omit = omit,
size: int | Omit = omit,
source: Optional[
Literal[
@@ -167,28 +181,44 @@ def list(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
| Omit = omit,
- status: Optional[Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]]
+ status: Optional[
+ Literal[
+ "pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"
+ ]
+ ]
| Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -209,6 +239,10 @@ def list(
Filter documents by metadata using MongoDB-style operators. Example:
{"department": "engineering", "priority": {"$gt": 3}}
+ include_chunks: When > 0, include up to this many extracted memories (chunks with summaries) per
+ document in each item's `chunks` field, in document order. 0 (default) omits
+ them.
+
source: Filter documents by source.
status: Filter documents by status.
@@ -234,6 +268,7 @@ def list(
"collection": collection,
"cursor": cursor,
"filter": filter,
+ "include_chunks": include_chunks,
"size": size,
"source": source,
"status": status,
@@ -254,24 +289,36 @@ def delete(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -281,23 +328,7 @@ def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MemoryDeleteResponse:
"""
- Delete a memory and its associated chunks from the index.
-
- This removes the memory completely from the vector index and database. The
- operation deletes:
-
- 1. All chunks associated with the resource (including embeddings)
- 2. The documents row AND any legacy resources rows sharing the identity —
- leaving either one behind would resurrect the memory through the double-read
- path (ENG-2477).
-
- Args: source: The document provider (e.g., gmail, notion, vault) resource_id:
- The unique identifier of the resource to delete api_token: Authentication token
-
- Returns: MemoryDeletionResponse with deletion details
-
- Raises: DocumentNotFound: If the resource doesn't exist or user doesn't have
- access
+ Delete a memory accessible to the authenticated credential.
Args:
extra_headers: Send extra headers
@@ -402,9 +433,9 @@ def add_bulk(
"""
Adds multiple documents to the index in a single request.
- All items are validated before any database operations occur. If any item fails
- validation, the entire batch is rejected with a 422 error detailing which items
- failed and why.
+ All items are validated before processing begins. If any item fails validation,
+ the entire batch is rejected with a 422 error detailing which items failed and
+ why.
Maximum 100 items per request. Each item follows the same schema as the
single-item /memories/add endpoint.
@@ -439,25 +470,38 @@ def get(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
],
+ include_chunks: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -466,10 +510,13 @@ def get(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MemoryGetResponse:
"""
- Retrieves a document by provider and resource_id, as a document-shaped response
- carrying the full hyperdoc tree (ENG-2479 Phase 4).
+ Retrieve a document by provider and resource ID, including its full hyperdoc
+ tree.
Args:
+ include_chunks: When true, include the document's extracted memories (chunks with summaries) in
+ the `chunks` field, in document order.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -485,7 +532,11 @@ def get(
return self._get(
path_template("/memories/get/{source}/{resource_id}", source=source, resource_id=resource_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"include_chunks": include_chunks}, memory_get_params.MemoryGetParams),
),
cast_to=MemoryGetResponse,
)
@@ -506,24 +557,36 @@ def search(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
| Omit = omit,
@@ -542,13 +605,10 @@ def search(
answer: If true, the query will be answered along with matching source documents.
- effort: How much compute to spend on retrieval. Mirrors the dial popularized by
- frontier-model APIs (OpenAI reasoning_effort, etc.). 'minimal' = verbatim
- single-shot retrieval (fastest). 'low' = LLM rewrites the query for better
- retrieval and extracts date filters. 'medium' = rewrite + agentic refinement
- loop (the answer LLM may request additional retrieval rounds, up to 3). 'high' =
- rewrite + extended refinement (up to 6 rounds). Higher = better recall, more
- latency, more cost.
+ effort: Controls retrieval thoroughness. 'minimal' performs direct retrieval. 'low'
+ improves the query and extracts date filters. 'medium' adds up to 3 refinement
+ rounds; 'high' allows up to 6. Higher levels can improve recall but add latency
+ and cost.
max_results: Maximum number of results to return.
@@ -557,10 +617,12 @@ def search(
provenance:
If true (effort='very_high' only), attach a provenance record to the response:
the source documents and entities the answer was grounded in, the agent's search
- trajectory, and any sources that failed. Adds one indexed lookup; intended for
- auditability / compliance use cases.
+ trajectory, and any sources that failed. Intended for auditability and
+ compliance use cases.
- sources: Only query documents from these sources.
+ sources: Only query documents from these sources. Names are case-insensitive and accept
+ either separator, so `Google Drive`'s provider may be given as `google_drive`,
+ `google-drive`, or `GOOGLE_DRIVE`.
extra_headers: Send extra headers
@@ -703,24 +765,36 @@ async def update(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
],
collection: Union[str, object, None] | Omit = omit,
date: Union[Union[str, datetime], object, None] | Omit = omit,
@@ -792,6 +866,7 @@ def list(
collection: Optional[str] | Omit = omit,
cursor: Optional[str] | Omit = omit,
filter: Optional[str] | Omit = omit,
+ include_chunks: int | Omit = omit,
size: int | Omit = omit,
source: Optional[
Literal[
@@ -800,28 +875,44 @@ def list(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
| Omit = omit,
- status: Optional[Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]]
+ status: Optional[
+ Literal[
+ "pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"
+ ]
+ ]
| Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -842,6 +933,10 @@ def list(
Filter documents by metadata using MongoDB-style operators. Example:
{"department": "engineering", "priority": {"$gt": 3}}
+ include_chunks: When > 0, include up to this many extracted memories (chunks with summaries) per
+ document in each item's `chunks` field, in document order. 0 (default) omits
+ them.
+
source: Filter documents by source.
status: Filter documents by status.
@@ -867,6 +962,7 @@ def list(
"collection": collection,
"cursor": cursor,
"filter": filter,
+ "include_chunks": include_chunks,
"size": size,
"source": source,
"status": status,
@@ -887,24 +983,36 @@ async def delete(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
],
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -914,23 +1022,7 @@ async def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MemoryDeleteResponse:
"""
- Delete a memory and its associated chunks from the index.
-
- This removes the memory completely from the vector index and database. The
- operation deletes:
-
- 1. All chunks associated with the resource (including embeddings)
- 2. The documents row AND any legacy resources rows sharing the identity —
- leaving either one behind would resurrect the memory through the double-read
- path (ENG-2477).
-
- Args: source: The document provider (e.g., gmail, notion, vault) resource_id:
- The unique identifier of the resource to delete api_token: Authentication token
-
- Returns: MemoryDeletionResponse with deletion details
-
- Raises: DocumentNotFound: If the resource doesn't exist or user doesn't have
- access
+ Delete a memory accessible to the authenticated credential.
Args:
extra_headers: Send extra headers
@@ -1035,9 +1127,9 @@ async def add_bulk(
"""
Adds multiple documents to the index in a single request.
- All items are validated before any database operations occur. If any item fails
- validation, the entire batch is rejected with a 422 error detailing which items
- failed and why.
+ All items are validated before processing begins. If any item fails validation,
+ the entire batch is rejected with a 422 error detailing which items failed and
+ why.
Maximum 100 items per request. Each item follows the same schema as the
single-item /memories/add endpoint.
@@ -1072,25 +1164,38 @@ async def get(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
],
+ include_chunks: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -1099,10 +1204,13 @@ async def get(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MemoryGetResponse:
"""
- Retrieves a document by provider and resource_id, as a document-shaped response
- carrying the full hyperdoc tree (ENG-2479 Phase 4).
+ Retrieve a document by provider and resource ID, including its full hyperdoc
+ tree.
Args:
+ include_chunks: When true, include the document's extracted memories (chunks with summaries) in
+ the `chunks` field, in document order.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -1118,7 +1226,13 @@ async def get(
return await self._get(
path_template("/memories/get/{source}/{resource_id}", source=source, resource_id=resource_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"include_chunks": include_chunks}, memory_get_params.MemoryGetParams
+ ),
),
cast_to=MemoryGetResponse,
)
@@ -1139,24 +1253,36 @@ async def search(
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
| Omit = omit,
@@ -1175,13 +1301,10 @@ async def search(
answer: If true, the query will be answered along with matching source documents.
- effort: How much compute to spend on retrieval. Mirrors the dial popularized by
- frontier-model APIs (OpenAI reasoning_effort, etc.). 'minimal' = verbatim
- single-shot retrieval (fastest). 'low' = LLM rewrites the query for better
- retrieval and extracts date filters. 'medium' = rewrite + agentic refinement
- loop (the answer LLM may request additional retrieval rounds, up to 3). 'high' =
- rewrite + extended refinement (up to 6 rounds). Higher = better recall, more
- latency, more cost.
+ effort: Controls retrieval thoroughness. 'minimal' performs direct retrieval. 'low'
+ improves the query and extracts date filters. 'medium' adds up to 3 refinement
+ rounds; 'high' allows up to 6. Higher levels can improve recall but add latency
+ and cost.
max_results: Maximum number of results to return.
@@ -1190,10 +1313,12 @@ async def search(
provenance:
If true (effort='very_high' only), attach a provenance record to the response:
the source documents and entities the answer was grounded in, the agent's search
- trajectory, and any sources that failed. Adds one indexed lookup; intended for
- auditability / compliance use cases.
+ trajectory, and any sources that failed. Intended for auditability and
+ compliance use cases.
- sources: Only query documents from these sources.
+ sources: Only query documents from these sources. Names are case-insensitive and accept
+ either separator, so `Google Drive`'s provider may be given as `google_drive`,
+ `google-drive`, or `GOOGLE_DRIVE`.
extra_headers: Send extra headers
diff --git a/src/hyperspell/types/__init__.py b/src/hyperspell/types/__init__.py
index d77f4c69..9f55fd0e 100644
--- a/src/hyperspell/types/__init__.py
+++ b/src/hyperspell/types/__init__.py
@@ -12,6 +12,7 @@
File as File,
Link as Link,
List as List,
+ Page as Page,
Task as Task,
Text as Text,
ToDo as ToDo,
@@ -54,6 +55,7 @@
from .memory_status import MemoryStatus as MemoryStatus
from .auth_me_response import AuthMeResponse as AuthMeResponse
from .memory_add_params import MemoryAddParams as MemoryAddParams
+from .memory_get_params import MemoryGetParams as MemoryGetParams
from .vault_list_params import VaultListParams as VaultListParams
from .folder_list_params import FolderListParams as FolderListParams
from .memory_list_params import MemoryListParams as MemoryListParams
@@ -81,15 +83,20 @@
from .evaluate_score_query_params import EvaluateScoreQueryParams as EvaluateScoreQueryParams
from .action_add_reaction_response import ActionAddReactionResponse as ActionAddReactionResponse
from .action_send_message_response import ActionSendMessageResponse as ActionSendMessageResponse
+from .context_document_list_params import ContextDocumentListParams as ContextDocumentListParams
from .evaluate_list_queries_params import EvaluateListQueriesParams as EvaluateListQueriesParams
from .folder_set_policies_response import FolderSetPoliciesResponse as FolderSetPoliciesResponse
from .integration_connect_response import IntegrationConnectResponse as IntegrationConnectResponse
+from .context_document_get_response import ContextDocumentGetResponse as ContextDocumentGetResponse
from .evaluate_score_query_response import EvaluateScoreQueryResponse as EvaluateScoreQueryResponse
from .folder_delete_policy_response import FolderDeletePolicyResponse as FolderDeletePolicyResponse
from .folder_list_policies_response import FolderListPoliciesResponse as FolderListPoliciesResponse
+from .context_document_list_response import ContextDocumentListResponse as ContextDocumentListResponse
from .evaluate_list_queries_response import EvaluateListQueriesResponse as EvaluateListQueriesResponse
from .evaluate_score_highlight_params import EvaluateScoreHighlightParams as EvaluateScoreHighlightParams
+from .context_document_generate_params import ContextDocumentGenerateParams as ContextDocumentGenerateParams
from .evaluate_score_highlight_response import EvaluateScoreHighlightResponse as EvaluateScoreHighlightResponse
+from .context_document_generate_response import ContextDocumentGenerateResponse as ContextDocumentGenerateResponse
# Rebuild cyclical models only after all modules are imported.
# This ensures that, when building the deferred (due to cyclical references) model schema,
@@ -112,6 +119,7 @@
shared.list.List.update_forward_refs() # type: ignore
shared.list_item.ListItem.update_forward_refs() # type: ignore
shared.message.Message.update_forward_refs() # type: ignore
+ shared.page.Page.update_forward_refs() # type: ignore
shared.paragraph.Paragraph.update_forward_refs() # type: ignore
shared.person.Person.update_forward_refs() # type: ignore
shared.query_result.QueryResult.update_forward_refs() # type: ignore
@@ -142,6 +150,7 @@
shared.list.List.model_rebuild(_parent_namespace_depth=0)
shared.list_item.ListItem.model_rebuild(_parent_namespace_depth=0)
shared.message.Message.model_rebuild(_parent_namespace_depth=0)
+ shared.page.Page.model_rebuild(_parent_namespace_depth=0)
shared.paragraph.Paragraph.model_rebuild(_parent_namespace_depth=0)
shared.person.Person.model_rebuild(_parent_namespace_depth=0)
shared.query_result.QueryResult.model_rebuild(_parent_namespace_depth=0)
diff --git a/src/hyperspell/types/action_add_reaction_params.py b/src/hyperspell/types/action_add_reaction_params.py
index 87e7b13d..0b997cec 100644
--- a/src/hyperspell/types/action_add_reaction_params.py
+++ b/src/hyperspell/types/action_add_reaction_params.py
@@ -15,34 +15,8 @@ class ActionAddReactionParams(TypedDict, total=False):
name: Required[str]
"""Emoji name without colons (e.g., thumbsup)"""
- provider: Required[
- Literal[
- "reddit",
- "notion",
- "slack",
- "google_calendar",
- "google_mail",
- "box",
- "dropbox",
- "github",
- "google_drive",
- "vault",
- "web_crawler",
- "trace",
- "microsoft_teams",
- "gmail_actions",
- "granola",
- "fathom",
- "fireflies",
- "linear",
- "hubspot",
- "salesforce",
- "coda",
- "lightfield",
- "gong",
- ]
- ]
- """Integration provider (e.g., slack)"""
+ provider: Required[Literal["slack"]]
+ """Integration provider."""
timestamp: Required[str]
"""Message timestamp to react to"""
diff --git a/src/hyperspell/types/action_send_message_params.py b/src/hyperspell/types/action_send_message_params.py
index bc76880d..3d866b45 100644
--- a/src/hyperspell/types/action_send_message_params.py
+++ b/src/hyperspell/types/action_send_message_params.py
@@ -9,34 +9,8 @@
class ActionSendMessageParams(TypedDict, total=False):
- provider: Required[
- Literal[
- "reddit",
- "notion",
- "slack",
- "google_calendar",
- "google_mail",
- "box",
- "dropbox",
- "github",
- "google_drive",
- "vault",
- "web_crawler",
- "trace",
- "microsoft_teams",
- "gmail_actions",
- "granola",
- "fathom",
- "fireflies",
- "linear",
- "hubspot",
- "salesforce",
- "coda",
- "lightfield",
- "gong",
- ]
- ]
- """Integration provider (e.g., slack)"""
+ provider: Required[Literal["slack"]]
+ """Integration provider."""
text: Required[str]
"""Message text"""
diff --git a/src/hyperspell/types/auth_delete_user_response.py b/src/hyperspell/types/auth_delete_user_response.py
index 64c8e293..02bfd58d 100644
--- a/src/hyperspell/types/auth_delete_user_response.py
+++ b/src/hyperspell/types/auth_delete_user_response.py
@@ -9,3 +9,5 @@ class AuthDeleteUserResponse(BaseModel):
message: str
success: bool
+
+ workflow_id: str
diff --git a/src/hyperspell/types/auth_me_response.py b/src/hyperspell/types/auth_me_response.py
index 918e0e73..df8efce4 100644
--- a/src/hyperspell/types/auth_me_response.py
+++ b/src/hyperspell/types/auth_me_response.py
@@ -39,24 +39,36 @@ class AuthMeResponse(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
"""All integrations available for the app"""
@@ -68,24 +80,36 @@ class AuthMeResponse(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
"""All integrations installed for the user"""
diff --git a/src/hyperspell/types/auth_user_token_params.py b/src/hyperspell/types/auth_user_token_params.py
index c95c2321..60dd465a 100644
--- a/src/hyperspell/types/auth_user_token_params.py
+++ b/src/hyperspell/types/auth_user_token_params.py
@@ -12,7 +12,10 @@ class AuthUserTokenParams(TypedDict, total=False):
user_id: Required[str]
expires_in: Optional[str]
- """Token lifetime, e.g., '30m', '2h', '1d'. Defaults to 24 hours if not provided."""
+ """Token lifetime, e.g., '30m', '2h', '1d'.
+
+ Defaults to 24 hours if not provided. Maximum 30 days.
+ """
origin: Optional[str]
"""Origin of the request, used for CSRF protection.
diff --git a/src/hyperspell/types/connection_list_response.py b/src/hyperspell/types/connection_list_response.py
index 6c5feef3..7c6ff916 100644
--- a/src/hyperspell/types/connection_list_response.py
+++ b/src/hyperspell/types/connection_list_response.py
@@ -24,31 +24,58 @@ class Connection(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
"""The connection's provider"""
- selected_count: Optional[int] = None
+ backfill_state: Optional[Literal["backfilling", "quiesced", "completed", "unknown"]] = None
+ """
+ State of the historical backfill for providers that deliver history
+ asynchronously: 'backfilling' while history is still streaming in, 'quiesced'
+ once no backfill batch has arrived for a while (drained or stalled), 'completed'
+ if the provider confirmed completion, and 'unknown' when the provider has not
+ reported a backfill state.
+ """
+
+ scope: Optional[Literal["user", "app"]] = None
+ """
+ 'user' for a personal connection; 'app' for an org-wide (app-level) connection
+ installed once by an app admin and shared with every user of the app.
"""
- Count of items in user_options.channels (Teams: workspaces selected; 0 means
- nothing is being indexed for integrations that require selection).
+
+ selected_count: Optional[int] = None
+ """Number of items selected for this connection.
+
+ For integrations that require selection, 0 means nothing is being indexed.
"""
diff --git a/src/hyperspell/types/context_document_generate_params.py b/src/hyperspell/types/context_document_generate_params.py
new file mode 100644
index 00000000..196ed0a4
--- /dev/null
+++ b/src/hyperspell/types/context_document_generate_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["ContextDocumentGenerateParams"]
+
+
+class ContextDocumentGenerateParams(TypedDict, total=False):
+ model: str
+ """Model used for final synthesis."""
+
+ prompt: Optional[str]
+ """Custom prompt template. Replaces the standard summary prompt."""
+
+ sources: Optional[SequenceNotStr[str]]
+ """Integration sources to include (e.g., ['gmail', 'slack']).
+
+ Defaults to all connected integrations.
+ """
+
+ user_id: Optional[str]
+ """Scope generation to a specific user's data."""
diff --git a/src/hyperspell/types/context_document_generate_response.py b/src/hyperspell/types/context_document_generate_response.py
new file mode 100644
index 00000000..7dd7059b
--- /dev/null
+++ b/src/hyperspell/types/context_document_generate_response.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["ContextDocumentGenerateResponse"]
+
+
+class ContextDocumentGenerateResponse(BaseModel):
+ created_at: datetime
+
+ document_id: str
+
+ status: str
diff --git a/src/hyperspell/types/context_document_get_response.py b/src/hyperspell/types/context_document_get_response.py
new file mode 100644
index 00000000..bc43de69
--- /dev/null
+++ b/src/hyperspell/types/context_document_get_response.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["ContextDocumentGetResponse"]
+
+
+class ContextDocumentGetResponse(BaseModel):
+ completed_at: Optional[datetime] = None
+
+ content: Optional[str] = None
+
+ created_at: datetime
+
+ document_id: str
+
+ error: Optional[str] = None
+
+ metadata: Dict[str, object]
+
+ model: str
+
+ prompt: Optional[str] = None
+
+ sources: List[str]
+
+ status: str
+
+ token_usage: Optional[Dict[str, object]] = None
+
+ user_id: Optional[str] = None
diff --git a/src/hyperspell/types/context_document_list_params.py b/src/hyperspell/types/context_document_list_params.py
new file mode 100644
index 00000000..ebc6f13b
--- /dev/null
+++ b/src/hyperspell/types/context_document_list_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ContextDocumentListParams"]
+
+
+class ContextDocumentListParams(TypedDict, total=False):
+ cursor: Optional[str]
+
+ limit: int
+
+ status: Optional[Literal["processing", "completed", "failed"]]
diff --git a/src/hyperspell/types/context_document_list_response.py b/src/hyperspell/types/context_document_list_response.py
new file mode 100644
index 00000000..97cf9372
--- /dev/null
+++ b/src/hyperspell/types/context_document_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["ContextDocumentListResponse"]
+
+
+class ContextDocumentListResponse(BaseModel):
+ completed_at: Optional[datetime] = None
+
+ created_at: datetime
+
+ document_id: str
+
+ model: str
+
+ sources: List[str]
+
+ status: str
+
+ token_count: Optional[int] = None
+
+ error: Optional[str] = None
diff --git a/src/hyperspell/types/context_documents/__init__.py b/src/hyperspell/types/context_documents/__init__.py
new file mode 100644
index 00000000..869a58d2
--- /dev/null
+++ b/src/hyperspell/types/context_documents/__init__.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .tree_get_response import TreeGetResponse as TreeGetResponse
+from .digest_list_params import DigestListParams as DigestListParams
+from .config_get_response import ConfigGetResponse as ConfigGetResponse
+from .config_update_params import ConfigUpdateParams as ConfigUpdateParams
+from .digest_list_response import DigestListResponse as DigestListResponse
+from .tree_generate_params import TreeGenerateParams as TreeGenerateParams
+from .config_reset_response import ConfigResetResponse as ConfigResetResponse
+from .config_update_response import ConfigUpdateResponse as ConfigUpdateResponse
+from .digest_generate_params import DigestGenerateParams as DigestGenerateParams
+from .tree_generate_response import TreeGenerateResponse as TreeGenerateResponse
+from .tree_get_latest_params import TreeGetLatestParams as TreeGetLatestParams
+from .tree_progress_response import TreeProgressResponse as TreeProgressResponse
+from .digest_generate_response import DigestGenerateResponse as DigestGenerateResponse
+from .tree_get_latest_response import TreeGetLatestResponse as TreeGetLatestResponse
diff --git a/src/hyperspell/types/context_documents/config_get_response.py b/src/hyperspell/types/context_documents/config_get_response.py
new file mode 100644
index 00000000..ca56060d
--- /dev/null
+++ b/src/hyperspell/types/context_documents/config_get_response.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigGetResponse"]
+
+
+class ConfigGetResponse(BaseModel):
+ """Brain-generation settings that customers can view and edit."""
+
+ prompts: Dict[str, object]
+
+ source_weights: Dict[str, str]
+
+ structure: Dict[str, object]
+
+ detected_domain: Optional[str] = None
+
+ domain: Optional[str] = None
diff --git a/src/hyperspell/types/context_documents/config_reset_response.py b/src/hyperspell/types/context_documents/config_reset_response.py
new file mode 100644
index 00000000..9dcb234c
--- /dev/null
+++ b/src/hyperspell/types/context_documents/config_reset_response.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigResetResponse"]
+
+
+class ConfigResetResponse(BaseModel):
+ """Brain-generation settings that customers can view and edit."""
+
+ prompts: Dict[str, object]
+
+ source_weights: Dict[str, str]
+
+ structure: Dict[str, object]
+
+ detected_domain: Optional[str] = None
+
+ domain: Optional[str] = None
diff --git a/src/hyperspell/types/context_documents/config_update_params.py b/src/hyperspell/types/context_documents/config_update_params.py
new file mode 100644
index 00000000..0acda28e
--- /dev/null
+++ b/src/hyperspell/types/context_documents/config_update_params.py
@@ -0,0 +1,63 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["ConfigUpdateParams", "Structure", "StructureCompany", "StructureWorkstream"]
+
+
+class ConfigUpdateParams(TypedDict, total=False):
+ company_prompts: Optional[Dict[str, str]]
+
+ detection_prompt: Optional[str]
+
+ domain: Optional[str]
+
+ personal_prompt: Optional[str]
+
+ source_weights: Optional[Dict[str, str]]
+
+ structure: Optional[Structure]
+ """Per-tier document definitions for custom generation."""
+
+ workstream_prompts: Optional[Dict[str, str]]
+
+
+class StructureCompany(TypedDict, total=False):
+ """
+ One document in a context-tree tier: what to generate and how to retrieve for it.
+ """
+
+ filename: Required[str]
+
+ key: Required[str]
+
+ prompt: Required[str]
+
+ search_queries: Required[SequenceNotStr[str]]
+
+
+class StructureWorkstream(TypedDict, total=False):
+ """
+ One document in a context-tree tier: what to generate and how to retrieve for it.
+ """
+
+ filename: Required[str]
+
+ key: Required[str]
+
+ prompt: Required[str]
+
+ search_queries: Required[SequenceNotStr[str]]
+
+
+class Structure(TypedDict, total=False):
+ """Per-tier document definitions for custom generation."""
+
+ company: Optional[Iterable[StructureCompany]]
+
+ workstream: Optional[Iterable[StructureWorkstream]]
diff --git a/src/hyperspell/types/context_documents/config_update_response.py b/src/hyperspell/types/context_documents/config_update_response.py
new file mode 100644
index 00000000..3d4a6c32
--- /dev/null
+++ b/src/hyperspell/types/context_documents/config_update_response.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigUpdateResponse"]
+
+
+class ConfigUpdateResponse(BaseModel):
+ """Brain-generation settings that customers can view and edit."""
+
+ prompts: Dict[str, object]
+
+ source_weights: Dict[str, str]
+
+ structure: Dict[str, object]
+
+ detected_domain: Optional[str] = None
+
+ domain: Optional[str] = None
diff --git a/src/hyperspell/types/context_documents/digest_generate_params.py b/src/hyperspell/types/context_documents/digest_generate_params.py
new file mode 100644
index 00000000..f44ef839
--- /dev/null
+++ b/src/hyperspell/types/context_documents/digest_generate_params.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import datetime
+from typing_extensions import Annotated, TypedDict
+
+from ..._types import SequenceNotStr
+from ..._utils import PropertyInfo
+
+__all__ = ["DigestGenerateParams"]
+
+
+class DigestGenerateParams(TypedDict, total=False):
+ period: str
+ """Digest cadence: 'daily' or 'weekly'.
+
+ Sets the default window when none is given.
+ """
+
+ sources: Optional[SequenceNotStr[str]]
+ """Integration sources to include (e.g., ['slack', 'github']). Defaults to all."""
+
+ window_end: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Exclusive upper bound of the digest window. Defaults to now."""
+
+ window_start: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Inclusive lower bound of the digest window.
+
+ Defaults to midnight UTC today (paired with window_end=now) when omitted. Both
+ bounds must be supplied together.
+ """
diff --git a/src/hyperspell/types/context_documents/digest_generate_response.py b/src/hyperspell/types/context_documents/digest_generate_response.py
new file mode 100644
index 00000000..d3112463
--- /dev/null
+++ b/src/hyperspell/types/context_documents/digest_generate_response.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["DigestGenerateResponse"]
+
+
+class DigestGenerateResponse(BaseModel):
+ created_at: datetime
+
+ status: str
+
+ tree_id: str
diff --git a/src/hyperspell/types/context_documents/digest_list_params.py b/src/hyperspell/types/context_documents/digest_list_params.py
new file mode 100644
index 00000000..79f076e3
--- /dev/null
+++ b/src/hyperspell/types/context_documents/digest_list_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["DigestListParams"]
+
+
+class DigestListParams(TypedDict, total=False):
+ limit: int
+
+ period: Optional[str]
diff --git a/src/hyperspell/types/context_documents/digest_list_response.py b/src/hyperspell/types/context_documents/digest_list_response.py
new file mode 100644
index 00000000..a55ab243
--- /dev/null
+++ b/src/hyperspell/types/context_documents/digest_list_response.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["DigestListResponse", "Digest"]
+
+
+class Digest(BaseModel):
+ """A digest summary. Fetch the full content through the tree-by-ID endpoint."""
+
+ completed_at: Optional[datetime] = None
+
+ created_at: datetime
+
+ period: Optional[str] = None
+
+ status: str
+
+ tree_id: str
+
+ window_end: Optional[datetime] = None
+
+ window_start: Optional[datetime] = None
+
+
+class DigestListResponse(BaseModel):
+ digests: List[Digest]
diff --git a/src/hyperspell/types/context_documents/tree_generate_params.py b/src/hyperspell/types/context_documents/tree_generate_params.py
new file mode 100644
index 00000000..18b51da6
--- /dev/null
+++ b/src/hyperspell/types/context_documents/tree_generate_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["TreeGenerateParams"]
+
+
+class TreeGenerateParams(TypedDict, total=False):
+ sources: Optional[SequenceNotStr[str]]
+ """Integration sources to include (e.g., ['gmail', 'slack']). Defaults to all."""
+
+ user_id: Optional[str]
+ """User ID for personal tier scoping.
+
+ When set, personal/context.md is generated from this user's data only. Company
+ and workstream tiers still use all data.
+ """
+
+ workstream_name: Optional[str]
+ """Generate docs for this workstream only (skip auto-detection)."""
diff --git a/src/hyperspell/types/context_documents/tree_generate_response.py b/src/hyperspell/types/context_documents/tree_generate_response.py
new file mode 100644
index 00000000..8c1e6906
--- /dev/null
+++ b/src/hyperspell/types/context_documents/tree_generate_response.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["TreeGenerateResponse"]
+
+
+class TreeGenerateResponse(BaseModel):
+ created_at: datetime
+
+ status: str
+
+ tree_id: str
diff --git a/src/hyperspell/types/context_documents/tree_get_latest_params.py b/src/hyperspell/types/context_documents/tree_get_latest_params.py
new file mode 100644
index 00000000..81893150
--- /dev/null
+++ b/src/hyperspell/types/context_documents/tree_get_latest_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["TreeGetLatestParams"]
+
+
+class TreeGetLatestParams(TypedDict, total=False):
+ status: Optional[str]
diff --git a/src/hyperspell/types/context_documents/tree_get_latest_response.py b/src/hyperspell/types/context_documents/tree_get_latest_response.py
new file mode 100644
index 00000000..75d993ad
--- /dev/null
+++ b/src/hyperspell/types/context_documents/tree_get_latest_response.py
@@ -0,0 +1,70 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["TreeGetLatestResponse", "File", "Generating"]
+
+
+class File(BaseModel):
+ content: str
+
+ path: str
+
+ team: Optional[str] = None
+
+ tier: str
+
+ updated_at: str
+
+ error: Optional[str] = None
+
+ provenance: Optional[Dict[str, object]] = None
+
+ status: Optional[str] = None
+
+
+class Generating(BaseModel):
+ """Status of a newer generation that is processing or recently failed.
+
+ This can accompany the last ready tree so clients can report progress while
+ continuing to use ready content.
+ """
+
+ created_at: datetime
+
+ status: Literal["processing", "failed"]
+
+ tree_id: str
+
+ error: Optional[str] = None
+
+ progress: Optional[Dict[str, object]] = None
+
+
+class TreeGetLatestResponse(BaseModel):
+ completed_at: Optional[datetime] = None
+
+ created_at: datetime
+
+ error: Optional[str] = None
+
+ files: Optional[List[File]] = None
+
+ meta: Dict[str, object]
+
+ status: str
+
+ tree_id: str
+
+ version: int
+
+ generating: Optional[Generating] = None
+ """Status of a newer generation that is processing or recently failed.
+
+ This can accompany the last ready tree so clients can report progress while
+ continuing to use ready content.
+ """
diff --git a/src/hyperspell/types/context_documents/tree_get_response.py b/src/hyperspell/types/context_documents/tree_get_response.py
new file mode 100644
index 00000000..99db09be
--- /dev/null
+++ b/src/hyperspell/types/context_documents/tree_get_response.py
@@ -0,0 +1,70 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["TreeGetResponse", "File", "Generating"]
+
+
+class File(BaseModel):
+ content: str
+
+ path: str
+
+ team: Optional[str] = None
+
+ tier: str
+
+ updated_at: str
+
+ error: Optional[str] = None
+
+ provenance: Optional[Dict[str, object]] = None
+
+ status: Optional[str] = None
+
+
+class Generating(BaseModel):
+ """Status of a newer generation that is processing or recently failed.
+
+ This can accompany the last ready tree so clients can report progress while
+ continuing to use ready content.
+ """
+
+ created_at: datetime
+
+ status: Literal["processing", "failed"]
+
+ tree_id: str
+
+ error: Optional[str] = None
+
+ progress: Optional[Dict[str, object]] = None
+
+
+class TreeGetResponse(BaseModel):
+ completed_at: Optional[datetime] = None
+
+ created_at: datetime
+
+ error: Optional[str] = None
+
+ files: Optional[List[File]] = None
+
+ meta: Dict[str, object]
+
+ status: str
+
+ tree_id: str
+
+ version: int
+
+ generating: Optional[Generating] = None
+ """Status of a newer generation that is processing or recently failed.
+
+ This can accompany the last ready tree so clients can report progress while
+ continuing to use ready content.
+ """
diff --git a/src/hyperspell/types/context_documents/tree_progress_response.py b/src/hyperspell/types/context_documents/tree_progress_response.py
new file mode 100644
index 00000000..95add82d
--- /dev/null
+++ b/src/hyperspell/types/context_documents/tree_progress_response.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["TreeProgressResponse"]
+
+
+class TreeProgressResponse(BaseModel):
+ """Response shape for GET /context-documents/tree/{tree_id}/progress."""
+
+ status: str
+
+ tree_id: str
+
+ completed_docs: Optional[int] = None
+
+ failed_docs: Optional[int] = None
+
+ failed_keys: Optional[List[str]] = None
+
+ phase: Optional[str] = None
+ """Generation phase.
+
+ Values: discover, search, select, synthesize, finalize, personal, done. Null
+ when detailed progress is unavailable.
+ """
+
+ total_docs: Optional[int] = None
diff --git a/src/hyperspell/types/integration_list_response.py b/src/hyperspell/types/integration_list_response.py
index 02f10164..d94fb976 100644
--- a/src/hyperspell/types/integration_list_response.py
+++ b/src/hyperspell/types/integration_list_response.py
@@ -30,33 +30,85 @@ class Integration(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
"""The integration's provider"""
actions_only: Optional[bool] = None
"""Whether this integration only supports write actions (no sync)"""
+ channel_selection_required: Optional[bool] = None
+ """Whether indexing waits until the user selects at least one channel."""
+
+ connected_via_service_account: Optional[bool] = None
+ """
+ Whether this app already has a scope='app' (service-account/bot) connection for
+ this integration. Informational only: a shared connection no longer blocks
+ personal OAuth — shared and personal connections can coexist.
+ """
+
+ folder_selection_required: Optional[bool] = None
+ """
+ Whether a new personal connection waits for the user to select at least one
+ folder before indexing begins. Shared service-account connections are exempt.
+ """
+
+ private_channels_included: Optional[bool] = None
+ """
+ Whether private channels are included by default when no explicit channel
+ selection is provided.
+ """
+
+ public_channels_included: Optional[bool] = None
+ """
+ Whether public channels are included by default when no explicit channel
+ selection is provided.
+ """
+
requires_channel_selection: Optional[bool] = None
"""Whether the user must select channels before indexing starts"""
+ supports_channel_selection: Optional[bool] = None
+ """Whether the integration allows users to choose specific channels to index.
+
+ Unless selection is required, an empty selection indexes all channels.
+ """
+
+ supports_folder_selection: Optional[bool] = None
+ """
+ Whether the integration supports listing folders and configuring per-folder sync
+ policies.
+ """
+
class IntegrationListResponse(BaseModel):
integrations: List[Integration]
diff --git a/src/hyperspell/types/integrations/__init__.py b/src/hyperspell/types/integrations/__init__.py
index d0911f57..3240c9d6 100644
--- a/src/hyperspell/types/integrations/__init__.py
+++ b/src/hyperspell/types/integrations/__init__.py
@@ -2,7 +2,5 @@
from __future__ import annotations
-from .calendar import Calendar as Calendar
-from .slack_list_params import SlackListParams as SlackListParams
from .web_crawler_index_params import WebCrawlerIndexParams as WebCrawlerIndexParams
from .web_crawler_index_response import WebCrawlerIndexResponse as WebCrawlerIndexResponse
diff --git a/src/hyperspell/types/integrations/calendar.py b/src/hyperspell/types/integrations/calendar.py
deleted file mode 100644
index 9580f518..00000000
--- a/src/hyperspell/types/integrations/calendar.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from typing import List, Optional
-
-from ..._models import BaseModel
-
-__all__ = ["Calendar", "Item"]
-
-
-class Item(BaseModel):
- id: str
- """The ID of the calendar"""
-
- name: str
- """The name of the calendar"""
-
- primary: bool
- """Whether the calendar is the primary calendar of the user"""
-
- timezone: Optional[str] = None
- """Default timezone of the calendar"""
-
-
-class Calendar(BaseModel):
- items: List[Item]
diff --git a/src/hyperspell/types/integrations/slack_list_params.py b/src/hyperspell/types/integrations/slack_list_params.py
deleted file mode 100644
index 17cf66d4..00000000
--- a/src/hyperspell/types/integrations/slack_list_params.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing import Optional
-from typing_extensions import TypedDict
-
-from ..._types import SequenceNotStr
-
-__all__ = ["SlackListParams"]
-
-
-class SlackListParams(TypedDict, total=False):
- channels: SequenceNotStr[str]
- """List of Slack channels to include (by id, name, or #name)."""
-
- exclude_archived: Optional[bool]
- """If set, pass 'exclude_archived' to Slack. If None, omit the param."""
-
- include_dms: bool
- """Include direct messages (im) when listing conversations."""
-
- include_group_dms: bool
- """Include group DMs (mpim) when listing conversations."""
-
- include_private: bool
- """Include private channels when constructing Slack 'types'."""
diff --git a/src/hyperspell/types/integrations/web_crawler_index_response.py b/src/hyperspell/types/integrations/web_crawler_index_response.py
index bb44ee7b..311b944e 100644
--- a/src/hyperspell/types/integrations/web_crawler_index_response.py
+++ b/src/hyperspell/types/integrations/web_crawler_index_response.py
@@ -16,24 +16,38 @@ class WebCrawlerIndexResponse(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
- status: Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]
+ status: Literal[
+ "pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"
+ ]
diff --git a/src/hyperspell/types/memory_add_bulk_response.py b/src/hyperspell/types/memory_add_bulk_response.py
index 0cb65978..bc7cbaa9 100644
--- a/src/hyperspell/types/memory_add_bulk_response.py
+++ b/src/hyperspell/types/memory_add_bulk_response.py
@@ -5,7 +5,22 @@
from .._models import BaseModel
from .memory_status import MemoryStatus
-__all__ = ["MemoryAddBulkResponse"]
+__all__ = ["MemoryAddBulkResponse", "Skipped"]
+
+
+class Skipped(BaseModel):
+ """A bulk item that was neither written nor indexed, with the reason.
+
+ ``owned_by_another_user`` means the resource ID already belongs to another
+ user in the app. The bulk endpoint skips that item without modifying the
+ existing document. Single-item ``/memories/add`` returns 409 instead.
+ """
+
+ reason: str
+ """Why the item was skipped (e.g. 'owned_by_another_user')"""
+
+ resource_id: str
+ """Resource ID of the skipped item"""
class MemoryAddBulkResponse(BaseModel):
@@ -17,4 +32,11 @@ class MemoryAddBulkResponse(BaseModel):
items: List[MemoryStatus]
"""Status of each ingested item"""
+ skipped: Optional[List[Skipped]] = None
+ """
+ Items not ingested because their resource_id is already owned by another user on
+ this app. Empty in the common case; a non-empty list is a partial success, not
+ an error.
+ """
+
success: Optional[bool] = None
diff --git a/src/hyperspell/types/memory_delete_response.py b/src/hyperspell/types/memory_delete_response.py
index 1b1b262b..db09c705 100644
--- a/src/hyperspell/types/memory_delete_response.py
+++ b/src/hyperspell/types/memory_delete_response.py
@@ -20,24 +20,36 @@ class MemoryDeleteResponse(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
success: bool
diff --git a/src/hyperspell/types/memory_get_params.py b/src/hyperspell/types/memory_get_params.py
new file mode 100644
index 00000000..5cff03c3
--- /dev/null
+++ b/src/hyperspell/types/memory_get_params.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["MemoryGetParams"]
+
+
+class MemoryGetParams(TypedDict, total=False):
+ source: Required[
+ Literal[
+ "reddit",
+ "notion",
+ "slack",
+ "google_calendar",
+ "google_mail",
+ "imap",
+ "google_meet",
+ "box",
+ "dropbox",
+ "github",
+ "gitlab",
+ "google_drive",
+ "vault",
+ "web_crawler",
+ "trace",
+ "microsoft_outlook",
+ "microsoft_teams",
+ "granola",
+ "fathom",
+ "fireflies",
+ "figma",
+ "linear",
+ "hubspot",
+ "salesforce",
+ "coda",
+ "confluence",
+ "jira",
+ "metabase",
+ "gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
+ ]
+ ]
+
+ include_chunks: bool
+ """
+ When true, include the document's extracted memories (chunks with summaries) in
+ the `chunks` field, in document order.
+ """
diff --git a/src/hyperspell/types/memory_get_response.py b/src/hyperspell/types/memory_get_response.py
index 7f701601..cb3347d8 100644
--- a/src/hyperspell/types/memory_get_response.py
+++ b/src/hyperspell/types/memory_get_response.py
@@ -2,15 +2,124 @@
from __future__ import annotations
-from typing import Dict, Union, Optional
+from typing import Dict, List as TypingList, Union, Optional
from datetime import datetime
from typing_extensions import Literal, Annotated, TypeAlias
from .._utils import PropertyInfo
from .._models import BaseModel
+from .shared.blob import Blob
+from .shared.code import Code
+from .shared.link import Link
+from .shared.text import Text
+from .shared.image import Image
from .shared.trace import Trace
+from .shared.comment import Comment
+from .shared.divider import Divider
+from .shared.metadata import Metadata
+from .shared.tool_call import ToolCall
+from .shared.line_break import LineBreak
+from .shared.tool_result import ToolResult
+from .shared.trace_message import TraceMessage
+
+__all__ = ["MemoryGetResponse", "Document", "DocumentInvoice", "DocumentInvoiceChild", "Chunk"]
+
+DocumentInvoiceChild: TypeAlias = Annotated[
+ Union[
+ Blob,
+ "Callout",
+ "chunk.Chunk",
+ Code,
+ Comment,
+ Divider,
+ "Equation",
+ "Footnote",
+ "Heading",
+ Image,
+ Link,
+ LineBreak,
+ "SharedList",
+ "ListItem",
+ "Page",
+ "Paragraph",
+ "Quote",
+ "Table",
+ "TableCell",
+ "TableRow",
+ Text,
+ "ToDo",
+ ToolCall,
+ ToolResult,
+ TraceMessage,
+ "Utterance",
+ ],
+ PropertyInfo(discriminator="type"),
+]
+
+
+class DocumentInvoice(BaseModel):
+ """A customer invoice, vendor bill, or credit memo.
+
+ Line items are included in ``children``.
+ """
+
+ id: Optional[str] = None
+
+ attachment_names: Optional[TypingList[str]] = None
+
+ balance_amount: Optional[float] = None
+
+ cancelled_at: Optional[datetime] = None
+
+ children: Optional[TypingList[DocumentInvoiceChild]] = None
+
+ contact_id: Optional[str] = None
+
+ contact_name: Optional[str] = None
+
+ currency: Optional[str] = None
+
+ due_at: Optional[datetime] = None
+
+ invoice_type: Optional[str] = None
+
+ metadata: Optional[Metadata] = None
+ """Optional annotations carried by a hyperdoc node.
+
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
+ """
+
+ notes: Optional[str] = None
+
+ number: Optional[str] = None
+
+ organization_id: Optional[str] = None
+
+ paid_amount: Optional[float] = None
+
+ paid_at: Optional[datetime] = None
+
+ posted_at: Optional[datetime] = None
+
+ reference: Optional[str] = None
+
+ refund_amount: Optional[float] = None
+
+ refund_reason: Optional[str] = None
+
+ refunded_at: Optional[datetime] = None
+
+ status: Optional[str] = None
+
+ tax_amount: Optional[float] = None
+
+ text: Optional[str] = None
+
+ total_amount: Optional[float] = None
+
+ type: Optional[Literal["invoice"]] = None
-__all__ = ["MemoryGetResponse", "Document"]
Document: TypeAlias = Annotated[
Union[
@@ -26,19 +135,33 @@
"Transcript",
"Company",
"Deal",
+ DocumentInvoice,
],
PropertyInfo(discriminator="type"),
]
+class Chunk(BaseModel):
+ """A searchable chunk extracted from a document during ingestion.
+
+ `summary` is null when no summary was generated for the chunk.
+ """
+
+ chunk_id: str
+ """Stable identifier of the chunk."""
+
+ summary: Optional[str] = None
+ """LLM-generated summary of the chunk, if one was produced."""
+
+
class MemoryGetResponse(BaseModel):
- """A document-shaped API response carrying the hyperdoc tree (ENG-2479/D12)."""
+ """A document-shaped API response containing the hyperdoc tree."""
document: Document
"""The full hyperdoc tree.
- Switch on `type` for the document frame and recurse `children` for the body —
- see the `` renderer.
+ Switch on `type` for the document frame and recurse through `children` for the
+ body.
"""
resource_id: str
@@ -49,29 +172,47 @@ class MemoryGetResponse(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
type: str
"""Hyperdoc document type discriminator (document, message, file, event, ...)."""
+ chunks: Optional[TypingList[Chunk]] = None
+ """Extracted memories (chunks with summaries) for this document, in document order.
+
+ Present only when explicitly requested via `include_chunks`; omitted otherwise.
+ """
+
collection: Optional[str] = None
"""The document's collection, if any."""
@@ -82,26 +223,42 @@ class MemoryGetResponse(BaseModel):
"""When Hyperspell first indexed the document."""
last_modified_at: Optional[datetime] = None
- """When the source document was last modified."""
+ """When the source document was last modified, if supplied by the source."""
metadata: Optional[Dict[str, object]] = None
"""Filterable custom metadata attached to the document."""
- status: Optional[Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]] = None
+ status: Optional[
+ Literal["pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"]
+ ] = None
"""Indexing status of the document."""
title: Optional[str] = None
"""Human-readable document title."""
-from .shared import document
+from .shared import chunk, document
from .shared.deal import Deal
from .shared.file import File
+from .shared.list import List as SharedList
+from .shared.page import Page
from .shared.task import Task
from .shared.event import Event
+from .shared.quote import Quote
+from .shared.table import Table
+from .shared.to_do import ToDo
from .shared.person import Person
+from .shared.callout import Callout
from .shared.company import Company
+from .shared.heading import Heading
from .shared.message import Message
from .shared.website import Website
+from .shared.equation import Equation
+from .shared.footnote import Footnote
+from .shared.list_item import ListItem
+from .shared.paragraph import Paragraph
+from .shared.table_row import TableRow
+from .shared.utterance import Utterance
+from .shared.table_cell import TableCell
from .shared.transcript import Transcript
from .shared.conversation import Conversation
diff --git a/src/hyperspell/types/memory_list_params.py b/src/hyperspell/types/memory_list_params.py
index 300c50a7..ee21609b 100644
--- a/src/hyperspell/types/memory_list_params.py
+++ b/src/hyperspell/types/memory_list_params.py
@@ -20,6 +20,13 @@ class MemoryListParams(TypedDict, total=False):
Example: {"department": "engineering", "priority": {"$gt": 3}}
"""
+ include_chunks: int
+ """
+ When > 0, include up to this many extracted memories (chunks with summaries) per
+ document in each item's `chunks` field, in document order. 0 (default) omits
+ them.
+ """
+
size: int
source: Optional[
@@ -29,27 +36,41 @@ class MemoryListParams(TypedDict, total=False):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
"""Filter documents by source."""
- status: Optional[Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]]
+ status: Optional[
+ Literal["pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"]
+ ]
"""Filter documents by status."""
diff --git a/src/hyperspell/types/memory_list_response.py b/src/hyperspell/types/memory_list_response.py
index aa6664c1..e62b6a04 100644
--- a/src/hyperspell/types/memory_list_response.py
+++ b/src/hyperspell/types/memory_list_response.py
@@ -2,15 +2,124 @@
from __future__ import annotations
-from typing import Dict, Union, Optional
+from typing import Dict, List as TypingList, Union, Optional
from datetime import datetime
from typing_extensions import Literal, Annotated, TypeAlias
from .._utils import PropertyInfo
from .._models import BaseModel
+from .shared.blob import Blob
+from .shared.code import Code
+from .shared.link import Link
+from .shared.text import Text
+from .shared.image import Image
from .shared.trace import Trace
+from .shared.comment import Comment
+from .shared.divider import Divider
+from .shared.metadata import Metadata
+from .shared.tool_call import ToolCall
+from .shared.line_break import LineBreak
+from .shared.tool_result import ToolResult
+from .shared.trace_message import TraceMessage
+
+__all__ = ["MemoryListResponse", "Document", "DocumentInvoice", "DocumentInvoiceChild", "Chunk"]
+
+DocumentInvoiceChild: TypeAlias = Annotated[
+ Union[
+ Blob,
+ "Callout",
+ "chunk.Chunk",
+ Code,
+ Comment,
+ Divider,
+ "Equation",
+ "Footnote",
+ "Heading",
+ Image,
+ Link,
+ LineBreak,
+ "SharedList",
+ "ListItem",
+ "Page",
+ "Paragraph",
+ "Quote",
+ "Table",
+ "TableCell",
+ "TableRow",
+ Text,
+ "ToDo",
+ ToolCall,
+ ToolResult,
+ TraceMessage,
+ "Utterance",
+ ],
+ PropertyInfo(discriminator="type"),
+]
+
+
+class DocumentInvoice(BaseModel):
+ """A customer invoice, vendor bill, or credit memo.
+
+ Line items are included in ``children``.
+ """
+
+ id: Optional[str] = None
+
+ attachment_names: Optional[TypingList[str]] = None
+
+ balance_amount: Optional[float] = None
+
+ cancelled_at: Optional[datetime] = None
+
+ children: Optional[TypingList[DocumentInvoiceChild]] = None
+
+ contact_id: Optional[str] = None
+
+ contact_name: Optional[str] = None
+
+ currency: Optional[str] = None
+
+ due_at: Optional[datetime] = None
+
+ invoice_type: Optional[str] = None
+
+ metadata: Optional[Metadata] = None
+ """Optional annotations carried by a hyperdoc node.
+
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
+ """
+
+ notes: Optional[str] = None
+
+ number: Optional[str] = None
+
+ organization_id: Optional[str] = None
+
+ paid_amount: Optional[float] = None
+
+ paid_at: Optional[datetime] = None
+
+ posted_at: Optional[datetime] = None
+
+ reference: Optional[str] = None
+
+ refund_amount: Optional[float] = None
+
+ refund_reason: Optional[str] = None
+
+ refunded_at: Optional[datetime] = None
+
+ status: Optional[str] = None
+
+ tax_amount: Optional[float] = None
+
+ text: Optional[str] = None
+
+ total_amount: Optional[float] = None
+
+ type: Optional[Literal["invoice"]] = None
-__all__ = ["MemoryListResponse", "Document"]
Document: TypeAlias = Annotated[
Union[
@@ -26,19 +135,33 @@
"Transcript",
"Company",
"Deal",
+ DocumentInvoice,
],
PropertyInfo(discriminator="type"),
]
+class Chunk(BaseModel):
+ """A searchable chunk extracted from a document during ingestion.
+
+ `summary` is null when no summary was generated for the chunk.
+ """
+
+ chunk_id: str
+ """Stable identifier of the chunk."""
+
+ summary: Optional[str] = None
+ """LLM-generated summary of the chunk, if one was produced."""
+
+
class MemoryListResponse(BaseModel):
- """A document-shaped API response carrying the hyperdoc tree (ENG-2479/D12)."""
+ """A document-shaped API response containing the hyperdoc tree."""
document: Document
"""The full hyperdoc tree.
- Switch on `type` for the document frame and recurse `children` for the body —
- see the `` renderer.
+ Switch on `type` for the document frame and recurse through `children` for the
+ body.
"""
resource_id: str
@@ -49,29 +172,47 @@ class MemoryListResponse(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
type: str
"""Hyperdoc document type discriminator (document, message, file, event, ...)."""
+ chunks: Optional[TypingList[Chunk]] = None
+ """Extracted memories (chunks with summaries) for this document, in document order.
+
+ Present only when explicitly requested via `include_chunks`; omitted otherwise.
+ """
+
collection: Optional[str] = None
"""The document's collection, if any."""
@@ -82,26 +223,42 @@ class MemoryListResponse(BaseModel):
"""When Hyperspell first indexed the document."""
last_modified_at: Optional[datetime] = None
- """When the source document was last modified."""
+ """When the source document was last modified, if supplied by the source."""
metadata: Optional[Dict[str, object]] = None
"""Filterable custom metadata attached to the document."""
- status: Optional[Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]] = None
+ status: Optional[
+ Literal["pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"]
+ ] = None
"""Indexing status of the document."""
title: Optional[str] = None
"""Human-readable document title."""
-from .shared import document
+from .shared import chunk, document
from .shared.deal import Deal
from .shared.file import File
+from .shared.list import List as SharedList
+from .shared.page import Page
from .shared.task import Task
from .shared.event import Event
+from .shared.quote import Quote
+from .shared.table import Table
+from .shared.to_do import ToDo
from .shared.person import Person
+from .shared.callout import Callout
from .shared.company import Company
+from .shared.heading import Heading
from .shared.message import Message
from .shared.website import Website
+from .shared.equation import Equation
+from .shared.footnote import Footnote
+from .shared.list_item import ListItem
+from .shared.paragraph import Paragraph
+from .shared.table_row import TableRow
+from .shared.utterance import Utterance
+from .shared.table_cell import TableCell
from .shared.transcript import Transcript
from .shared.conversation import Conversation
diff --git a/src/hyperspell/types/memory_search_params.py b/src/hyperspell/types/memory_search_params.py
index ef256252..09cb39fa 100644
--- a/src/hyperspell/types/memory_search_params.py
+++ b/src/hyperspell/types/memory_search_params.py
@@ -12,8 +12,6 @@
__all__ = [
"MemorySearchParams",
"Options",
- "OptionsBox",
- "OptionsGoogleCalendar",
"OptionsGoogleDrive",
"OptionsGoogleMail",
"OptionsNotion",
@@ -31,14 +29,11 @@ class MemorySearchParams(TypedDict, total=False):
"""If true, the query will be answered along with matching source documents."""
effort: Literal["minimal", "low", "medium", "high", "very_high"]
- """How much compute to spend on retrieval.
-
- Mirrors the dial popularized by frontier-model APIs (OpenAI reasoning_effort,
- etc.). 'minimal' = verbatim single-shot retrieval (fastest). 'low' = LLM
- rewrites the query for better retrieval and extracts date filters. 'medium' =
- rewrite + agentic refinement loop (the answer LLM may request additional
- retrieval rounds, up to 3). 'high' = rewrite + extended refinement (up to 6
- rounds). Higher = better recall, more latency, more cost.
+ """Controls retrieval thoroughness.
+
+ 'minimal' performs direct retrieval. 'low' improves the query and extracts date
+ filters. 'medium' adds up to 3 refinement rounds; 'high' allows up to 6. Higher
+ levels can improve recall but add latency and cost.
"""
max_results: int
@@ -51,8 +46,8 @@ class MemorySearchParams(TypedDict, total=False):
"""
If true (effort='very_high' only), attach a provenance record to the response:
the source documents and entities the answer was grounded in, the agent's search
- trajectory, and any sources that failed. Adds one indexed lookup; intended for
- auditability / compliance use cases.
+ trajectory, and any sources that failed. Intended for auditability and
+ compliance use cases.
"""
sources: List[
@@ -62,57 +57,42 @@ class MemorySearchParams(TypedDict, total=False):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
- """Only query documents from these sources."""
-
-
-class OptionsBox(TypedDict, total=False):
- """Search options for Box"""
-
- weight: float
- """Weight of results from this source.
+ """Only query documents from these sources.
- A weight greater than 1.0 means more results from this source will be returned,
- a weight less than 1.0 means fewer results will be returned. This will only
- affect results if multiple sources are queried at the same time.
- """
-
-
-class OptionsGoogleCalendar(TypedDict, total=False):
- """Search options for Google Calendar"""
-
- calendar_id: Optional[str]
- """The ID of the calendar to search.
-
- If not provided, it will use the ID of the default calendar. You can get the
- list of calendars with the `/integrations/google_calendar/list` endpoint.
- """
-
- weight: float
- """Weight of results from this source.
-
- A weight greater than 1.0 means more results from this source will be returned,
- a weight less than 1.0 means fewer results will be returned. This will only
- affect results if multiple sources are queried at the same time.
+ Names are case-insensitive and accept either separator, so `Google Drive`'s
+ provider may be given as `google_drive`, `google-drive`, or `GOOGLE_DRIVE`.
"""
@@ -233,25 +213,30 @@ class Options(TypedDict, total=False):
"""Only query documents created on or after this date."""
answer_model: Literal[
- "llama-3.1", "gemma2", "qwen-qwq", "mistral-saba", "llama-4-scout", "deepseek-r1", "gpt-oss-20b", "gpt-oss-120b"
+ "llama-3.1",
+ "gemma2",
+ "qwen-qwq",
+ "mistral-saba",
+ "llama-4-scout",
+ "deepseek-r1",
+ "gpt-oss-20b",
+ "gpt-oss-120b",
+ "claude-sonnet-4-6",
+ "claude-sonnet-5",
+ "claude-opus-4-7",
+ "claude-opus-4-8",
]
"""Model to use for answer generation when answer=True"""
before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
"""Only query documents created before this date."""
- box: OptionsBox
- """Search options for Box"""
-
filter: Optional[Dict[str, object]]
"""Metadata filters using MongoDB-style operators.
Example: {'status': 'published', 'priority': {'$gt': 3}}
"""
- google_calendar: OptionsGoogleCalendar
- """Search options for Google Calendar"""
-
google_drive: OptionsGoogleDrive
"""Search options for Google Drive"""
@@ -290,6 +275,12 @@ class Options(TypedDict, total=False):
slack: OptionsSlack
"""Search options for Slack"""
+ timezone: str
+ """IANA timezone used to interpret date-only bounds and relative calendar phrases.
+
+ Defaults to UTC.
+ """
+
vault: OptionsVault
"""Search options for vault"""
diff --git a/src/hyperspell/types/memory_status.py b/src/hyperspell/types/memory_status.py
index 47474f7c..14ae0f50 100644
--- a/src/hyperspell/types/memory_status.py
+++ b/src/hyperspell/types/memory_status.py
@@ -16,24 +16,38 @@ class MemoryStatus(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
- status: Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]
+ status: Literal[
+ "pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"
+ ]
diff --git a/src/hyperspell/types/memory_status_response.py b/src/hyperspell/types/memory_status_response.py
index 7e9bd79b..34ed578a 100644
--- a/src/hyperspell/types/memory_status_response.py
+++ b/src/hyperspell/types/memory_status_response.py
@@ -1,13 +1,108 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Dict
+from typing import Dict, List, Optional
+from datetime import datetime
+from typing_extensions import Literal
from .._models import BaseModel
-__all__ = ["MemoryStatusResponse"]
+__all__ = [
+ "MemoryStatusResponse",
+ "Integration",
+ "IntegrationConnection",
+ "IntegrationConnectionError",
+ "IntegrationError",
+]
+
+
+class IntegrationConnectionError(BaseModel):
+ """The current error for a connection.
+
+ ``detail`` contains a sanitized summary suitable for display.
+ """
+
+ at: datetime
+
+ detail: Optional[str] = None
+
+ kind: Literal["auth", "rate_limited", "provider", "internal"]
+ """Classification of the most recent synchronization or indexing failure."""
+
+ origin: Optional[str] = None
+
+ retry_at: Optional[datetime] = None
+
+
+class IntegrationConnection(BaseModel):
+ """The current health of one connection."""
+
+ id: str
+
+ error: Optional[IntegrationConnectionError] = None
+ """The current error for a connection.
+
+ `detail` contains a sanitized summary suitable for display.
+ """
+
+ label: Optional[str] = None
+
+ last_activity_at: Optional[datetime] = None
+
+ last_synced_at: Optional[datetime] = None
+
+ status: Literal[
+ "broken", "stalled", "error", "rate_limited", "syncing", "connected", "live", "never_synced", "not_connected"
+ ]
+ """Current health status of a connection or integration."""
+
+
+class IntegrationError(BaseModel):
+ """The current error for a connection.
+
+ ``detail`` contains a sanitized summary suitable for display.
+ """
+
+ at: datetime
+
+ detail: Optional[str] = None
+
+ kind: Literal["auth", "rate_limited", "provider", "internal"]
+ """Classification of the most recent synchronization or indexing failure."""
+
+ origin: Optional[str] = None
+
+ retry_at: Optional[datetime] = None
+
+
+class Integration(BaseModel):
+ """Health summary for a configured integration.
+
+ ``provider`` uses lowercase snake_case naming (e.g. ``google_drive``).
+ """
+
+ connections: List[IntegrationConnection]
+
+ error: Optional[IntegrationError] = None
+ """The current error for a connection.
+
+ `detail` contains a sanitized summary suitable for display.
+ """
+
+ integration_id: str
+
+ last_synced_at: Optional[datetime] = None
+
+ provider: str
+
+ status: Literal[
+ "broken", "stalled", "error", "rate_limited", "syncing", "connected", "live", "never_synced", "not_connected"
+ ]
+ """Current health status of a connection or integration."""
class MemoryStatusResponse(BaseModel):
providers: Dict[str, Dict[str, int]]
total: Dict[str, int]
+
+ integrations: Optional[List[Integration]] = None
diff --git a/src/hyperspell/types/memory_update_params.py b/src/hyperspell/types/memory_update_params.py
index 0e543114..2e1ed65d 100644
--- a/src/hyperspell/types/memory_update_params.py
+++ b/src/hyperspell/types/memory_update_params.py
@@ -19,24 +19,36 @@ class MemoryUpdateParams(TypedDict, total=False):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
]
diff --git a/src/hyperspell/types/shared/__init__.py b/src/hyperspell/types/shared/__init__.py
index e07083a8..feb8a27f 100644
--- a/src/hyperspell/types/shared/__init__.py
+++ b/src/hyperspell/types/shared/__init__.py
@@ -6,6 +6,7 @@
from .file import File as File
from .link import Link as Link
from .list import List as List
+from .page import Page as Page
from .task import Task as Task
from .text import Text as Text
from .chunk import Chunk as Chunk
diff --git a/src/hyperspell/types/shared/blob.py b/src/hyperspell/types/shared/blob.py
index b30b7dd3..d73fa898 100644
--- a/src/hyperspell/types/shared/blob.py
+++ b/src/hyperspell/types/shared/blob.py
@@ -23,15 +23,10 @@ class Blob(BaseModel):
id: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["blob"]] = None
diff --git a/src/hyperspell/types/shared/callout.py b/src/hyperspell/types/shared/callout.py
index ccaad590..f7d46672 100644
--- a/src/hyperspell/types/shared/callout.py
+++ b/src/hyperspell/types/shared/callout.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -96,15 +98,10 @@ class Callout(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -115,6 +112,7 @@ class Callout(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/chunk.py b/src/hyperspell/types/shared/chunk.py
index 42d7cc8b..ffcd5d50 100644
--- a/src/hyperspell/types/shared/chunk.py
+++ b/src/hyperspell/types/shared/chunk.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -96,15 +98,10 @@ class Chunk(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -113,6 +110,7 @@ class Chunk(BaseModel):
from .list import List as ListList
+from .page import Page
from .quote import Quote
from .table import Table
from .to_do import ToDo
diff --git a/src/hyperspell/types/shared/code.py b/src/hyperspell/types/shared/code.py
index 15aba371..987c284f 100644
--- a/src/hyperspell/types/shared/code.py
+++ b/src/hyperspell/types/shared/code.py
@@ -17,15 +17,10 @@ class Code(BaseModel):
language: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["code"]] = None
diff --git a/src/hyperspell/types/shared/comment.py b/src/hyperspell/types/shared/comment.py
index 7c93c996..a6e8289a 100644
--- a/src/hyperspell/types/shared/comment.py
+++ b/src/hyperspell/types/shared/comment.py
@@ -18,15 +18,10 @@ class Comment(BaseModel):
created_at: Optional[datetime] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["comment"]] = None
diff --git a/src/hyperspell/types/shared/company.py b/src/hyperspell/types/shared/company.py
index 20642049..d715d772 100644
--- a/src/hyperspell/types/shared/company.py
+++ b/src/hyperspell/types/shared/company.py
@@ -38,6 +38,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -55,7 +56,7 @@
class Company(BaseModel):
- """A CRM company/account record (ENG-2476/D10)."""
+ """A CRM company or account record."""
id: Optional[str] = None
@@ -80,15 +81,10 @@ class Company(BaseModel):
is_active: Optional[bool] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
name: Optional[str] = None
@@ -107,6 +103,7 @@ class Company(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/conversation.py b/src/hyperspell/types/shared/conversation.py
index 00c0564b..e1413136 100644
--- a/src/hyperspell/types/shared/conversation.py
+++ b/src/hyperspell/types/shared/conversation.py
@@ -19,17 +19,14 @@ class Conversation(BaseModel):
children: Optional[List["Message"]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
+ participants: Optional[List["Person"]] = None
+
text: Optional[str] = None
title: Optional[str] = None
@@ -37,4 +34,5 @@ class Conversation(BaseModel):
type: Optional[Literal["conversation"]] = None
+from .person import Person
from .message import Message
diff --git a/src/hyperspell/types/shared/deal.py b/src/hyperspell/types/shared/deal.py
index 60bad8e0..2b1baf5a 100644
--- a/src/hyperspell/types/shared/deal.py
+++ b/src/hyperspell/types/shared/deal.py
@@ -39,6 +39,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -56,7 +57,7 @@
class Deal(BaseModel):
- """A CRM deal/opportunity record (ENG-2476/D10)."""
+ """A CRM deal or opportunity record."""
id: Optional[str] = None
@@ -77,15 +78,10 @@ class Deal(BaseModel):
lost_reason: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
name: Optional[str] = None
@@ -106,6 +102,7 @@ class Deal(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/divider.py b/src/hyperspell/types/shared/divider.py
index 17c80be2..9a96be96 100644
--- a/src/hyperspell/types/shared/divider.py
+++ b/src/hyperspell/types/shared/divider.py
@@ -13,15 +13,10 @@ class Divider(BaseModel):
id: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["divider"]] = None
diff --git a/src/hyperspell/types/shared/document.py b/src/hyperspell/types/shared/document.py
index 1e9e3ee2..052f3857 100644
--- a/src/hyperspell/types/shared/document.py
+++ b/src/hyperspell/types/shared/document.py
@@ -38,6 +38,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -60,15 +61,10 @@ class Document(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -79,6 +75,7 @@ class Document(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/equation.py b/src/hyperspell/types/shared/equation.py
index 6343cf8f..b31507fe 100644
--- a/src/hyperspell/types/shared/equation.py
+++ b/src/hyperspell/types/shared/equation.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -96,15 +98,10 @@ class Equation(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -113,6 +110,7 @@ class Equation(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/event.py b/src/hyperspell/types/shared/event.py
index 303c264b..e3e0b710 100644
--- a/src/hyperspell/types/shared/event.py
+++ b/src/hyperspell/types/shared/event.py
@@ -39,6 +39,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -69,15 +70,10 @@ class Event(BaseModel):
meeting_url: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
start_at: Optional[datetime] = None
@@ -90,6 +86,7 @@ class Event(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/file.py b/src/hyperspell/types/shared/file.py
index 52657f26..6b84f50b 100644
--- a/src/hyperspell/types/shared/file.py
+++ b/src/hyperspell/types/shared/file.py
@@ -38,6 +38,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -63,16 +64,14 @@ class File(BaseModel):
children: Optional[TypingList[Child]] = None
- metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ content_truncated: Optional[bool] = None
+ """Whether the stored readable body is a capped preview of the full file."""
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
+ metadata: Optional[Metadata] = None
+ """Optional annotations carried by a hyperdoc node.
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
path: Optional[TypingList[str]] = None
@@ -85,6 +84,7 @@ class File(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/footnote.py b/src/hyperspell/types/shared/footnote.py
index d604fb5d..b3c57646 100644
--- a/src/hyperspell/types/shared/footnote.py
+++ b/src/hyperspell/types/shared/footnote.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -96,15 +98,10 @@ class Footnote(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -113,6 +110,7 @@ class Footnote(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/heading.py b/src/hyperspell/types/shared/heading.py
index 752520ce..e83da14f 100644
--- a/src/hyperspell/types/shared/heading.py
+++ b/src/hyperspell/types/shared/heading.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -98,15 +100,10 @@ class Heading(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -115,6 +112,7 @@ class Heading(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/image.py b/src/hyperspell/types/shared/image.py
index 3ee29e68..cb9d4942 100644
--- a/src/hyperspell/types/shared/image.py
+++ b/src/hyperspell/types/shared/image.py
@@ -17,15 +17,10 @@ class Image(BaseModel):
id: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["image"]] = None
diff --git a/src/hyperspell/types/shared/line_break.py b/src/hyperspell/types/shared/line_break.py
index ad4c4e28..5f350d38 100644
--- a/src/hyperspell/types/shared/line_break.py
+++ b/src/hyperspell/types/shared/line_break.py
@@ -13,15 +13,10 @@ class LineBreak(BaseModel):
id: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["line_break"]] = None
diff --git a/src/hyperspell/types/shared/link.py b/src/hyperspell/types/shared/link.py
index 3a6676e8..07125fcd 100644
--- a/src/hyperspell/types/shared/link.py
+++ b/src/hyperspell/types/shared/link.py
@@ -17,15 +17,10 @@ class Link(BaseModel):
id: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["link"]] = None
diff --git a/src/hyperspell/types/shared/list.py b/src/hyperspell/types/shared/list.py
index ae53ac45..f2ff8011 100644
--- a/src/hyperspell/types/shared/list.py
+++ b/src/hyperspell/types/shared/list.py
@@ -3,8 +3,9 @@
from __future__ import annotations
import typing
-from typing_extensions import Literal, TypeAlias, TypeAliasType
+from typing_extensions import Literal, Annotated, TypeAlias, TypeAliasType
+from ..._utils import PropertyInfo
from .metadata import Metadata
from ..._compat import PYDANTIC_V1
from ..._models import BaseModel
@@ -12,9 +13,9 @@
__all__ = ["List", "Child"]
if typing.TYPE_CHECKING or not PYDANTIC_V1:
- Child = TypeAliasType("Child", typing.Union["ListItem", "ToDo"])
+ Child = TypeAliasType("Child", Annotated[typing.Union["ListItem", "ToDo"], PropertyInfo(discriminator="type")])
else:
- Child: TypeAlias = typing.Union["ListItem", "ToDo"]
+ Child: TypeAlias = Annotated[typing.Union["ListItem", "ToDo"], PropertyInfo(discriminator="type")]
class List(BaseModel):
@@ -23,15 +24,10 @@ class List(BaseModel):
children: typing.Optional[typing.List[Child]] = None
metadata: typing.Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
ordered: typing.Optional[bool] = None
diff --git a/src/hyperspell/types/shared/list_item.py b/src/hyperspell/types/shared/list_item.py
index a24bef61..e4f9f394 100644
--- a/src/hyperspell/types/shared/list_item.py
+++ b/src/hyperspell/types/shared/list_item.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -96,15 +98,10 @@ class ListItem(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -113,6 +110,7 @@ class ListItem(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/message.py b/src/hyperspell/types/shared/message.py
index c63ae448..0bdea8ff 100644
--- a/src/hyperspell/types/shared/message.py
+++ b/src/hyperspell/types/shared/message.py
@@ -39,6 +39,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -78,15 +79,10 @@ class Message(BaseModel):
mentioned_users: Optional[TypingList["Person"]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
num_replies: Optional[int] = None
@@ -110,6 +106,7 @@ class Message(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/metadata.py b/src/hyperspell/types/shared/metadata.py
index 42aea64c..e58db64b 100644
--- a/src/hyperspell/types/shared/metadata.py
+++ b/src/hyperspell/types/shared/metadata.py
@@ -8,13 +8,10 @@
class Source(BaseModel):
- """A reference to a memory/chunk that a block's content is grounded in (ENG-1390).
+ """A reference to source content that supports a block.
- Chunks are the unit persisted to the DB — extracted memories become chunks when
- indexed — so `chunk_id` is the stable pointer back to the source. `resource_id`
- and `source` locate the originating document; `score` carries optional retrieval
- relevance. Kept deliberately self-contained (plain `str` for `source` rather than
- the `DocumentProviders` enum) so the hyperdoc format stays free of app-layer imports.
+ ``chunk_id`` identifies the supporting content. ``resource_id`` and
+ ``source`` identify its document, and ``score`` optionally records relevance.
"""
chunk_id: str
@@ -27,15 +24,10 @@ class Source(BaseModel):
class Metadata(BaseModel):
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is
+ omitted from serialized responses.
"""
edited_by: Optional[str] = None
diff --git a/src/hyperspell/types/shared/page.py b/src/hyperspell/types/shared/page.py
new file mode 100644
index 00000000..ff713ef8
--- /dev/null
+++ b/src/hyperspell/types/shared/page.py
@@ -0,0 +1,129 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List as TypingList, Union, Optional
+from typing_extensions import Literal, Annotated, TypeAlias, TypeAliasType
+
+from .blob import Blob
+from .code import Code
+from .link import Link
+from .text import Text
+from .image import Image
+from .comment import Comment
+from .divider import Divider
+from ..._utils import PropertyInfo
+from .metadata import Metadata
+from ..._compat import PYDANTIC_V1
+from ..._models import BaseModel
+from .tool_call import ToolCall
+from .line_break import LineBreak
+from .tool_result import ToolResult
+from .trace_message import TraceMessage
+
+__all__ = ["Page", "Child"]
+
+if TYPE_CHECKING or not PYDANTIC_V1:
+ Child = TypeAliasType(
+ "Child",
+ Annotated[
+ Union[
+ Blob,
+ "Callout",
+ "Chunk",
+ Code,
+ Comment,
+ Divider,
+ "Equation",
+ "Footnote",
+ "Heading",
+ Image,
+ Link,
+ LineBreak,
+ "ListList",
+ "ListItem",
+ "Page",
+ "Paragraph",
+ "Quote",
+ "Table",
+ "TableCell",
+ "TableRow",
+ Text,
+ "ToDo",
+ ToolCall,
+ ToolResult,
+ TraceMessage,
+ "Utterance",
+ ],
+ PropertyInfo(discriminator="type"),
+ ],
+ )
+else:
+ Child: TypeAlias = Annotated[
+ Union[
+ Blob,
+ "Callout",
+ "Chunk",
+ Code,
+ Comment,
+ Divider,
+ "Equation",
+ "Footnote",
+ "Heading",
+ Image,
+ Link,
+ LineBreak,
+ "ListList",
+ "ListItem",
+ "Page",
+ "Paragraph",
+ "Quote",
+ "Table",
+ "TableCell",
+ "TableRow",
+ Text,
+ "ToDo",
+ ToolCall,
+ ToolResult,
+ TraceMessage,
+ "Utterance",
+ ],
+ PropertyInfo(discriminator="type"),
+ ]
+
+
+class Page(BaseModel):
+ id: Optional[str] = None
+
+ children: Optional[TypingList[Child]] = None
+
+ metadata: Optional[Metadata] = None
+ """Optional annotations carried by a hyperdoc node.
+
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
+ """
+
+ page_number: Optional[int] = None
+
+ preview_url: Optional[str] = None
+
+ text: Optional[str] = None
+
+ type: Optional[Literal["page"]] = None
+
+
+from .list import List as ListList
+from .chunk import Chunk
+from .quote import Quote
+from .table import Table
+from .to_do import ToDo
+from .callout import Callout
+from .heading import Heading
+from .equation import Equation
+from .footnote import Footnote
+from .list_item import ListItem
+from .paragraph import Paragraph
+from .table_row import TableRow
+from .utterance import Utterance
+from .table_cell import TableCell
diff --git a/src/hyperspell/types/shared/paragraph.py b/src/hyperspell/types/shared/paragraph.py
index e2a4a3eb..52f5083e 100644
--- a/src/hyperspell/types/shared/paragraph.py
+++ b/src/hyperspell/types/shared/paragraph.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -96,15 +98,10 @@ class Paragraph(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -113,6 +110,7 @@ class Paragraph(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/person.py b/src/hyperspell/types/shared/person.py
index fed40d03..b1a38112 100644
--- a/src/hyperspell/types/shared/person.py
+++ b/src/hyperspell/types/shared/person.py
@@ -43,6 +43,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -75,6 +76,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -98,6 +100,8 @@ class Person(BaseModel):
alt_names: Optional[TypingList[str]] = None
+ buying_roles: Optional[TypingList[str]] = None
+
children: Optional[TypingList[Child]] = None
company: Optional[str] = None
@@ -113,38 +117,66 @@ class Person(BaseModel):
emails: Optional[TypingList[str]] = None
"""All known email addresses; `email` holds the primary one"""
+ employment_role: Optional[str] = None
+
+ employment_seniority: Optional[str] = None
+
+ employment_sub_role: Optional[str] = None
+
image_url: Optional[str] = None
+ industry: Optional[str] = None
+
+ is_app_user: Optional[bool] = None
+
+ is_bot: Optional[bool] = None
+
job_title: Optional[str] = None
+ last_sales_activity_at: Optional[str] = None
+
+ last_sales_activity_type: Optional[str] = None
+
+ lead_status: Optional[str] = None
+
+ lifecycle_stage: Optional[str] = None
+
link_urls: Optional[TypingList[str]] = None
- metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ linkedin_url: Optional[str] = None
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
+ marketing_contact_status: Optional[str] = None
+
+ metadata: Optional[Metadata] = None
+ """Optional annotations carried by a hyperdoc node.
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
name: Optional[str] = None
+ original_source: Optional[str] = None
+
+ persona: Optional[str] = None
+
phone_numbers: Optional[TypingList[str]] = None
tags: Optional[TypingList[str]] = None
text: Optional[str] = None
+ timezone: Optional[str] = None
+
type: Optional[Literal["person"]] = None
username: Optional[str] = None
+ website: Optional[str] = None
+
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/provenance.py b/src/hyperspell/types/shared/provenance.py
index fbd6c40e..c14e1f31 100644
--- a/src/hyperspell/types/shared/provenance.py
+++ b/src/hyperspell/types/shared/provenance.py
@@ -11,12 +11,7 @@
class Provenance(BaseModel):
- """Auditability record attached to an agentic answer.
-
- Gated behind ``provenance=true`` on the request: the cheap parts (sources,
- steps, failed_sources) are derived from in-memory loop state, but ``entities``
- costs one indexed DB lookup, so the whole record is only built on request.
- """
+ """Auditability record returned when requested for a supported query."""
entities: Optional[List[ProvenanceEntity]] = None
diff --git a/src/hyperspell/types/shared/provenance_source.py b/src/hyperspell/types/shared/provenance_source.py
index 1be00cbb..132ee68c 100644
--- a/src/hyperspell/types/shared/provenance_source.py
+++ b/src/hyperspell/types/shared/provenance_source.py
@@ -1,6 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import List, Optional
from typing_extensions import Literal
from ..._models import BaseModel
@@ -9,7 +9,10 @@
class ProvenanceSource(BaseModel):
- """A source document that informed the final answer (the post-rank result set)."""
+ """A source document that informed the final answer.
+
+ Includes available retrieval details such as title and relevance score.
+ """
resource_id: str
@@ -19,26 +22,46 @@ class ProvenanceSource(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
+ chunk_id: Optional[str] = None
+
+ content_sha256: Optional[str] = None
+
+ owner: Optional[str] = None
+
score: Optional[float] = None
+ span: Optional[List[object]] = None
+
title: Optional[str] = None
diff --git a/src/hyperspell/types/shared/query_result.py b/src/hyperspell/types/shared/query_result.py
index 23819e33..7afa6cb6 100644
--- a/src/hyperspell/types/shared/query_result.py
+++ b/src/hyperspell/types/shared/query_result.py
@@ -14,10 +14,16 @@ class QueryResult(BaseModel):
answer: Optional[str] = None
"""The answer to the query, if the request was set to answer."""
+ disclaimer: Optional[str] = None
+ """Privacy notice set when cross-user alpha mode ran.
+
+ Callers must display it alongside the synthesized answer.
+ """
+
documents: Optional[List["ScoredDocumentResponse"]] = None
"""
- The matching documents, each carrying its hyperdoc tree plus query-path
- score/highlights/summary (ENG-2479 Phase 4).
+ The matching documents, each carrying its hyperdoc tree plus query-path score,
+ highlights, and summary.
"""
errors: Optional[List[Dict[str, str]]] = None
@@ -28,12 +34,7 @@ class QueryResult(BaseModel):
"""
provenance: Optional[Provenance] = None
- """Auditability record attached to an agentic answer.
-
- Gated behind `provenance=true` on the request: the cheap parts (sources, steps,
- failed_sources) are derived from in-memory loop state, but `entities` costs one
- indexed DB lookup, so the whole record is only built on request.
- """
+ """Auditability record returned when requested for a supported query."""
query: Optional[str] = None
"""The query string that was issued."""
diff --git a/src/hyperspell/types/shared/quote.py b/src/hyperspell/types/shared/quote.py
index 8d59ffde..e148b769 100644
--- a/src/hyperspell/types/shared/quote.py
+++ b/src/hyperspell/types/shared/quote.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -96,15 +98,10 @@ class Quote(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -113,6 +110,7 @@ class Quote(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .table import Table
from .to_do import ToDo
diff --git a/src/hyperspell/types/shared/scored_document_response.py b/src/hyperspell/types/shared/scored_document_response.py
index 6e3902f1..4bf4d154 100644
--- a/src/hyperspell/types/shared/scored_document_response.py
+++ b/src/hyperspell/types/shared/scored_document_response.py
@@ -2,15 +2,124 @@
from __future__ import annotations
-from typing import Dict, List, Union, Optional
+from typing import Dict, List as TypingList, Union, Optional
from datetime import datetime
from typing_extensions import Literal, Annotated, TypeAlias
+from .blob import Blob
+from .code import Code
+from .link import Link
+from .text import Text
+from .image import Image
from .trace import Trace
+from .comment import Comment
+from .divider import Divider
from ..._utils import PropertyInfo
+from .metadata import Metadata
from ..._models import BaseModel
+from .tool_call import ToolCall
+from .line_break import LineBreak
+from .tool_result import ToolResult
+from .trace_message import TraceMessage
+
+__all__ = ["ScoredDocumentResponse", "Document", "DocumentInvoice", "DocumentInvoiceChild", "Chunk"]
+
+DocumentInvoiceChild: TypeAlias = Annotated[
+ Union[
+ Blob,
+ "Callout",
+ "chunk.Chunk",
+ Code,
+ Comment,
+ Divider,
+ "Equation",
+ "Footnote",
+ "Heading",
+ Image,
+ Link,
+ LineBreak,
+ "ListList",
+ "ListItem",
+ "Page",
+ "Paragraph",
+ "Quote",
+ "Table",
+ "TableCell",
+ "TableRow",
+ Text,
+ "ToDo",
+ ToolCall,
+ ToolResult,
+ TraceMessage,
+ "Utterance",
+ ],
+ PropertyInfo(discriminator="type"),
+]
+
+
+class DocumentInvoice(BaseModel):
+ """A customer invoice, vendor bill, or credit memo.
+
+ Line items are included in ``children``.
+ """
+
+ id: Optional[str] = None
+
+ attachment_names: Optional[TypingList[str]] = None
+
+ balance_amount: Optional[float] = None
+
+ cancelled_at: Optional[datetime] = None
+
+ children: Optional[TypingList[DocumentInvoiceChild]] = None
+
+ contact_id: Optional[str] = None
+
+ contact_name: Optional[str] = None
+
+ currency: Optional[str] = None
+
+ due_at: Optional[datetime] = None
+
+ invoice_type: Optional[str] = None
+
+ metadata: Optional[Metadata] = None
+ """Optional annotations carried by a hyperdoc node.
+
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
+ """
+
+ notes: Optional[str] = None
+
+ number: Optional[str] = None
+
+ organization_id: Optional[str] = None
+
+ paid_amount: Optional[float] = None
+
+ paid_at: Optional[datetime] = None
+
+ posted_at: Optional[datetime] = None
+
+ reference: Optional[str] = None
+
+ refund_amount: Optional[float] = None
+
+ refund_reason: Optional[str] = None
+
+ refunded_at: Optional[datetime] = None
+
+ status: Optional[str] = None
+
+ tax_amount: Optional[float] = None
+
+ text: Optional[str] = None
+
+ total_amount: Optional[float] = None
+
+ type: Optional[Literal["invoice"]] = None
-__all__ = ["ScoredDocumentResponse", "Document"]
Document: TypeAlias = Annotated[
Union[
@@ -26,23 +135,36 @@
"Transcript",
"Company",
"Deal",
+ DocumentInvoice,
],
PropertyInfo(discriminator="type"),
]
+class Chunk(BaseModel):
+ """A searchable chunk extracted from a document during ingestion.
+
+ `summary` is null when no summary was generated for the chunk.
+ """
+
+ chunk_id: str
+ """Stable identifier of the chunk."""
+
+ summary: Optional[str] = None
+ """LLM-generated summary of the chunk, if one was produced."""
+
+
class ScoredDocumentResponse(BaseModel):
"""
- A `DocumentResponse` plus the query-path fields a `ScoredDocument` carries
- (ENG-2479): relevance score, matched highlights, and the concatenated
- summary of those highlights.
+ A document response with its relevance score, matched highlights, and
+ a summary of those highlights.
"""
document: Document
"""The full hyperdoc tree.
- Switch on `type` for the document frame and recurse `children` for the body —
- see the `` renderer.
+ Switch on `type` for the document frame and recurse through `children` for the
+ body.
"""
resource_id: str
@@ -53,43 +175,61 @@ class ScoredDocumentResponse(BaseModel):
"slack",
"google_calendar",
"google_mail",
+ "imap",
+ "google_meet",
"box",
"dropbox",
"github",
+ "gitlab",
"google_drive",
"vault",
"web_crawler",
"trace",
+ "microsoft_outlook",
"microsoft_teams",
- "gmail_actions",
"granola",
"fathom",
"fireflies",
+ "figma",
"linear",
"hubspot",
"salesforce",
"coda",
- "lightfield",
+ "confluence",
+ "jira",
+ "metabase",
"gong",
+ "clickup",
+ "lightfield",
+ "pylon",
+ "fellow",
+ "odoo",
+ "external_mcp",
]
type: str
"""Hyperdoc document type discriminator (document, message, file, event, ...)."""
+ chunks: Optional[TypingList[Chunk]] = None
+ """Extracted memories (chunks with summaries) for this document, in document order.
+
+ Present only when explicitly requested via `include_chunks`; omitted otherwise.
+ """
+
collection: Optional[str] = None
"""The document's collection, if any."""
document_date: Optional[datetime] = None
"""The document's own date (e.g. email sent date, event date)."""
- highlights: Optional[List[object]] = None
+ highlights: Optional[TypingList[object]] = None
"""The matched chunks that made this document a hit, with per-chunk scores."""
ingested_at: Optional[datetime] = None
"""When Hyperspell first indexed the document."""
last_modified_at: Optional[datetime] = None
- """When the source document was last modified."""
+ """When the source document was last modified, if supplied by the source."""
metadata: Optional[Dict[str, object]] = None
"""Filterable custom metadata attached to the document."""
@@ -97,7 +237,9 @@ class ScoredDocumentResponse(BaseModel):
score: Optional[float] = None
"""Relevance of the document to the query."""
- status: Optional[Literal["pending", "processing", "completed", "failed", "pending_review", "skipped"]] = None
+ status: Optional[
+ Literal["pending", "processing", "completed", "failed", "pending_review", "skipped", "filtered", "cancelled"]
+ ] = None
"""Indexing status of the document."""
summary: Optional[str] = None
@@ -107,14 +249,28 @@ class ScoredDocumentResponse(BaseModel):
"""Human-readable document title."""
-from . import document
+from . import chunk, document
from .deal import Deal
from .file import File
+from .list import List as ListList
+from .page import Page
from .task import Task
from .event import Event
+from .quote import Quote
+from .table import Table
+from .to_do import ToDo
from .person import Person
+from .callout import Callout
from .company import Company
+from .heading import Heading
from .message import Message
from .website import Website
+from .equation import Equation
+from .footnote import Footnote
+from .list_item import ListItem
+from .paragraph import Paragraph
+from .table_row import TableRow
+from .utterance import Utterance
+from .table_cell import TableCell
from .transcript import Transcript
from .conversation import Conversation
diff --git a/src/hyperspell/types/shared/table.py b/src/hyperspell/types/shared/table.py
index 3d503bd6..d0d64c28 100644
--- a/src/hyperspell/types/shared/table.py
+++ b/src/hyperspell/types/shared/table.py
@@ -20,15 +20,10 @@ class Table(BaseModel):
"""Whether the first row should be treated as a header"""
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
diff --git a/src/hyperspell/types/shared/table_cell.py b/src/hyperspell/types/shared/table_cell.py
index ca23c9ae..91660d44 100644
--- a/src/hyperspell/types/shared/table_cell.py
+++ b/src/hyperspell/types/shared/table_cell.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -98,15 +100,10 @@ class TableCell(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -115,6 +112,7 @@ class TableCell(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/table_row.py b/src/hyperspell/types/shared/table_row.py
index 25b496d0..ef26fb60 100644
--- a/src/hyperspell/types/shared/table_row.py
+++ b/src/hyperspell/types/shared/table_row.py
@@ -17,15 +17,10 @@ class TableRow(BaseModel):
children: Optional[List["TableCell"]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
diff --git a/src/hyperspell/types/shared/task.py b/src/hyperspell/types/shared/task.py
index 4f03723e..24242687 100644
--- a/src/hyperspell/types/shared/task.py
+++ b/src/hyperspell/types/shared/task.py
@@ -39,6 +39,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -65,15 +66,10 @@ class Task(BaseModel):
due_at: Optional[datetime] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
priority: Optional[Literal["urgent", "high", "medium", "low"]] = None
@@ -86,6 +82,7 @@ class Task(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/text.py b/src/hyperspell/types/shared/text.py
index 1cd76e31..95340529 100644
--- a/src/hyperspell/types/shared/text.py
+++ b/src/hyperspell/types/shared/text.py
@@ -17,15 +17,10 @@ class Text(BaseModel):
marks: Optional[List[Literal["bold", "italic", "underline", "strikethrough", "code", "math"]]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["text"]] = None
diff --git a/src/hyperspell/types/shared/to_do.py b/src/hyperspell/types/shared/to_do.py
index ae186b14..545d192f 100644
--- a/src/hyperspell/types/shared/to_do.py
+++ b/src/hyperspell/types/shared/to_do.py
@@ -42,6 +42,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -74,6 +75,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -98,15 +100,10 @@ class ToDo(BaseModel):
children: Optional[TypingList[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -115,6 +112,7 @@ class ToDo(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/src/hyperspell/types/shared/tool_call.py b/src/hyperspell/types/shared/tool_call.py
index cf4908bc..e65b0b08 100644
--- a/src/hyperspell/types/shared/tool_call.py
+++ b/src/hyperspell/types/shared/tool_call.py
@@ -21,15 +21,10 @@ class ToolCall(BaseModel):
args: Optional[Dict[str, object]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["tool_call"]] = None
diff --git a/src/hyperspell/types/shared/tool_result.py b/src/hyperspell/types/shared/tool_result.py
index 661ab774..55a9faa1 100644
--- a/src/hyperspell/types/shared/tool_result.py
+++ b/src/hyperspell/types/shared/tool_result.py
@@ -23,15 +23,10 @@ class ToolResult(BaseModel):
is_error: Optional[bool] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
type: Optional[Literal["tool_result"]] = None
diff --git a/src/hyperspell/types/shared/trace.py b/src/hyperspell/types/shared/trace.py
index 1c47c5cc..64c7d0f0 100644
--- a/src/hyperspell/types/shared/trace.py
+++ b/src/hyperspell/types/shared/trace.py
@@ -27,15 +27,10 @@ class Trace(BaseModel):
children: Optional[List[Child]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
diff --git a/src/hyperspell/types/shared/trace_message.py b/src/hyperspell/types/shared/trace_message.py
index 72645fdf..b39f4dda 100644
--- a/src/hyperspell/types/shared/trace_message.py
+++ b/src/hyperspell/types/shared/trace_message.py
@@ -20,15 +20,10 @@ class TraceMessage(BaseModel):
message_type: Optional[Literal["message", "thinking"]] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
role: Optional[Literal["user", "assistant"]] = None
diff --git a/src/hyperspell/types/shared/transcript.py b/src/hyperspell/types/shared/transcript.py
index faab4b66..bcc6eb9f 100644
--- a/src/hyperspell/types/shared/transcript.py
+++ b/src/hyperspell/types/shared/transcript.py
@@ -13,9 +13,7 @@
class Transcript(BaseModel):
- """
- A time-anchored, speaker-attributed transcript — meetings, calls
- (ENG-2476/D10; mirrors the Trace+TraceStep precedent).
+ """A time-anchored, speaker-attributed transcript for a meeting or call.
Utterance timestamps are relative offsets from `started_at`, which is the
absolute wall-clock anchor.
@@ -28,15 +26,10 @@ class Transcript(BaseModel):
ended_at: Optional[datetime] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
-
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
+ """Optional annotations carried by a hyperdoc node.
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
participants: Optional[List["Person"]] = None
diff --git a/src/hyperspell/types/shared/utterance.py b/src/hyperspell/types/shared/utterance.py
index db249782..567f1076 100644
--- a/src/hyperspell/types/shared/utterance.py
+++ b/src/hyperspell/types/shared/utterance.py
@@ -12,11 +12,9 @@
class Utterance(BaseModel):
- """A speaker-attributed segment of a transcript (ENG-2476/D10).
+ """A speaker-attributed segment of a transcript.
- "Utterance" is the standard name for this across transcription providers
- (AssemblyAI, Deepgram, Rev). Timestamps are relative offsets in seconds —
- provider-native; absolute times derive from `Transcript.started_at`.
+ Start and end times are offsets in seconds from the beginning of the transcript.
"""
text: str
@@ -26,15 +24,10 @@ class Utterance(BaseModel):
end: Optional[float] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
speaker: Optional["Person"] = None
diff --git a/src/hyperspell/types/shared/website.py b/src/hyperspell/types/shared/website.py
index d0a5b49a..e16b7b5b 100644
--- a/src/hyperspell/types/shared/website.py
+++ b/src/hyperspell/types/shared/website.py
@@ -38,6 +38,7 @@
LineBreak,
"ListList",
"ListItem",
+ "Page",
"Paragraph",
"Quote",
"Table",
@@ -70,15 +71,10 @@ class Website(BaseModel):
language: Optional[str] = None
metadata: Optional[Metadata] = None
- """Per-block annotations carried by any Hyperdoc node (ENG-1390).
+ """Optional annotations carried by a hyperdoc node.
- Out-of-band annotations that travel with a block but aren't part of its content:
- provenance (`sources`) and human edit attribution (`edited_by`). New annotation
- types get added here as typed fields as the need arises.
-
- Empty by default. Because `Node.model_dump` forces `exclude_none=True`, an unset
- `metadata` (None) is dropped from serialization entirely, and within a populated
- `Metadata` only the set keys survive.
+ Includes source provenance and human edit attribution. Unset metadata is omitted
+ from serialized responses.
"""
text: Optional[str] = None
@@ -89,6 +85,7 @@ class Website(BaseModel):
from .list import List as ListList
+from .page import Page
from .chunk import Chunk
from .quote import Quote
from .table import Table
diff --git a/tests/api_resources/context_documents/__init__.py b/tests/api_resources/context_documents/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/context_documents/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/context_documents/test_config.py b/tests/api_resources/context_documents/test_config.py
new file mode 100644
index 00000000..1ba55a06
--- /dev/null
+++ b/tests/api_resources/context_documents/test_config.py
@@ -0,0 +1,238 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from hyperspell import Hyperspell, AsyncHyperspell
+from tests.utils import assert_matches_type
+from hyperspell.types.context_documents import (
+ ConfigGetResponse,
+ ConfigResetResponse,
+ ConfigUpdateResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestConfig:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_update(self, client: Hyperspell) -> None:
+ config = client.context_documents.config.update()
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Hyperspell) -> None:
+ config = client.context_documents.config.update(
+ company_prompts={"foo": "string"},
+ detection_prompt="detection_prompt",
+ domain="domain",
+ personal_prompt="personal_prompt",
+ source_weights={"foo": "string"},
+ structure={
+ "company": [
+ {
+ "filename": "filename",
+ "key": "key",
+ "prompt": "prompt",
+ "search_queries": ["string"],
+ }
+ ],
+ "workstream": [
+ {
+ "filename": "filename",
+ "key": "key",
+ "prompt": "prompt",
+ "search_queries": ["string"],
+ }
+ ],
+ },
+ workstream_prompts={"foo": "string"},
+ )
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Hyperspell) -> None:
+ response = client.context_documents.config.with_raw_response.update()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = response.parse()
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Hyperspell) -> None:
+ with client.context_documents.config.with_streaming_response.update() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = response.parse()
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Hyperspell) -> None:
+ config = client.context_documents.config.get()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Hyperspell) -> None:
+ response = client.context_documents.config.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Hyperspell) -> None:
+ with client.context_documents.config.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_reset(self, client: Hyperspell) -> None:
+ config = client.context_documents.config.reset()
+ assert_matches_type(ConfigResetResponse, config, path=["response"])
+
+ @parametrize
+ def test_raw_response_reset(self, client: Hyperspell) -> None:
+ response = client.context_documents.config.with_raw_response.reset()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = response.parse()
+ assert_matches_type(ConfigResetResponse, config, path=["response"])
+
+ @parametrize
+ def test_streaming_response_reset(self, client: Hyperspell) -> None:
+ with client.context_documents.config.with_streaming_response.reset() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = response.parse()
+ assert_matches_type(ConfigResetResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncConfig:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncHyperspell) -> None:
+ config = await async_client.context_documents.config.update()
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ config = await async_client.context_documents.config.update(
+ company_prompts={"foo": "string"},
+ detection_prompt="detection_prompt",
+ domain="domain",
+ personal_prompt="personal_prompt",
+ source_weights={"foo": "string"},
+ structure={
+ "company": [
+ {
+ "filename": "filename",
+ "key": "key",
+ "prompt": "prompt",
+ "search_queries": ["string"],
+ }
+ ],
+ "workstream": [
+ {
+ "filename": "filename",
+ "key": "key",
+ "prompt": "prompt",
+ "search_queries": ["string"],
+ }
+ ],
+ },
+ workstream_prompts={"foo": "string"},
+ )
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.config.with_raw_response.update()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = await response.parse()
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.config.with_streaming_response.update() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = await response.parse()
+ assert_matches_type(ConfigUpdateResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncHyperspell) -> None:
+ config = await async_client.context_documents.config.get()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.config.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = await response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.config.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = await response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_reset(self, async_client: AsyncHyperspell) -> None:
+ config = await async_client.context_documents.config.reset()
+ assert_matches_type(ConfigResetResponse, config, path=["response"])
+
+ @parametrize
+ async def test_raw_response_reset(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.config.with_raw_response.reset()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = await response.parse()
+ assert_matches_type(ConfigResetResponse, config, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_reset(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.config.with_streaming_response.reset() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = await response.parse()
+ assert_matches_type(ConfigResetResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/context_documents/test_digests.py b/tests/api_resources/context_documents/test_digests.py
new file mode 100644
index 00000000..c895de3e
--- /dev/null
+++ b/tests/api_resources/context_documents/test_digests.py
@@ -0,0 +1,164 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from hyperspell import Hyperspell, AsyncHyperspell
+from tests.utils import assert_matches_type
+from hyperspell._utils import parse_datetime
+from hyperspell.types.context_documents import (
+ DigestListResponse,
+ DigestGenerateResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestDigests:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Hyperspell) -> None:
+ digest = client.context_documents.digests.list()
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Hyperspell) -> None:
+ digest = client.context_documents.digests.list(
+ limit=0,
+ period="period",
+ )
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Hyperspell) -> None:
+ response = client.context_documents.digests.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ digest = response.parse()
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Hyperspell) -> None:
+ with client.context_documents.digests.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ digest = response.parse()
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_generate(self, client: Hyperspell) -> None:
+ digest = client.context_documents.digests.generate()
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ @parametrize
+ def test_method_generate_with_all_params(self, client: Hyperspell) -> None:
+ digest = client.context_documents.digests.generate(
+ period="period",
+ sources=["string"],
+ window_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+ window_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+ )
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ @parametrize
+ def test_raw_response_generate(self, client: Hyperspell) -> None:
+ response = client.context_documents.digests.with_raw_response.generate()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ digest = response.parse()
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ @parametrize
+ def test_streaming_response_generate(self, client: Hyperspell) -> None:
+ with client.context_documents.digests.with_streaming_response.generate() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ digest = response.parse()
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncDigests:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncHyperspell) -> None:
+ digest = await async_client.context_documents.digests.list()
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ digest = await async_client.context_documents.digests.list(
+ limit=0,
+ period="period",
+ )
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.digests.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ digest = await response.parse()
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.digests.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ digest = await response.parse()
+ assert_matches_type(DigestListResponse, digest, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_generate(self, async_client: AsyncHyperspell) -> None:
+ digest = await async_client.context_documents.digests.generate()
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ @parametrize
+ async def test_method_generate_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ digest = await async_client.context_documents.digests.generate(
+ period="period",
+ sources=["string"],
+ window_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+ window_start=parse_datetime("2019-12-27T18:11:19.117Z"),
+ )
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ @parametrize
+ async def test_raw_response_generate(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.digests.with_raw_response.generate()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ digest = await response.parse()
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_generate(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.digests.with_streaming_response.generate() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ digest = await response.parse()
+ assert_matches_type(DigestGenerateResponse, digest, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/context_documents/test_trees.py b/tests/api_resources/context_documents/test_trees.py
new file mode 100644
index 00000000..835953ec
--- /dev/null
+++ b/tests/api_resources/context_documents/test_trees.py
@@ -0,0 +1,313 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from hyperspell import Hyperspell, AsyncHyperspell
+from tests.utils import assert_matches_type
+from hyperspell.types.context_documents import (
+ TreeGetResponse,
+ TreeGenerateResponse,
+ TreeProgressResponse,
+ TreeGetLatestResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestTrees:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_generate(self, client: Hyperspell) -> None:
+ tree = client.context_documents.trees.generate()
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ @parametrize
+ def test_method_generate_with_all_params(self, client: Hyperspell) -> None:
+ tree = client.context_documents.trees.generate(
+ sources=["string"],
+ user_id="user_id",
+ workstream_name="workstream_name",
+ )
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ @parametrize
+ def test_raw_response_generate(self, client: Hyperspell) -> None:
+ response = client.context_documents.trees.with_raw_response.generate()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = response.parse()
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ @parametrize
+ def test_streaming_response_generate(self, client: Hyperspell) -> None:
+ with client.context_documents.trees.with_streaming_response.generate() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = response.parse()
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Hyperspell) -> None:
+ tree = client.context_documents.trees.get(
+ "tree_id",
+ )
+ assert_matches_type(TreeGetResponse, tree, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Hyperspell) -> None:
+ response = client.context_documents.trees.with_raw_response.get(
+ "tree_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = response.parse()
+ assert_matches_type(TreeGetResponse, tree, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Hyperspell) -> None:
+ with client.context_documents.trees.with_streaming_response.get(
+ "tree_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = response.parse()
+ assert_matches_type(TreeGetResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Hyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `tree_id` but received ''"):
+ client.context_documents.trees.with_raw_response.get(
+ "",
+ )
+
+ @parametrize
+ def test_method_get_latest(self, client: Hyperspell) -> None:
+ tree = client.context_documents.trees.get_latest()
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ @parametrize
+ def test_method_get_latest_with_all_params(self, client: Hyperspell) -> None:
+ tree = client.context_documents.trees.get_latest(
+ status="status",
+ )
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ @parametrize
+ def test_raw_response_get_latest(self, client: Hyperspell) -> None:
+ response = client.context_documents.trees.with_raw_response.get_latest()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = response.parse()
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get_latest(self, client: Hyperspell) -> None:
+ with client.context_documents.trees.with_streaming_response.get_latest() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = response.parse()
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_progress(self, client: Hyperspell) -> None:
+ tree = client.context_documents.trees.progress(
+ "tree_id",
+ )
+ assert_matches_type(TreeProgressResponse, tree, path=["response"])
+
+ @parametrize
+ def test_raw_response_progress(self, client: Hyperspell) -> None:
+ response = client.context_documents.trees.with_raw_response.progress(
+ "tree_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = response.parse()
+ assert_matches_type(TreeProgressResponse, tree, path=["response"])
+
+ @parametrize
+ def test_streaming_response_progress(self, client: Hyperspell) -> None:
+ with client.context_documents.trees.with_streaming_response.progress(
+ "tree_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = response.parse()
+ assert_matches_type(TreeProgressResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_progress(self, client: Hyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `tree_id` but received ''"):
+ client.context_documents.trees.with_raw_response.progress(
+ "",
+ )
+
+
+class TestAsyncTrees:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_generate(self, async_client: AsyncHyperspell) -> None:
+ tree = await async_client.context_documents.trees.generate()
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_method_generate_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ tree = await async_client.context_documents.trees.generate(
+ sources=["string"],
+ user_id="user_id",
+ workstream_name="workstream_name",
+ )
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_raw_response_generate(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.trees.with_raw_response.generate()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = await response.parse()
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_generate(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.trees.with_streaming_response.generate() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = await response.parse()
+ assert_matches_type(TreeGenerateResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncHyperspell) -> None:
+ tree = await async_client.context_documents.trees.get(
+ "tree_id",
+ )
+ assert_matches_type(TreeGetResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.trees.with_raw_response.get(
+ "tree_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = await response.parse()
+ assert_matches_type(TreeGetResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.trees.with_streaming_response.get(
+ "tree_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = await response.parse()
+ assert_matches_type(TreeGetResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncHyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `tree_id` but received ''"):
+ await async_client.context_documents.trees.with_raw_response.get(
+ "",
+ )
+
+ @parametrize
+ async def test_method_get_latest(self, async_client: AsyncHyperspell) -> None:
+ tree = await async_client.context_documents.trees.get_latest()
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_method_get_latest_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ tree = await async_client.context_documents.trees.get_latest(
+ status="status",
+ )
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get_latest(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.trees.with_raw_response.get_latest()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = await response.parse()
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get_latest(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.trees.with_streaming_response.get_latest() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = await response.parse()
+ assert_matches_type(TreeGetLatestResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_progress(self, async_client: AsyncHyperspell) -> None:
+ tree = await async_client.context_documents.trees.progress(
+ "tree_id",
+ )
+ assert_matches_type(TreeProgressResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_raw_response_progress(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.trees.with_raw_response.progress(
+ "tree_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ tree = await response.parse()
+ assert_matches_type(TreeProgressResponse, tree, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_progress(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.trees.with_streaming_response.progress(
+ "tree_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ tree = await response.parse()
+ assert_matches_type(TreeProgressResponse, tree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_progress(self, async_client: AsyncHyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `tree_id` but received ''"):
+ await async_client.context_documents.trees.with_raw_response.progress(
+ "",
+ )
diff --git a/tests/api_resources/integrations/test_google_calendar.py b/tests/api_resources/integrations/test_google_calendar.py
deleted file mode 100644
index 7175efac..00000000
--- a/tests/api_resources/integrations/test_google_calendar.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from hyperspell import Hyperspell, AsyncHyperspell
-from tests.utils import assert_matches_type
-from hyperspell.types.integrations import Calendar
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestGoogleCalendar:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_list(self, client: Hyperspell) -> None:
- google_calendar = client.integrations.google_calendar.list()
- assert_matches_type(Calendar, google_calendar, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Hyperspell) -> None:
- response = client.integrations.google_calendar.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- google_calendar = response.parse()
- assert_matches_type(Calendar, google_calendar, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Hyperspell) -> None:
- with client.integrations.google_calendar.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- google_calendar = response.parse()
- assert_matches_type(Calendar, google_calendar, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
-
-class TestAsyncGoogleCalendar:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncHyperspell) -> None:
- google_calendar = await async_client.integrations.google_calendar.list()
- assert_matches_type(Calendar, google_calendar, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None:
- response = await async_client.integrations.google_calendar.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- google_calendar = await response.parse()
- assert_matches_type(Calendar, google_calendar, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None:
- async with async_client.integrations.google_calendar.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- google_calendar = await response.parse()
- assert_matches_type(Calendar, google_calendar, path=["response"])
-
- assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/integrations/test_slack.py b/tests/api_resources/integrations/test_slack.py
deleted file mode 100644
index d4312400..00000000
--- a/tests/api_resources/integrations/test_slack.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-import os
-from typing import Any, cast
-
-import pytest
-
-from hyperspell import Hyperspell, AsyncHyperspell
-from tests.utils import assert_matches_type
-
-base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-
-
-class TestSlack:
- parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
-
- @parametrize
- def test_method_list(self, client: Hyperspell) -> None:
- slack = client.integrations.slack.list()
- assert_matches_type(object, slack, path=["response"])
-
- @parametrize
- def test_method_list_with_all_params(self, client: Hyperspell) -> None:
- slack = client.integrations.slack.list(
- channels=["string"],
- exclude_archived=True,
- include_dms=True,
- include_group_dms=True,
- include_private=True,
- )
- assert_matches_type(object, slack, path=["response"])
-
- @parametrize
- def test_raw_response_list(self, client: Hyperspell) -> None:
- response = client.integrations.slack.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- slack = response.parse()
- assert_matches_type(object, slack, path=["response"])
-
- @parametrize
- def test_streaming_response_list(self, client: Hyperspell) -> None:
- with client.integrations.slack.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- slack = response.parse()
- assert_matches_type(object, slack, path=["response"])
-
- assert cast(Any, response.is_closed) is True
-
-
-class TestAsyncSlack:
- parametrize = pytest.mark.parametrize(
- "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
- )
-
- @parametrize
- async def test_method_list(self, async_client: AsyncHyperspell) -> None:
- slack = await async_client.integrations.slack.list()
- assert_matches_type(object, slack, path=["response"])
-
- @parametrize
- async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None:
- slack = await async_client.integrations.slack.list(
- channels=["string"],
- exclude_archived=True,
- include_dms=True,
- include_group_dms=True,
- include_private=True,
- )
- assert_matches_type(object, slack, path=["response"])
-
- @parametrize
- async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None:
- response = await async_client.integrations.slack.with_raw_response.list()
-
- assert response.is_closed is True
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- slack = await response.parse()
- assert_matches_type(object, slack, path=["response"])
-
- @parametrize
- async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None:
- async with async_client.integrations.slack.with_streaming_response.list() as response:
- assert not response.is_closed
- assert response.http_request.headers.get("X-Stainless-Lang") == "python"
-
- slack = await response.parse()
- assert_matches_type(object, slack, path=["response"])
-
- assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_actions.py b/tests/api_resources/test_actions.py
index 7c908656..9a70ba07 100644
--- a/tests/api_resources/test_actions.py
+++ b/tests/api_resources/test_actions.py
@@ -25,7 +25,7 @@ def test_method_add_reaction(self, client: Hyperspell) -> None:
action = client.actions.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
)
assert_matches_type(ActionAddReactionResponse, action, path=["response"])
@@ -35,7 +35,7 @@ def test_method_add_reaction_with_all_params(self, client: Hyperspell) -> None:
action = client.actions.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
connection="connection",
)
@@ -46,7 +46,7 @@ def test_raw_response_add_reaction(self, client: Hyperspell) -> None:
response = client.actions.with_raw_response.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
)
@@ -60,7 +60,7 @@ def test_streaming_response_add_reaction(self, client: Hyperspell) -> None:
with client.actions.with_streaming_response.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
) as response:
assert not response.is_closed
@@ -74,7 +74,7 @@ def test_streaming_response_add_reaction(self, client: Hyperspell) -> None:
@parametrize
def test_method_send_message(self, client: Hyperspell) -> None:
action = client.actions.send_message(
- provider="reddit",
+ provider="slack",
text="text",
)
assert_matches_type(ActionSendMessageResponse, action, path=["response"])
@@ -82,7 +82,7 @@ def test_method_send_message(self, client: Hyperspell) -> None:
@parametrize
def test_method_send_message_with_all_params(self, client: Hyperspell) -> None:
action = client.actions.send_message(
- provider="reddit",
+ provider="slack",
text="text",
channel="channel",
connection="connection",
@@ -93,7 +93,7 @@ def test_method_send_message_with_all_params(self, client: Hyperspell) -> None:
@parametrize
def test_raw_response_send_message(self, client: Hyperspell) -> None:
response = client.actions.with_raw_response.send_message(
- provider="reddit",
+ provider="slack",
text="text",
)
@@ -105,7 +105,7 @@ def test_raw_response_send_message(self, client: Hyperspell) -> None:
@parametrize
def test_streaming_response_send_message(self, client: Hyperspell) -> None:
with client.actions.with_streaming_response.send_message(
- provider="reddit",
+ provider="slack",
text="text",
) as response:
assert not response.is_closed
@@ -127,7 +127,7 @@ async def test_method_add_reaction(self, async_client: AsyncHyperspell) -> None:
action = await async_client.actions.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
)
assert_matches_type(ActionAddReactionResponse, action, path=["response"])
@@ -137,7 +137,7 @@ async def test_method_add_reaction_with_all_params(self, async_client: AsyncHype
action = await async_client.actions.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
connection="connection",
)
@@ -148,7 +148,7 @@ async def test_raw_response_add_reaction(self, async_client: AsyncHyperspell) ->
response = await async_client.actions.with_raw_response.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
)
@@ -162,7 +162,7 @@ async def test_streaming_response_add_reaction(self, async_client: AsyncHyperspe
async with async_client.actions.with_streaming_response.add_reaction(
channel="channel",
name="name",
- provider="reddit",
+ provider="slack",
timestamp="timestamp",
) as response:
assert not response.is_closed
@@ -176,7 +176,7 @@ async def test_streaming_response_add_reaction(self, async_client: AsyncHyperspe
@parametrize
async def test_method_send_message(self, async_client: AsyncHyperspell) -> None:
action = await async_client.actions.send_message(
- provider="reddit",
+ provider="slack",
text="text",
)
assert_matches_type(ActionSendMessageResponse, action, path=["response"])
@@ -184,7 +184,7 @@ async def test_method_send_message(self, async_client: AsyncHyperspell) -> None:
@parametrize
async def test_method_send_message_with_all_params(self, async_client: AsyncHyperspell) -> None:
action = await async_client.actions.send_message(
- provider="reddit",
+ provider="slack",
text="text",
channel="channel",
connection="connection",
@@ -195,7 +195,7 @@ async def test_method_send_message_with_all_params(self, async_client: AsyncHype
@parametrize
async def test_raw_response_send_message(self, async_client: AsyncHyperspell) -> None:
response = await async_client.actions.with_raw_response.send_message(
- provider="reddit",
+ provider="slack",
text="text",
)
@@ -207,7 +207,7 @@ async def test_raw_response_send_message(self, async_client: AsyncHyperspell) ->
@parametrize
async def test_streaming_response_send_message(self, async_client: AsyncHyperspell) -> None:
async with async_client.actions.with_streaming_response.send_message(
- provider="reddit",
+ provider="slack",
text="text",
) as response:
assert not response.is_closed
diff --git a/tests/api_resources/test_context_documents.py b/tests/api_resources/test_context_documents.py
new file mode 100644
index 00000000..f22e9c93
--- /dev/null
+++ b/tests/api_resources/test_context_documents.py
@@ -0,0 +1,259 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from hyperspell import Hyperspell, AsyncHyperspell
+from tests.utils import assert_matches_type
+from hyperspell.types import (
+ ContextDocumentGetResponse,
+ ContextDocumentListResponse,
+ ContextDocumentGenerateResponse,
+)
+from hyperspell.pagination import SyncContextDocumentsCursorPage, AsyncContextDocumentsCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestContextDocuments:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Hyperspell) -> None:
+ context_document = client.context_documents.list()
+ assert_matches_type(
+ SyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Hyperspell) -> None:
+ context_document = client.context_documents.list(
+ cursor="cursor",
+ limit=0,
+ status="processing",
+ )
+ assert_matches_type(
+ SyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ @parametrize
+ def test_raw_response_list(self, client: Hyperspell) -> None:
+ response = client.context_documents.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context_document = response.parse()
+ assert_matches_type(
+ SyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ @parametrize
+ def test_streaming_response_list(self, client: Hyperspell) -> None:
+ with client.context_documents.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context_document = response.parse()
+ assert_matches_type(
+ SyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_generate(self, client: Hyperspell) -> None:
+ context_document = client.context_documents.generate()
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ @parametrize
+ def test_method_generate_with_all_params(self, client: Hyperspell) -> None:
+ context_document = client.context_documents.generate(
+ model="model",
+ prompt="prompt",
+ sources=["string"],
+ user_id="user_id",
+ )
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ @parametrize
+ def test_raw_response_generate(self, client: Hyperspell) -> None:
+ response = client.context_documents.with_raw_response.generate()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context_document = response.parse()
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ @parametrize
+ def test_streaming_response_generate(self, client: Hyperspell) -> None:
+ with client.context_documents.with_streaming_response.generate() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context_document = response.parse()
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Hyperspell) -> None:
+ context_document = client.context_documents.get(
+ "document_id",
+ )
+ assert_matches_type(ContextDocumentGetResponse, context_document, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Hyperspell) -> None:
+ response = client.context_documents.with_raw_response.get(
+ "document_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context_document = response.parse()
+ assert_matches_type(ContextDocumentGetResponse, context_document, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Hyperspell) -> None:
+ with client.context_documents.with_streaming_response.get(
+ "document_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context_document = response.parse()
+ assert_matches_type(ContextDocumentGetResponse, context_document, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Hyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `document_id` but received ''"):
+ client.context_documents.with_raw_response.get(
+ "",
+ )
+
+
+class TestAsyncContextDocuments:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncHyperspell) -> None:
+ context_document = await async_client.context_documents.list()
+ assert_matches_type(
+ AsyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ context_document = await async_client.context_documents.list(
+ cursor="cursor",
+ limit=0,
+ status="processing",
+ )
+ assert_matches_type(
+ AsyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context_document = await response.parse()
+ assert_matches_type(
+ AsyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context_document = await response.parse()
+ assert_matches_type(
+ AsyncContextDocumentsCursorPage[ContextDocumentListResponse], context_document, path=["response"]
+ )
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_generate(self, async_client: AsyncHyperspell) -> None:
+ context_document = await async_client.context_documents.generate()
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ @parametrize
+ async def test_method_generate_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ context_document = await async_client.context_documents.generate(
+ model="model",
+ prompt="prompt",
+ sources=["string"],
+ user_id="user_id",
+ )
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ @parametrize
+ async def test_raw_response_generate(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.with_raw_response.generate()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context_document = await response.parse()
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_generate(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.with_streaming_response.generate() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context_document = await response.parse()
+ assert_matches_type(ContextDocumentGenerateResponse, context_document, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncHyperspell) -> None:
+ context_document = await async_client.context_documents.get(
+ "document_id",
+ )
+ assert_matches_type(ContextDocumentGetResponse, context_document, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.context_documents.with_raw_response.get(
+ "document_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ context_document = await response.parse()
+ assert_matches_type(ContextDocumentGetResponse, context_document, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.context_documents.with_streaming_response.get(
+ "document_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ context_document = await response.parse()
+ assert_matches_type(ContextDocumentGetResponse, context_document, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncHyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `document_id` but received ''"):
+ await async_client.context_documents.with_raw_response.get(
+ "",
+ )
diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py
index 125b2809..68b303e8 100644
--- a/tests/api_resources/test_memories.py
+++ b/tests/api_resources/test_memories.py
@@ -93,6 +93,7 @@ def test_method_list_with_all_params(self, client: Hyperspell) -> None:
collection="collection",
cursor="cursor",
filter="filter",
+ include_chunks=0,
size=0,
source="reddit",
status="pending",
@@ -247,6 +248,15 @@ def test_method_get(self, client: Hyperspell) -> None:
)
assert_matches_type(MemoryGetResponse, memory, path=["response"])
+ @parametrize
+ def test_method_get_with_all_params(self, client: Hyperspell) -> None:
+ memory = client.memories.get(
+ resource_id="resource_id",
+ source="reddit",
+ include_chunks=True,
+ )
+ assert_matches_type(MemoryGetResponse, memory, path=["response"])
+
@parametrize
def test_raw_response_get(self, client: Hyperspell) -> None:
response = client.memories.with_raw_response.get(
@@ -299,12 +309,7 @@ def test_method_search_with_all_params(self, client: Hyperspell) -> None:
"after": parse_datetime("2019-12-27T18:11:19.117Z"),
"answer_model": "llama-3.1",
"before": parse_datetime("2019-12-27T18:11:19.117Z"),
- "box": {"weight": 0},
"filter": {},
- "google_calendar": {
- "calendar_id": "calendar_id",
- "weight": 0,
- },
"google_drive": {"weight": 0},
"google_mail": {
"label_ids": ["string"],
@@ -326,6 +331,7 @@ def test_method_search_with_all_params(self, client: Hyperspell) -> None:
"include_private": True,
"weight": 0,
},
+ "timezone": "timezone",
"vault": {"weight": 0},
"web_crawler": {
"max_depth": 0,
@@ -499,6 +505,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncHyperspell)
collection="collection",
cursor="cursor",
filter="filter",
+ include_chunks=0,
size=0,
source="reddit",
status="pending",
@@ -653,6 +660,15 @@ async def test_method_get(self, async_client: AsyncHyperspell) -> None:
)
assert_matches_type(MemoryGetResponse, memory, path=["response"])
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncHyperspell) -> None:
+ memory = await async_client.memories.get(
+ resource_id="resource_id",
+ source="reddit",
+ include_chunks=True,
+ )
+ assert_matches_type(MemoryGetResponse, memory, path=["response"])
+
@parametrize
async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None:
response = await async_client.memories.with_raw_response.get(
@@ -705,12 +721,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncHyperspell
"after": parse_datetime("2019-12-27T18:11:19.117Z"),
"answer_model": "llama-3.1",
"before": parse_datetime("2019-12-27T18:11:19.117Z"),
- "box": {"weight": 0},
"filter": {},
- "google_calendar": {
- "calendar_id": "calendar_id",
- "weight": 0,
- },
"google_drive": {"weight": 0},
"google_mail": {
"label_ids": ["string"],
@@ -732,6 +743,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncHyperspell
"include_private": True,
"weight": 0,
},
+ "timezone": "timezone",
"vault": {"weight": 0},
"web_crawler": {
"max_depth": 0,
diff --git a/tests/test_client.py b/tests/test_client.py
index f0f5bcca..a7a6d0ca 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -438,12 +438,10 @@ def test_validate_headers(self) -> None:
client = Hyperspell(base_url=base_url, api_key=api_key, user_id=user_id, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("Authorization") == f"Bearer {api_key}"
- request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
- assert request.headers.get("X-As-User") == user_id
with pytest.raises(HyperspellError):
with update_env(**{"HYPERSPELL_API_KEY": Omit()}):
- client2 = Hyperspell(base_url=base_url, api_key=None, user_id=None, _strict_response_validation=True)
+ client2 = Hyperspell(base_url=base_url, api_key=None, user_id=user_id, _strict_response_validation=True)
_ = client2
def test_default_query_option(self) -> None:
@@ -1424,13 +1422,11 @@ def test_validate_headers(self) -> None:
client = AsyncHyperspell(base_url=base_url, api_key=api_key, user_id=user_id, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("Authorization") == f"Bearer {api_key}"
- request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
- assert request.headers.get("X-As-User") == user_id
with pytest.raises(HyperspellError):
with update_env(**{"HYPERSPELL_API_KEY": Omit()}):
client2 = AsyncHyperspell(
- base_url=base_url, api_key=None, user_id=None, _strict_response_validation=True
+ base_url=base_url, api_key=None, user_id=user_id, _strict_response_validation=True
)
_ = client2
diff --git a/uv.lock b/uv.lock
index 01163374..71d5cc38 100644
--- a/uv.lock
+++ b/uv.lock
@@ -4,10 +4,10 @@ requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.14' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
"python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
- "python_full_version < '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
"python_full_version >= '3.10' and extra == 'group-10-hyperspell-pydantic-v1' and extra != 'group-10-hyperspell-pydantic-v2'",
- "python_full_version < '3.10' and extra == 'group-10-hyperspell-pydantic-v1' and extra != 'group-10-hyperspell-pydantic-v2'",
"python_full_version >= '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra != 'group-10-hyperspell-pydantic-v2'",
+ "python_full_version < '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
+ "python_full_version < '3.10' and extra == 'group-10-hyperspell-pydantic-v1' and extra != 'group-10-hyperspell-pydantic-v2'",
"python_full_version < '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra != 'group-10-hyperspell-pydantic-v2'",
]
conflicts = [[
@@ -470,7 +470,7 @@ wheels = [
[[package]]
name = "hyperspell"
-version = "0.38.0"
+version = "0.0.1"
source = { editable = "." }
dependencies = [
{ name = "anyio" },
@@ -1063,8 +1063,8 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
"python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
- "python_full_version < '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
"python_full_version >= '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra != 'group-10-hyperspell-pydantic-v2'",
+ "python_full_version < '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra == 'group-10-hyperspell-pydantic-v2'",
"python_full_version < '3.10' and extra != 'group-10-hyperspell-pydantic-v1' and extra != 'group-10-hyperspell-pydantic-v2'",
]
dependencies = [