gumloop 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {gumloop-0.3.2 → gumloop-0.3.3}/PKG-INFO +1 -1
  2. {gumloop-0.3.2 → gumloop-0.3.3}/pyproject.toml +1 -1
  3. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/__init__.py +1 -1
  4. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/_client.py +7 -3
  5. gumloop-0.3.3/src/gumloop/cli/commands/brain.py +66 -0
  6. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/main.py +2 -0
  7. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/__init__.py +4 -0
  8. gumloop-0.3.3/src/gumloop/resources/brain.py +47 -0
  9. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/types.py +29 -0
  10. gumloop-0.3.3/tests/cli/test_brain.py +81 -0
  11. gumloop-0.3.3/tests/sdk/test_brain.py +75 -0
  12. {gumloop-0.3.2 → gumloop-0.3.3}/uv.lock +1 -1
  13. {gumloop-0.3.2 → gumloop-0.3.3}/.env.example +0 -0
  14. {gumloop-0.3.2 → gumloop-0.3.3}/.gitignore +0 -0
  15. {gumloop-0.3.2 → gumloop-0.3.3}/.pre-commit-config.yaml +0 -0
  16. {gumloop-0.3.2 → gumloop-0.3.3}/.python-version +0 -0
  17. {gumloop-0.3.2 → gumloop-0.3.3}/LICENSE +0 -0
  18. {gumloop-0.3.2 → gumloop-0.3.3}/README.md +0 -0
  19. {gumloop-0.3.2 → gumloop-0.3.3}/scripts/check +0 -0
  20. {gumloop-0.3.2 → gumloop-0.3.3}/scripts/fmt +0 -0
  21. {gumloop-0.3.2 → gumloop-0.3.3}/scripts/sdk_examples.py +0 -0
  22. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/_http.py +0 -0
  23. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/__init__.py +0 -0
  24. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/__init__.py +0 -0
  25. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/_args_input.py +0 -0
  26. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/_downloads.py +0 -0
  27. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/agents.py +0 -0
  28. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/artifacts.py +0 -0
  29. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/auth.py +0 -0
  30. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/chat.py +0 -0
  31. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/mcp.py +0 -0
  32. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/sessions.py +0 -0
  33. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/commands/skills.py +0 -0
  34. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/console.py +0 -0
  35. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/context.py +0 -0
  36. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/credentials.py +0 -0
  37. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/errors.py +0 -0
  38. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/cli/oauth.py +0 -0
  39. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/errors.py +0 -0
  40. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/oauth.py +0 -0
  41. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/py.typed +0 -0
  42. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/agents.py +0 -0
  43. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/artifacts.py +0 -0
  44. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/chat.py +0 -0
  45. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/mcp.py +0 -0
  46. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/sessions.py +0 -0
  47. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/skills.py +0 -0
  48. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/resources/teams.py +0 -0
  49. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/spec/__init__.py +0 -0
  50. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/spec/_compat.py +0 -0
  51. {gumloop-0.3.2 → gumloop-0.3.3}/src/gumloop/spec/_extensions.py +0 -0
  52. {gumloop-0.3.2 → gumloop-0.3.3}/tests/__init__.py +0 -0
  53. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/__init__.py +0 -0
  54. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/conftest.py +0 -0
  55. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_agents.py +0 -0
  56. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_args_input.py +0 -0
  57. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_artifacts.py +0 -0
  58. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_chat.py +0 -0
  59. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_context.py +0 -0
  60. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_credentials.py +0 -0
  61. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_login.py +0 -0
  62. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_main.py +0 -0
  63. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_mcp.py +0 -0
  64. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_sessions.py +0 -0
  65. {gumloop-0.3.2 → gumloop-0.3.3}/tests/cli/test_skills.py +0 -0
  66. {gumloop-0.3.2 → gumloop-0.3.3}/tests/conftest.py +0 -0
  67. {gumloop-0.3.2 → gumloop-0.3.3}/tests/integration/conftest.py +0 -0
  68. {gumloop-0.3.2 → gumloop-0.3.3}/tests/integration/test_live.py +0 -0
  69. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/__init__.py +0 -0
  70. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/conftest.py +0 -0
  71. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/helpers.py +0 -0
  72. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_agents.py +0 -0
  73. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_artifacts.py +0 -0
  74. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_chat.py +0 -0
  75. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_client.py +0 -0
  76. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_mcp.py +0 -0
  77. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_oauth.py +0 -0
  78. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_sessions.py +0 -0
  79. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_skills.py +0 -0
  80. {gumloop-0.3.2 → gumloop-0.3.3}/tests/sdk/test_teams.py +0 -0
  81. {gumloop-0.3.2 → gumloop-0.3.3}/tests/test_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gumloop
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Python client for the Gumloop API
5
5
  Project-URL: Homepage, https://github.com/gumloop/gumloop-py
6
6
  Project-URL: Bug Tracker, https://github.com/gumloop/gumloop-py/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gumloop"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Python client for the Gumloop API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -6,7 +6,7 @@ from gumloop.errors import AuthenticationError
6
6
  from gumloop.errors import GumloopError
7
7
  from gumloop.oauth import OAuth
8
8
 
9
- __version__ = "0.3.2"
9
+ __version__ = "0.3.3"
10
10
  __all__ = [
11
11
  "APIStatusError",
12
12
  "AsyncGumloop",
@@ -15,12 +15,14 @@ from gumloop.resources import Agents
15
15
  from gumloop.resources import Artifacts
16
16
  from gumloop.resources import AsyncAgents
17
17
  from gumloop.resources import AsyncArtifacts
18
+ from gumloop.resources import AsyncBrain
18
19
  from gumloop.resources import AsyncChat
19
20
  from gumloop.resources import AsyncMCP
20
21
  from gumloop.resources import AsyncModels
21
22
  from gumloop.resources import AsyncSessions
22
23
  from gumloop.resources import AsyncSkills
23
24
  from gumloop.resources import AsyncTeams
25
+ from gumloop.resources import Brain
24
26
  from gumloop.resources import Chat
25
27
  from gumloop.resources import Models
26
28
  from gumloop.resources import Sessions
@@ -41,9 +43,9 @@ def _derive_stream_base_url(base_url: str) -> str:
41
43
  class Gumloop:
42
44
  """Sync Gumloop SDK client. Composes an :class:`HttpClient` with the
43
45
  resource classes (``agents``, ``sessions``, ``mcp``, ``teams``,
44
- ``skills``, ``artifacts``, ``models``). The :attr:`oauth` attribute
45
- holds OAuth helpers — kept off the transport because it bootstraps the
46
- bearer token rather than consuming one."""
46
+ ``skills``, ``artifacts``, ``brain``, ``models``). The :attr:`oauth`
47
+ attribute holds OAuth helpers — kept off the transport because it
48
+ bootstraps the bearer token rather than consuming one."""
47
49
 
48
50
  def __init__(
49
51
  self,
@@ -85,6 +87,7 @@ class Gumloop:
85
87
  self.teams = Teams(self._http)
86
88
  self.skills = Skills(self._http)
87
89
  self.artifacts = Artifacts(self._http)
90
+ self.brain = Brain(self._http)
88
91
  self.oauth = OAuth(base_url=self.base_url, timeout=self.timeout)
89
92
 
90
93
  def close(self) -> None:
@@ -137,6 +140,7 @@ class AsyncGumloop:
137
140
  self.teams = AsyncTeams(self._http)
138
141
  self.skills = AsyncSkills(self._http)
139
142
  self.artifacts = AsyncArtifacts(self._http)
143
+ self.brain = AsyncBrain(self._http)
140
144
  self.oauth = OAuth(base_url=self.base_url, timeout=self.timeout)
141
145
 
142
146
  async def __aenter__(self) -> AsyncGumloop:
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from gumloop import GumloopError
8
+ from gumloop.cli.console import console
9
+ from gumloop.cli.console import print_json
10
+ from gumloop.cli.context import CliContext
11
+ from gumloop.cli.errors import exit_with_error
12
+
13
+ brain_app = typer.Typer(help="Search your Company Brain.", no_args_is_help=True, rich_markup_mode="rich")
14
+
15
+
16
+ @brain_app.command(
17
+ "search",
18
+ epilog=(
19
+ "Examples:\n"
20
+ ' gumloop brain search "onboarding process"\n'
21
+ ' gumloop brain search "pricing" --limit 5 --source notion --json'
22
+ ),
23
+ )
24
+ def search_brain(
25
+ ctx: typer.Context,
26
+ query: Annotated[str, typer.Argument(help="Search query.")],
27
+ limit: Annotated[
28
+ int | None,
29
+ typer.Option("--limit", help="Maximum number of results to return (1-50)."),
30
+ ] = None,
31
+ source: Annotated[
32
+ list[str] | None,
33
+ typer.Option("--source", help="Filter by source type (repeatable), e.g. notion, google_drive, slack."),
34
+ ] = None,
35
+ json_output: Annotated[
36
+ bool,
37
+ typer.Option("--json", help="Print the raw SDK response as JSON."),
38
+ ] = False,
39
+ ) -> None:
40
+ """Search indexed Company Brain sources."""
41
+ cli: CliContext = ctx.obj
42
+ try:
43
+ response = cli.call_with_refresh(
44
+ lambda client: client.brain.search(query, limit=limit, source_type=source or None)
45
+ )
46
+ except GumloopError as error:
47
+ exit_with_error(error, json_output=json_output)
48
+
49
+ if json_output:
50
+ print_json(response)
51
+ return
52
+
53
+ if not response.results:
54
+ console.print("No results found.")
55
+ return
56
+
57
+ console.print("SCORE", "SOURCE", "TITLE", "URL", sep="\t", soft_wrap=True)
58
+ for result in response.results:
59
+ console.print(
60
+ "" if result.score is None else f"{result.score:.3f}",
61
+ result.source or "",
62
+ result.title or "",
63
+ result.url or "",
64
+ sep="\t",
65
+ soft_wrap=True,
66
+ )
@@ -11,6 +11,7 @@ from gumloop.cli.commands.agents import agents_app
11
11
  from gumloop.cli.commands.artifacts import artifacts_app
12
12
  from gumloop.cli.commands.auth import login as login_command
13
13
  from gumloop.cli.commands.auth import logout as logout_command
14
+ from gumloop.cli.commands.brain import brain_app
14
15
  from gumloop.cli.commands.chat import chat_app
15
16
  from gumloop.cli.commands.mcp import mcp_app
16
17
  from gumloop.cli.commands.sessions import sessions_app
@@ -97,6 +98,7 @@ app.add_typer(sessions_app, name="sessions")
97
98
  app.add_typer(skills_app, name="skills")
98
99
  app.add_typer(artifacts_app, name="artifacts")
99
100
  app.add_typer(chat_app, name="chat")
101
+ app.add_typer(brain_app, name="brain")
100
102
 
101
103
 
102
104
  def _require_supported_platform() -> None:
@@ -6,6 +6,8 @@ from gumloop.resources.agents import AsyncModels
6
6
  from gumloop.resources.agents import Models
7
7
  from gumloop.resources.artifacts import Artifacts
8
8
  from gumloop.resources.artifacts import AsyncArtifacts
9
+ from gumloop.resources.brain import AsyncBrain
10
+ from gumloop.resources.brain import Brain
9
11
  from gumloop.resources.chat import AsyncChat
10
12
  from gumloop.resources.chat import Chat
11
13
  from gumloop.resources.mcp import MCP
@@ -23,12 +25,14 @@ __all__ = [
23
25
  "Artifacts",
24
26
  "AsyncAgents",
25
27
  "AsyncArtifacts",
28
+ "AsyncBrain",
26
29
  "AsyncChat",
27
30
  "AsyncMCP",
28
31
  "AsyncModels",
29
32
  "AsyncSessions",
30
33
  "AsyncSkills",
31
34
  "AsyncTeams",
35
+ "Brain",
32
36
  "Chat",
33
37
  "Models",
34
38
  "Sessions",
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from gumloop._http import AsyncHttpClient
6
+ from gumloop._http import HttpClient
7
+ from gumloop.types import BrainSearchRequest
8
+ from gumloop.types import BrainSearchResponse
9
+
10
+
11
+ class Brain:
12
+ def __init__(self, client: HttpClient) -> None:
13
+ self._client = client
14
+
15
+ def search(
16
+ self,
17
+ query: str,
18
+ *,
19
+ limit: int | None = None,
20
+ source_type: list[str] | None = None,
21
+ **kwargs: Any,
22
+ ) -> BrainSearchResponse:
23
+ return BrainSearchResponse.model_validate(
24
+ self._client.post(
25
+ "brain/search",
26
+ json=BrainSearchRequest.build(query=query, limit=limit, source_type=source_type, **kwargs),
27
+ )
28
+ )
29
+
30
+
31
+ class AsyncBrain:
32
+ def __init__(self, client: AsyncHttpClient) -> None:
33
+ self._client = client
34
+
35
+ async def search(
36
+ self,
37
+ query: str,
38
+ *,
39
+ limit: int | None = None,
40
+ source_type: list[str] | None = None,
41
+ **kwargs: Any,
42
+ ) -> BrainSearchResponse:
43
+ data = await self._client.post(
44
+ "brain/search",
45
+ json=BrainSearchRequest.build(query=query, limit=limit, source_type=source_type, **kwargs),
46
+ )
47
+ return BrainSearchResponse.model_validate(data)
@@ -236,6 +236,35 @@ class SkillDownloadResponse(_Model):
236
236
  major_version: int | None = None
237
237
 
238
238
 
239
+ # ---------------------------------------------------------------------------
240
+ # Brain types
241
+ # ---------------------------------------------------------------------------
242
+
243
+
244
+ class BrainSearchRequest(_Model):
245
+ query: str
246
+ limit: int | None = None
247
+ source_type: list[str] | None = Field(default=None, min_length=1)
248
+
249
+
250
+ class BrainSearchResult(_Model):
251
+ document_id: str | None = None
252
+ source: str | None = None
253
+ title: str | None = None
254
+ content: str | None = None
255
+ url: str | None = None
256
+ score: float | None = None
257
+ updated_at: str | None = None
258
+ owner_name: str | None = None
259
+ owner_email: str | None = None
260
+ parent_title: str | None = None
261
+ metadata: dict[str, Any] = Field(default_factory=dict)
262
+
263
+
264
+ class BrainSearchResponse(_Model):
265
+ results: list[BrainSearchResult] = Field(default_factory=list)
266
+
267
+
239
268
  # ---------------------------------------------------------------------------
240
269
  # Artifact types
241
270
  # ---------------------------------------------------------------------------
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import httpx
6
+ import respx
7
+ from typer.testing import CliRunner
8
+
9
+ from gumloop.cli.credentials import Credentials
10
+ from gumloop.cli.credentials import save_credentials
11
+ from gumloop.cli.main import app
12
+ from tests.sdk.helpers import API_BASE
13
+
14
+ _RESULT = {
15
+ "document_id": "notion:doc_1",
16
+ "source": "notion",
17
+ "title": "Onboarding",
18
+ "content": "How we onboard new teammates.",
19
+ "url": "https://notion.so/doc_1",
20
+ "score": 0.87,
21
+ }
22
+
23
+
24
+ @respx.mock
25
+ def test_brain_search_forwards_query_limit_and_sources(cli_runner: CliRunner) -> None:
26
+ route = respx.post(f"{API_BASE}/brain/search").mock(return_value=httpx.Response(200, json={"results": []}))
27
+ save_credentials(Credentials(api_key="key"))
28
+
29
+ result = cli_runner.invoke(
30
+ app,
31
+ ["brain", "search", "onboarding", "--limit", "5", "--source", "notion", "--source", "slack", "--json"],
32
+ )
33
+
34
+ assert result.exit_code == 0, result.output
35
+ body = json.loads(route.calls[0].request.content)
36
+ assert body == {"query": "onboarding", "limit": 5, "source_type": ["notion", "slack"]}
37
+
38
+
39
+ @respx.mock
40
+ def test_brain_search_renders_result_table(cli_runner: CliRunner) -> None:
41
+ respx.post(f"{API_BASE}/brain/search").mock(return_value=httpx.Response(200, json={"results": [_RESULT]}))
42
+ save_credentials(Credentials(api_key="key"))
43
+
44
+ result = cli_runner.invoke(app, ["brain", "search", "onboarding"])
45
+
46
+ assert result.exit_code == 0, result.output
47
+ assert "Onboarding" in result.output
48
+ assert "notion" in result.output
49
+ assert "0.870" in result.output
50
+
51
+
52
+ @respx.mock
53
+ def test_brain_search_prints_empty_state(cli_runner: CliRunner) -> None:
54
+ respx.post(f"{API_BASE}/brain/search").mock(return_value=httpx.Response(200, json={"results": []}))
55
+ save_credentials(Credentials(api_key="key"))
56
+
57
+ result = cli_runner.invoke(app, ["brain", "search", "nothing"])
58
+
59
+ assert result.exit_code == 0, result.output
60
+ assert "No results found." in result.output
61
+
62
+
63
+ @respx.mock
64
+ def test_brain_search_surfaces_api_errors(cli_runner: CliRunner) -> None:
65
+ respx.post(f"{API_BASE}/brain/search").mock(
66
+ return_value=httpx.Response(
67
+ 402,
68
+ json={
69
+ "error": {
70
+ "code": "credit_limit_exceeded",
71
+ "message": "Credit limit exceeded.",
72
+ "type": "payment_required_error",
73
+ }
74
+ },
75
+ )
76
+ )
77
+ save_credentials(Credentials(api_key="key"))
78
+
79
+ result = cli_runner.invoke(app, ["brain", "search", "q", "--json"])
80
+
81
+ assert result.exit_code != 0
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+
5
+ import httpx
6
+ import pytest
7
+ import respx
8
+ from pydantic import ValidationError
9
+
10
+ from gumloop import AsyncGumloop
11
+ from gumloop import Gumloop
12
+ from tests.sdk.helpers import API_BASE
13
+ from tests.sdk.helpers import request_json
14
+
15
+ _RESULT = {
16
+ "document_id": "notion:doc_1",
17
+ "source": "notion",
18
+ "title": "Onboarding",
19
+ "content": "How we onboard new teammates.",
20
+ "url": "https://notion.so/doc_1",
21
+ "score": 0.87,
22
+ "updated_at": "2026-01-02T03:04:05+00:00",
23
+ "owner_name": "Ada",
24
+ }
25
+
26
+
27
+ @respx.mock
28
+ def test_brain_search_posts_query_and_drops_unset_fields(client: Gumloop) -> None:
29
+ route = respx.post(f"{API_BASE}/brain/search").mock(return_value=httpx.Response(200, json={"results": [_RESULT]}))
30
+
31
+ result = client.brain.search("onboarding")
32
+
33
+ assert request_json(route.calls[0].request) == {"query": "onboarding"}
34
+ assert len(result.results) == 1
35
+ assert result.results[0].title == "Onboarding"
36
+ assert result.results[0].source == "notion"
37
+ assert result.results[0].score == 0.87
38
+
39
+
40
+ @respx.mock
41
+ def test_brain_search_forwards_limit_and_source_type(client: Gumloop) -> None:
42
+ route = respx.post(f"{API_BASE}/brain/search").mock(return_value=httpx.Response(200, json={"results": []}))
43
+
44
+ result = client.brain.search("pricing", limit=5, source_type=["notion", "slack"])
45
+
46
+ assert request_json(route.calls[0].request) == {
47
+ "query": "pricing",
48
+ "limit": 5,
49
+ "source_type": ["notion", "slack"],
50
+ }
51
+ assert result.results == []
52
+
53
+
54
+ def test_brain_search_rejects_empty_source_type(client: Gumloop) -> None:
55
+ with pytest.raises(ValidationError):
56
+ client.brain.search("pricing", source_type=[])
57
+
58
+
59
+ @respx.mock
60
+ def test_brain_search_passes_through_unknown_kwargs(client: Gumloop) -> None:
61
+ route = respx.post(f"{API_BASE}/brain/search").mock(return_value=httpx.Response(200, json={"results": []}))
62
+
63
+ client.brain.search("q", future_param="x")
64
+
65
+ assert request_json(route.calls[0].request) == {"query": "q", "future_param": "x"}
66
+
67
+
68
+ @respx.mock
69
+ def test_async_brain_search_mirrors_sync(async_client: AsyncGumloop) -> None:
70
+ route = respx.post(f"{API_BASE}/brain/search").mock(return_value=httpx.Response(200, json={"results": [_RESULT]}))
71
+
72
+ result = asyncio.run(async_client.brain.search("onboarding", limit=3))
73
+
74
+ assert request_json(route.calls[0].request) == {"query": "onboarding", "limit": 3}
75
+ assert result.results[0].document_id == "notion:doc_1"
@@ -355,7 +355,7 @@ wheels = [
355
355
 
356
356
  [[package]]
357
357
  name = "gumloop"
358
- version = "0.3.2"
358
+ version = "0.3.3"
359
359
  source = { editable = "." }
360
360
  dependencies = [
361
361
  { name = "httpx" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes