gopher-mcp-python 0.1.16__tar.gz → 0.1.23__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 (37) hide show
  1. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/PKG-INFO +1 -1
  2. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/__init__.py +3 -2
  3. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/agent.py +157 -1
  4. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/config.py +11 -1
  5. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/library.py +148 -0
  6. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/PKG-INFO +1 -1
  7. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/SOURCES.txt +1 -0
  8. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/pyproject.toml +1 -1
  9. gopher_mcp_python-0.1.23/tests/test_agent_create_by.py +90 -0
  10. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/LICENSE +0 -0
  11. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/README.md +0 -0
  12. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/__init__.py +0 -0
  13. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/errors.py +0 -0
  14. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/gopher_auth.py +0 -0
  15. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/auth/scope_helpers.py +0 -0
  16. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/errors.py +0 -0
  17. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/__init__.py +0 -0
  18. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/__init__.py +0 -0
  19. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/auth_client.py +0 -0
  20. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/auto_refresh.py +0 -0
  21. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/config_loader.py +0 -0
  22. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/loader.py +0 -0
  23. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/oauth_client.py +0 -0
  24. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/session_manager.py +0 -0
  25. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/types.py +0 -0
  26. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/ffi/auth/validation_options.py +0 -0
  27. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/result.py +0 -0
  28. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python/server_config.py +0 -0
  29. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/dependency_links.txt +0 -0
  30. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/requires.txt +0 -0
  31. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/gopher_mcp_python.egg-info/top_level.txt +0 -0
  32. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/setup.cfg +0 -0
  33. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/setup.py +0 -0
  34. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_config.py +0 -0
  35. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_ffi.py +0 -0
  36. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_gopher_auth.py +0 -0
  37. {gopher_mcp_python-0.1.16 → gopher_mcp_python-0.1.23}/tests/test_result.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gopher-mcp-python
3
- Version: 0.1.16
3
+ Version: 0.1.23
4
4
  Summary: Python SDK for Gopher MCP - AI Agent orchestration framework with native performance
5
5
  Author-email: Gopher Security <dev@gophersecurity.com>
6
6
  License: Apache-2.0
@@ -32,7 +32,7 @@ from gopher_mcp_python.errors import (
32
32
  TimeoutError,
33
33
  )
34
34
  from gopher_mcp_python.server_config import ServerConfig
35
- from gopher_mcp_python.ffi import GopherOrchLibrary
35
+ from gopher_mcp_python.ffi import GopherOrchLibrary, GopherOrchHandle
36
36
 
37
37
  # Auth module re-exports
38
38
  from gopher_mcp_python.ffi.auth import (
@@ -50,7 +50,7 @@ from gopher_mcp_python.ffi.auth import (
50
50
  is_auth_available,
51
51
  )
52
52
 
53
- __version__ = "0.1.16"
53
+ __version__ = "0.1.23"
54
54
 
55
55
  __all__ = [
56
56
  # Main classes
@@ -68,6 +68,7 @@ __all__ = [
68
68
  "TimeoutError",
69
69
  # FFI
70
70
  "GopherOrchLibrary",
71
+ "GopherOrchHandle",
71
72
  # Auth
72
73
  "GopherAuthError",
73
74
  "ValidationResult",
@@ -27,7 +27,7 @@ Example with context manager:
27
27
  """
28
28
 
29
29
  import atexit
30
- from typing import Optional
30
+ from typing import Callable, Optional
31
31
 
32
32
  from gopher_mcp_python.config import GopherAgentConfig
33
33
  from gopher_mcp_python.result import AgentResult, AgentResultStatus
@@ -183,6 +183,162 @@ class GopherAgent:
183
183
  .build()
184
184
  )
185
185
 
186
+ @staticmethod
187
+ def create_with_server_id(
188
+ provider: str, model: str, api_key: str, server_id: str
189
+ ) -> "GopherAgent":
190
+ """
191
+ Create a new GopherAgent scoped to a single MCP server by id.
192
+
193
+ Fetches server config from the Gopher API using the Bearer api key,
194
+ appending "?serverId={server_id}" so the response carries only the
195
+ matching MCP server entry.
196
+
197
+ Args:
198
+ provider: Provider name (e.g., "AnthropicProvider")
199
+ model: Model identifier accepted by the chosen provider
200
+ api_key: Gopher API key
201
+ server_id: MCP server id to scope the agent to
202
+
203
+ Returns:
204
+ GopherAgent instance
205
+ """
206
+ return GopherAgent._create_from_ffi(
207
+ lambda lib: lib.agent_create_by_server_id(
208
+ provider, model, api_key, server_id
209
+ )
210
+ )
211
+
212
+ @staticmethod
213
+ def create_with_server_name(
214
+ provider: str, model: str, api_key: str, server_name: str
215
+ ) -> "GopherAgent":
216
+ """
217
+ Create a new GopherAgent scoped to a single MCP server by name.
218
+
219
+ Fetches server config from the Gopher API using the Bearer api key,
220
+ appending "?serverName={server_name}" so the response carries only
221
+ the matching MCP server entry.
222
+
223
+ Args:
224
+ provider: Provider name (e.g., "AnthropicProvider")
225
+ model: Model identifier accepted by the chosen provider
226
+ api_key: Gopher API key
227
+ server_name: MCP server name to scope the agent to
228
+
229
+ Returns:
230
+ GopherAgent instance
231
+ """
232
+ return GopherAgent._create_from_ffi(
233
+ lambda lib: lib.agent_create_by_server_name(
234
+ provider, model, api_key, server_name
235
+ )
236
+ )
237
+
238
+ @staticmethod
239
+ def create_with_gateway_id(
240
+ provider: str, model: str, api_key: str, gateway_id: str
241
+ ) -> "GopherAgent":
242
+ """
243
+ Create a new GopherAgent scoped to a single MCP gateway by id.
244
+
245
+ Fetches server config from the Gopher API using the Bearer api key,
246
+ appending "?gatewayId={gateway_id}" so the response carries the
247
+ backing MCP servers for that gateway.
248
+
249
+ Args:
250
+ provider: Provider name (e.g., "AnthropicProvider")
251
+ model: Model identifier accepted by the chosen provider
252
+ api_key: Gopher API key
253
+ gateway_id: MCP gateway id to scope the agent to
254
+
255
+ Returns:
256
+ GopherAgent instance
257
+ """
258
+ return GopherAgent._create_from_ffi(
259
+ lambda lib: lib.agent_create_by_gateway_id(
260
+ provider, model, api_key, gateway_id
261
+ )
262
+ )
263
+
264
+ @staticmethod
265
+ def create_with_gateway_name(
266
+ provider: str, model: str, api_key: str, gateway_name: str
267
+ ) -> "GopherAgent":
268
+ """
269
+ Create a new GopherAgent scoped to a single MCP gateway by name.
270
+
271
+ Fetches server config from the Gopher API using the Bearer api key,
272
+ appending "?gatewayName={gateway_name}" so the response carries the
273
+ backing MCP servers for that gateway.
274
+
275
+ Args:
276
+ provider: Provider name (e.g., "AnthropicProvider")
277
+ model: Model identifier accepted by the chosen provider
278
+ api_key: Gopher API key
279
+ gateway_name: MCP gateway name to scope the agent to
280
+
281
+ Returns:
282
+ GopherAgent instance
283
+ """
284
+ return GopherAgent._create_from_ffi(
285
+ lambda lib: lib.agent_create_by_gateway_name(
286
+ provider, model, api_key, gateway_name
287
+ )
288
+ )
289
+
290
+ @staticmethod
291
+ def create_with_url(provider: str, model: str, url: str) -> "GopherAgent":
292
+ """
293
+ Create a new GopherAgent for a single MCP server reachable at a URL.
294
+
295
+ Skips the remote config fetch entirely: synthesises an http_sse
296
+ server entry around the URL and delegates to create_by_json on the
297
+ native side. Useful for local development or one-off endpoints where
298
+ the operator already knows the URL.
299
+
300
+ Args:
301
+ provider: Provider name (e.g., "AnthropicProvider")
302
+ model: Model identifier accepted by the chosen provider
303
+ url: Full URL of the MCP server (e.g., "http://127.0.0.1:8080/mcp")
304
+
305
+ Returns:
306
+ GopherAgent instance
307
+ """
308
+ return GopherAgent._create_from_ffi(
309
+ lambda lib: lib.agent_create_by_url(provider, model, url)
310
+ )
311
+
312
+ @staticmethod
313
+ def _create_from_ffi(
314
+ create_handle: Callable[[GopherOrchLibrary], Optional[GopherOrchHandle]],
315
+ ) -> "GopherAgent":
316
+ """
317
+ Shared handle-creation pump for factories that bypass GopherAgentConfig.
318
+
319
+ Ensures the native library is initialised, invokes the supplied FFI
320
+ callable, and translates a null handle return into AgentError using
321
+ the same last_error / clear_error contract as create().
322
+ """
323
+ if not _initialized:
324
+ GopherAgent.init()
325
+
326
+ lib = GopherOrchLibrary.get_instance()
327
+ if lib is None:
328
+ raise AgentError("Native library not available")
329
+
330
+ try:
331
+ handle = create_handle(lib)
332
+ except Exception as e:
333
+ raise AgentError(f"Failed to create agent: {e}")
334
+
335
+ if handle is None:
336
+ error = lib.get_last_error_message()
337
+ lib.clear_error()
338
+ raise AgentError(error or "Failed to create agent")
339
+
340
+ return GopherAgent(handle)
341
+
186
342
  def run(self, query: str, timeout_ms: int = 60000) -> str:
187
343
  """
188
344
  Run a query against the agent.
@@ -9,10 +9,20 @@ from typing import Optional
9
9
 
10
10
  class GopherAgentConfig:
11
11
  """
12
- Immutable configuration for GopherAgent.
12
+ Immutable configuration for GopherAgent created via GopherAgent.create().
13
13
 
14
14
  Use the builder() method to create configurations.
15
15
 
16
+ The builder accepts only the api_key / server_config XOR that maps to
17
+ the original gopher_orch_agent_create_by_api_key and
18
+ gopher_orch_agent_create_by_json C entry points. The five newer routing
19
+ factories (GopherAgent.create_with_server_id, create_with_server_name,
20
+ create_with_gateway_id, create_with_gateway_name, create_with_url) take
21
+ additional inputs (server / gateway identifier, or URL) that do not fit
22
+ that XOR shape and deliberately bypass this builder; they are exposed
23
+ as static methods on GopherAgent and dispatch into GopherOrchLibrary
24
+ directly via GopherAgent._create_from_ffi.
25
+
16
26
  Example:
17
27
  >>> config = (GopherAgentConfig.builder()
18
28
  ... .provider("AnthropicProvider")
@@ -134,6 +134,54 @@ class GopherOrchLibrary:
134
134
  ]
135
135
  self._lib.gopher_orch_agent_create_by_api_key.restype = c_void_p
136
136
 
137
+ # Routing factories: scope the agent to a single MCP server or gateway
138
+ # selected by id / name, or to a known MCP URL. These C symbols landed
139
+ # in gopher-orch 0.1.23 -- wrapped in try / except so the SDK still
140
+ # loads against an older libgopher-orch, with the higher-level
141
+ # factories raising AgentError at call time if the symbol is missing.
142
+ try:
143
+ self._lib.gopher_orch_agent_create_by_server_id.argtypes = [
144
+ c_char_p,
145
+ c_char_p,
146
+ c_char_p,
147
+ c_char_p,
148
+ ]
149
+ self._lib.gopher_orch_agent_create_by_server_id.restype = c_void_p
150
+
151
+ self._lib.gopher_orch_agent_create_by_server_name.argtypes = [
152
+ c_char_p,
153
+ c_char_p,
154
+ c_char_p,
155
+ c_char_p,
156
+ ]
157
+ self._lib.gopher_orch_agent_create_by_server_name.restype = c_void_p
158
+
159
+ self._lib.gopher_orch_agent_create_by_gateway_id.argtypes = [
160
+ c_char_p,
161
+ c_char_p,
162
+ c_char_p,
163
+ c_char_p,
164
+ ]
165
+ self._lib.gopher_orch_agent_create_by_gateway_id.restype = c_void_p
166
+
167
+ self._lib.gopher_orch_agent_create_by_gateway_name.argtypes = [
168
+ c_char_p,
169
+ c_char_p,
170
+ c_char_p,
171
+ c_char_p,
172
+ ]
173
+ self._lib.gopher_orch_agent_create_by_gateway_name.restype = c_void_p
174
+
175
+ self._lib.gopher_orch_agent_create_by_url.argtypes = [
176
+ c_char_p,
177
+ c_char_p,
178
+ c_char_p,
179
+ ]
180
+ self._lib.gopher_orch_agent_create_by_url.restype = c_void_p
181
+ except AttributeError:
182
+ # Older libgopher-orch builds (< 0.1.23) lack these symbols.
183
+ pass
184
+
137
185
  self._lib.gopher_orch_agent_run.argtypes = [c_void_p, c_char_p, c_int64]
138
186
  self._lib.gopher_orch_agent_run.restype = c_char_p
139
187
 
@@ -287,6 +335,106 @@ class GopherOrchLibrary:
287
335
  api_key.encode("utf-8"),
288
336
  )
289
337
 
338
+ def agent_create_by_server_id(
339
+ self, provider: str, model: str, api_key: str, server_id: str
340
+ ) -> Optional[GopherOrchHandle]:
341
+ """Create an agent scoped to a single MCP server by id.
342
+
343
+ The native side fetches server config from the Gopher API using the
344
+ Bearer api key, appending "?serverId={server_id}" so the response
345
+ carries only the matching MCP server entry.
346
+ """
347
+ if not self._available or self._lib is None:
348
+ return None
349
+ fn = getattr(self._lib, "gopher_orch_agent_create_by_server_id", None)
350
+ if fn is None:
351
+ return None
352
+ return fn(
353
+ provider.encode("utf-8"),
354
+ model.encode("utf-8"),
355
+ api_key.encode("utf-8"),
356
+ server_id.encode("utf-8"),
357
+ )
358
+
359
+ def agent_create_by_server_name(
360
+ self, provider: str, model: str, api_key: str, server_name: str
361
+ ) -> Optional[GopherOrchHandle]:
362
+ """Create an agent scoped to a single MCP server by name.
363
+
364
+ Mirrors agent_create_by_server_id but routes via "?serverName=".
365
+ """
366
+ if not self._available or self._lib is None:
367
+ return None
368
+ fn = getattr(self._lib, "gopher_orch_agent_create_by_server_name", None)
369
+ if fn is None:
370
+ return None
371
+ return fn(
372
+ provider.encode("utf-8"),
373
+ model.encode("utf-8"),
374
+ api_key.encode("utf-8"),
375
+ server_name.encode("utf-8"),
376
+ )
377
+
378
+ def agent_create_by_gateway_id(
379
+ self, provider: str, model: str, api_key: str, gateway_id: str
380
+ ) -> Optional[GopherOrchHandle]:
381
+ """Create an agent scoped to a single MCP gateway by id.
382
+
383
+ The native side appends "?gatewayId={gateway_id}" to the Gopher API
384
+ fetch so the response carries the backing MCP servers for that
385
+ gateway.
386
+ """
387
+ if not self._available or self._lib is None:
388
+ return None
389
+ fn = getattr(self._lib, "gopher_orch_agent_create_by_gateway_id", None)
390
+ if fn is None:
391
+ return None
392
+ return fn(
393
+ provider.encode("utf-8"),
394
+ model.encode("utf-8"),
395
+ api_key.encode("utf-8"),
396
+ gateway_id.encode("utf-8"),
397
+ )
398
+
399
+ def agent_create_by_gateway_name(
400
+ self, provider: str, model: str, api_key: str, gateway_name: str
401
+ ) -> Optional[GopherOrchHandle]:
402
+ """Create an agent scoped to a single MCP gateway by name.
403
+
404
+ Mirrors agent_create_by_gateway_id but routes via "?gatewayName=".
405
+ """
406
+ if not self._available or self._lib is None:
407
+ return None
408
+ fn = getattr(self._lib, "gopher_orch_agent_create_by_gateway_name", None)
409
+ if fn is None:
410
+ return None
411
+ return fn(
412
+ provider.encode("utf-8"),
413
+ model.encode("utf-8"),
414
+ api_key.encode("utf-8"),
415
+ gateway_name.encode("utf-8"),
416
+ )
417
+
418
+ def agent_create_by_url(
419
+ self, provider: str, model: str, url: str
420
+ ) -> Optional[GopherOrchHandle]:
421
+ """Create an agent for a single MCP server reachable at a URL.
422
+
423
+ Skips the remote config fetch: the native side synthesises an
424
+ http_sse server entry around the URL. Useful for local development
425
+ or one-off endpoints where the operator already knows the URL.
426
+ """
427
+ if not self._available or self._lib is None:
428
+ return None
429
+ fn = getattr(self._lib, "gopher_orch_agent_create_by_url", None)
430
+ if fn is None:
431
+ return None
432
+ return fn(
433
+ provider.encode("utf-8"),
434
+ model.encode("utf-8"),
435
+ url.encode("utf-8"),
436
+ )
437
+
290
438
  def agent_run(
291
439
  self, agent: GopherOrchHandle, query: str, timeout_ms: int
292
440
  ) -> Optional[str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gopher-mcp-python
3
- Version: 0.1.16
3
+ Version: 0.1.23
4
4
  Summary: Python SDK for Gopher MCP - AI Agent orchestration framework with native performance
5
5
  Author-email: Gopher Security <dev@gophersecurity.com>
6
6
  License: Apache-2.0
@@ -28,6 +28,7 @@ gopher_mcp_python/ffi/auth/oauth_client.py
28
28
  gopher_mcp_python/ffi/auth/session_manager.py
29
29
  gopher_mcp_python/ffi/auth/types.py
30
30
  gopher_mcp_python/ffi/auth/validation_options.py
31
+ tests/test_agent_create_by.py
31
32
  tests/test_config.py
32
33
  tests/test_ffi.py
33
34
  tests/test_gopher_auth.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gopher-mcp-python"
7
- version = "0.1.16"
7
+ version = "0.1.23"
8
8
  description = "Python SDK for Gopher MCP - AI Agent orchestration framework with native performance"
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -0,0 +1,90 @@
1
+ """
2
+ Contract tests for the five routing factories on GopherAgent.
3
+
4
+ Mirrors the failure-path test set in
5
+ gopher-orch/tests/gopher/orch/agent_create_by_test.cc which locks down
6
+ the nullptr-on-failure contract that the C FFI surfaces here as
7
+ AgentError. Happy-path coverage needs a stubbed HTTP listener capturing
8
+ the /v1/mcp-servers query string with the camelCase routing keys
9
+ (serverId / serverName / gatewayId / gatewayName); that infrastructure
10
+ is tracked separately, same as on the C++ side.
11
+ """
12
+
13
+ import pytest
14
+
15
+ from gopher_mcp_python import AgentError, GopherAgent
16
+ from gopher_mcp_python.ffi import GopherOrchLibrary
17
+
18
+ PROVIDER = "AnthropicProvider"
19
+ MODEL = "test-model"
20
+ BAD_PROVIDER = "NotARealProvider"
21
+ URL = "http://127.0.0.1:8080/mcp"
22
+
23
+
24
+ pytestmark = pytest.mark.skipif(
25
+ not GopherOrchLibrary.is_available(),
26
+ reason="Native library not available -- run ./build.sh first",
27
+ )
28
+
29
+
30
+ class TestRoutingFactoryContracts:
31
+ """Failure-path contract for the five routing factories on GopherAgent."""
32
+
33
+ # ----------------------------------------------------------------
34
+ # Empty api key. fetch_mcp_servers throws on the native side; the
35
+ # factory must surface that as AgentError rather than returning a
36
+ # partially-constructed agent or a null handle leaking through.
37
+ # ----------------------------------------------------------------
38
+
39
+ def test_create_with_server_id_rejects_empty_api_key(self) -> None:
40
+ with pytest.raises(AgentError):
41
+ GopherAgent.create_with_server_id(PROVIDER, MODEL, "", "srv-1")
42
+
43
+ def test_create_with_server_name_rejects_empty_api_key(self) -> None:
44
+ with pytest.raises(AgentError):
45
+ GopherAgent.create_with_server_name(
46
+ PROVIDER, MODEL, "", "my-server"
47
+ )
48
+
49
+ def test_create_with_gateway_id_rejects_empty_api_key(self) -> None:
50
+ with pytest.raises(AgentError):
51
+ GopherAgent.create_with_gateway_id(PROVIDER, MODEL, "", "gw-1")
52
+
53
+ def test_create_with_gateway_name_rejects_empty_api_key(self) -> None:
54
+ with pytest.raises(AgentError):
55
+ GopherAgent.create_with_gateway_name(
56
+ PROVIDER, MODEL, "", "my-gateway"
57
+ )
58
+
59
+ # ----------------------------------------------------------------
60
+ # create_with_url rejects empty url before any FFI work happens.
61
+ # Mirrors the CreateByUrlRejectsEmptyUrl case in the C++ suite.
62
+ # ----------------------------------------------------------------
63
+
64
+ def test_create_with_url_rejects_empty_url(self) -> None:
65
+ with pytest.raises(AgentError):
66
+ GopherAgent.create_with_url(PROVIDER, MODEL, "")
67
+
68
+ # ----------------------------------------------------------------
69
+ # Unknown provider. create_with_url synthesises a local http_sse
70
+ # config and reaches create_by_json on the native side, which
71
+ # rejects an unknown provider name. The factory must surface that
72
+ # as AgentError.
73
+ # ----------------------------------------------------------------
74
+
75
+ def test_create_with_url_rejects_unknown_provider(self) -> None:
76
+ with pytest.raises(AgentError):
77
+ GopherAgent.create_with_url(BAD_PROVIDER, MODEL, URL)
78
+
79
+ # ----------------------------------------------------------------
80
+ # AgentError surfaces a non-empty message so SDK consumers can log
81
+ # a meaningful diagnostic; the C side fills last_error() and the
82
+ # wrapper pump should propagate it through. A future change to the
83
+ # error pump that swallows the underlying C diagnostic gets caught
84
+ # here immediately.
85
+ # ----------------------------------------------------------------
86
+
87
+ def test_create_with_server_id_surfaces_non_empty_message(self) -> None:
88
+ with pytest.raises(AgentError) as exc_info:
89
+ GopherAgent.create_with_server_id(PROVIDER, MODEL, "", "srv-1")
90
+ assert len(str(exc_info.value)) > 0