From a4618fd77e3b274036e0454e4d268d07df92f461 Mon Sep 17 00:00:00 2001 From: stlc-bot Date: Wed, 2 Sep 2026 21:24:30 +0000 Subject: [PATCH] feat(api): sync generated SDK from staging ca1f56f6d237bb6dca45205a5ebe0a82f96ac4fb Source: hyperspell/python-sdk-staging@ca1f56f6d237bb6dca45205a5ebe0a82f96ac4fb Promoted by: https://github.com/hyperspell/hyperspell/actions/runs/33684943954 --- .devcontainer/devcontainer.json | 2 +- .github/workflows/ci.yml | 44 +- .github/workflows/sdk-tests.yml | 25 + .stats.yml | 2 +- README.md | 70 ++- api.md | 70 ++- scripts/mock | 2 +- src/hyperspell/_client.py | 74 ++- src/hyperspell/pagination.py | 42 +- src/hyperspell/resources/__init__.py | 14 + src/hyperspell/resources/actions.py | 112 +--- src/hyperspell/resources/auth.py | 16 +- src/hyperspell/resources/connections.py | 18 +- .../resources/context_documents/__init__.py | 61 +++ .../resources/context_documents/config.py | 323 ++++++++++++ .../context_documents/context_documents.py | 491 ++++++++++++++++++ .../resources/context_documents/digests.py | 319 ++++++++++++ .../resources/context_documents/trees.py | 470 +++++++++++++++++ src/hyperspell/resources/evaluate.py | 8 +- .../resources/integrations/__init__.py | 28 - .../resources/integrations/google_calendar.py | 143 ----- .../resources/integrations/integrations.py | 64 --- .../resources/integrations/slack.py | 228 -------- src/hyperspell/resources/memories.py | 301 +++++++---- src/hyperspell/types/__init__.py | 9 + .../types/action_add_reaction_params.py | 30 +- .../types/action_send_message_params.py | 30 +- .../types/auth_delete_user_response.py | 2 + src/hyperspell/types/auth_me_response.py | 32 +- .../types/auth_user_token_params.py | 5 +- .../types/connection_list_response.py | 37 +- .../types/context_document_generate_params.py | 27 + .../context_document_generate_response.py | 15 + .../types/context_document_get_response.py | 34 ++ .../types/context_document_list_params.py | 16 + .../types/context_document_list_response.py | 26 + .../types/context_documents/__init__.py | 18 + .../context_documents/config_get_response.py | 21 + .../config_reset_response.py | 21 + .../context_documents/config_update_params.py | 63 +++ .../config_update_response.py | 21 + .../digest_generate_params.py | 33 ++ .../digest_generate_response.py | 15 + .../context_documents/digest_list_params.py | 14 + .../context_documents/digest_list_response.py | 30 ++ .../context_documents/tree_generate_params.py | 25 + .../tree_generate_response.py | 15 + .../tree_get_latest_params.py | 12 + .../tree_get_latest_response.py | 70 +++ .../context_documents/tree_get_response.py | 70 +++ .../tree_progress_response.py | 30 ++ .../types/integration_list_response.py | 56 +- src/hyperspell/types/integrations/__init__.py | 2 - src/hyperspell/types/integrations/calendar.py | 25 - .../types/integrations/slack_list_params.py | 27 - .../web_crawler_index_response.py | 20 +- .../types/memory_add_bulk_response.py | 24 +- .../types/memory_delete_response.py | 16 +- src/hyperspell/types/memory_get_params.py | 55 ++ src/hyperspell/types/memory_get_response.py | 177 ++++++- src/hyperspell/types/memory_list_params.py | 27 +- src/hyperspell/types/memory_list_response.py | 177 ++++++- src/hyperspell/types/memory_search_params.py | 93 ++-- src/hyperspell/types/memory_status.py | 20 +- .../types/memory_status_response.py | 99 +++- src/hyperspell/types/memory_update_params.py | 16 +- src/hyperspell/types/shared/__init__.py | 1 + src/hyperspell/types/shared/blob.py | 11 +- src/hyperspell/types/shared/callout.py | 14 +- src/hyperspell/types/shared/chunk.py | 14 +- src/hyperspell/types/shared/code.py | 11 +- src/hyperspell/types/shared/comment.py | 11 +- src/hyperspell/types/shared/company.py | 15 +- src/hyperspell/types/shared/conversation.py | 14 +- src/hyperspell/types/shared/deal.py | 15 +- src/hyperspell/types/shared/divider.py | 11 +- src/hyperspell/types/shared/document.py | 13 +- src/hyperspell/types/shared/equation.py | 14 +- src/hyperspell/types/shared/event.py | 13 +- src/hyperspell/types/shared/file.py | 16 +- src/hyperspell/types/shared/footnote.py | 14 +- src/hyperspell/types/shared/heading.py | 14 +- src/hyperspell/types/shared/image.py | 11 +- src/hyperspell/types/shared/line_break.py | 11 +- src/hyperspell/types/shared/link.py | 11 +- src/hyperspell/types/shared/list.py | 18 +- src/hyperspell/types/shared/list_item.py | 14 +- src/hyperspell/types/shared/message.py | 13 +- src/hyperspell/types/shared/metadata.py | 20 +- src/hyperspell/types/shared/page.py | 129 +++++ src/hyperspell/types/shared/paragraph.py | 14 +- src/hyperspell/types/shared/person.py | 48 +- src/hyperspell/types/shared/provenance.py | 7 +- .../types/shared/provenance_source.py | 31 +- src/hyperspell/types/shared/query_result.py | 17 +- src/hyperspell/types/shared/quote.py | 14 +- .../types/shared/scored_document_response.py | 182 ++++++- src/hyperspell/types/shared/table.py | 11 +- src/hyperspell/types/shared/table_cell.py | 14 +- src/hyperspell/types/shared/table_row.py | 11 +- src/hyperspell/types/shared/task.py | 13 +- src/hyperspell/types/shared/text.py | 11 +- src/hyperspell/types/shared/to_do.py | 14 +- src/hyperspell/types/shared/tool_call.py | 11 +- src/hyperspell/types/shared/tool_result.py | 11 +- src/hyperspell/types/shared/trace.py | 11 +- src/hyperspell/types/shared/trace_message.py | 11 +- src/hyperspell/types/shared/transcript.py | 15 +- src/hyperspell/types/shared/utterance.py | 17 +- src/hyperspell/types/shared/website.py | 13 +- .../context_documents/__init__.py | 1 + .../context_documents/test_config.py | 238 +++++++++ .../context_documents/test_digests.py | 164 ++++++ .../context_documents/test_trees.py | 313 +++++++++++ .../integrations/test_google_calendar.py | 74 --- .../api_resources/integrations/test_slack.py | 95 ---- tests/api_resources/test_actions.py | 32 +- tests/api_resources/test_context_documents.py | 259 +++++++++ tests/api_resources/test_memories.py | 32 +- tests/test_client.py | 8 +- uv.lock | 8 +- 121 files changed, 5068 insertions(+), 1542 deletions(-) create mode 100644 .github/workflows/sdk-tests.yml create mode 100644 src/hyperspell/resources/context_documents/__init__.py create mode 100644 src/hyperspell/resources/context_documents/config.py create mode 100644 src/hyperspell/resources/context_documents/context_documents.py create mode 100644 src/hyperspell/resources/context_documents/digests.py create mode 100644 src/hyperspell/resources/context_documents/trees.py delete mode 100644 src/hyperspell/resources/integrations/google_calendar.py delete mode 100644 src/hyperspell/resources/integrations/slack.py create mode 100644 src/hyperspell/types/context_document_generate_params.py create mode 100644 src/hyperspell/types/context_document_generate_response.py create mode 100644 src/hyperspell/types/context_document_get_response.py create mode 100644 src/hyperspell/types/context_document_list_params.py create mode 100644 src/hyperspell/types/context_document_list_response.py create mode 100644 src/hyperspell/types/context_documents/__init__.py create mode 100644 src/hyperspell/types/context_documents/config_get_response.py create mode 100644 src/hyperspell/types/context_documents/config_reset_response.py create mode 100644 src/hyperspell/types/context_documents/config_update_params.py create mode 100644 src/hyperspell/types/context_documents/config_update_response.py create mode 100644 src/hyperspell/types/context_documents/digest_generate_params.py create mode 100644 src/hyperspell/types/context_documents/digest_generate_response.py create mode 100644 src/hyperspell/types/context_documents/digest_list_params.py create mode 100644 src/hyperspell/types/context_documents/digest_list_response.py create mode 100644 src/hyperspell/types/context_documents/tree_generate_params.py create mode 100644 src/hyperspell/types/context_documents/tree_generate_response.py create mode 100644 src/hyperspell/types/context_documents/tree_get_latest_params.py create mode 100644 src/hyperspell/types/context_documents/tree_get_latest_response.py create mode 100644 src/hyperspell/types/context_documents/tree_get_response.py create mode 100644 src/hyperspell/types/context_documents/tree_progress_response.py delete mode 100644 src/hyperspell/types/integrations/calendar.py delete mode 100644 src/hyperspell/types/integrations/slack_list_params.py create mode 100644 src/hyperspell/types/memory_get_params.py create mode 100644 src/hyperspell/types/shared/page.py create mode 100644 tests/api_resources/context_documents/__init__.py create mode 100644 tests/api_resources/context_documents/test_config.py create mode 100644 tests/api_resources/context_documents/test_digests.py create mode 100644 tests/api_resources/context_documents/test_trees.py delete mode 100644 tests/api_resources/integrations/test_google_calendar.py delete mode 100644 tests/api_resources/integrations/test_slack.py create mode 100644 tests/api_resources/test_context_documents.py 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 = [