argo-proxy 3.1.1__tar.gz → 3.2.0a0__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 (48) hide show
  1. {argo_proxy-3.1.1/src/argo_proxy.egg-info → argo_proxy-3.2.0a0}/PKG-INFO +5 -5
  2. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/pyproject.toml +4 -4
  3. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0/src/argo_proxy.egg-info}/PKG-INFO +5 -5
  4. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/SOURCES.txt +2 -2
  5. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/requires.txt +4 -4
  6. argo_proxy-3.2.0a0/src/argoproxy/__init__.py +1 -0
  7. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/app.py +65 -3
  8. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/handlers.py +21 -2
  9. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/parser.py +11 -0
  10. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/io.py +15 -1
  11. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/model.py +5 -0
  12. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/dispatch.py +136 -428
  13. argo_proxy-3.2.0a0/tests/test_unix_socket.py +179 -0
  14. argo_proxy-3.1.1/src/argoproxy/__init__.py +0 -1
  15. argo_proxy-3.1.1/src/argoproxy/utils/tool_calls.py +0 -263
  16. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/LICENSE +0 -0
  17. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/README.md +0 -0
  18. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/setup.cfg +0 -0
  19. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/dependency_links.txt +0 -0
  20. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/entry_points.txt +0 -0
  21. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argo_proxy.egg-info/top_level.txt +0 -0
  22. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/_vendor/__init__.py +0 -0
  23. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/_vendor/semver.py +0 -0
  24. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/_vendor/yaml.py +0 -0
  25. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/__init__.py +0 -0
  26. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/cli/display.py +0 -0
  27. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/__init__.py +0 -0
  28. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/interactive.py +0 -0
  29. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/config/validation.py +0 -0
  30. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/dev_proxy.py +0 -0
  31. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/extras.py +0 -0
  32. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/endpoints/passthrough.py +0 -0
  33. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/models.py +0 -0
  34. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/performance.py +0 -0
  35. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/py.typed +0 -0
  36. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/attack_logger.py +0 -0
  37. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/image_processing.py +0 -0
  38. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/logging.py +0 -0
  39. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/misc.py +0 -0
  40. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/models.py +0 -0
  41. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/src/argoproxy/utils/transports.py +0 -0
  42. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_chat_completions.py +0 -0
  43. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_embeddings.py +0 -0
  44. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_function_calling_multiple.py +0 -0
  45. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_function_calling_single.py +0 -0
  46. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_leaked_tool_parser.py +0 -0
  47. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/test/test_model_resolution.py +0 -0
  48. {argo_proxy-3.1.1 → argo_proxy-3.2.0a0}/tests/test_config_migrate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: argo-proxy
3
- Version: 3.1.1
3
+ Version: 3.2.0a0
4
4
  Summary: Proxy server to Argo API, OpenAI format compatible
5
5
  Author-email: Peng Ding <oaklight@gmx.com>
6
6
  License-Expression: MIT
@@ -15,7 +15,7 @@ Requires-Python: >=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: aiohttp>=3.12.2
18
- Requires-Dist: llm-rosetta<0.7.0,>=0.6.8
18
+ Requires-Dist: llm-rosetta>=0.7.0a0
19
19
  Requires-Dist: pydantic>=2.11.7
20
20
  Requires-Dist: tiktoken>=0.9.0
21
21
  Requires-Dist: tqdm>=4.67.1
@@ -23,9 +23,9 @@ Requires-Dist: Pillow>=12.0.0
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: dotenv>=0.9.9; extra == "dev"
25
25
  Requires-Dist: openai>=1.79.0; extra == "dev"
26
- Requires-Dist: ruff>=0.15.0; extra == "dev"
27
- Requires-Dist: ty>=0.0.31; extra == "dev"
28
- Requires-Dist: complexipy>=5.2.0; extra == "dev"
26
+ Requires-Dist: ruff==0.15.20; extra == "dev"
27
+ Requires-Dist: ty==0.0.54; extra == "dev"
28
+ Requires-Dist: complexipy==5.6.1; extra == "dev"
29
29
  Requires-Dist: build>=1.2.2.post1; extra == "dev"
30
30
  Requires-Dist: twine>=6.1.0; extra == "dev"
31
31
  Requires-Dist: httpx>=0.28.1; extra == "dev"
@@ -20,7 +20,7 @@ classifiers = [
20
20
 
21
21
  dependencies = [
22
22
  "aiohttp>=3.12.2",
23
- "llm-rosetta>=0.6.8,<0.7.0",
23
+ "llm-rosetta>=0.7.0a0",
24
24
  "pydantic>=2.11.7",
25
25
  "tiktoken>=0.9.0",
26
26
  "tqdm>=4.67.1",
@@ -31,9 +31,9 @@ dependencies = [
31
31
  dev = [
32
32
  "dotenv>=0.9.9",
33
33
  "openai>=1.79.0",
34
- "ruff>=0.15.0",
35
- "ty>=0.0.31",
36
- "complexipy>=5.2.0",
34
+ "ruff==0.15.20",
35
+ "ty==0.0.54",
36
+ "complexipy==5.6.1",
37
37
  "build>=1.2.2.post1",
38
38
  "twine>=6.1.0",
39
39
  "httpx>=0.28.1",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: argo-proxy
3
- Version: 3.1.1
3
+ Version: 3.2.0a0
4
4
  Summary: Proxy server to Argo API, OpenAI format compatible
5
5
  Author-email: Peng Ding <oaklight@gmx.com>
6
6
  License-Expression: MIT
@@ -15,7 +15,7 @@ Requires-Python: >=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: aiohttp>=3.12.2
18
- Requires-Dist: llm-rosetta<0.7.0,>=0.6.8
18
+ Requires-Dist: llm-rosetta>=0.7.0a0
19
19
  Requires-Dist: pydantic>=2.11.7
20
20
  Requires-Dist: tiktoken>=0.9.0
21
21
  Requires-Dist: tqdm>=4.67.1
@@ -23,9 +23,9 @@ Requires-Dist: Pillow>=12.0.0
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: dotenv>=0.9.9; extra == "dev"
25
25
  Requires-Dist: openai>=1.79.0; extra == "dev"
26
- Requires-Dist: ruff>=0.15.0; extra == "dev"
27
- Requires-Dist: ty>=0.0.31; extra == "dev"
28
- Requires-Dist: complexipy>=5.2.0; extra == "dev"
26
+ Requires-Dist: ruff==0.15.20; extra == "dev"
27
+ Requires-Dist: ty==0.0.54; extra == "dev"
28
+ Requires-Dist: complexipy==5.6.1; extra == "dev"
29
29
  Requires-Dist: build>=1.2.2.post1; extra == "dev"
30
30
  Requires-Dist: twine>=6.1.0; extra == "dev"
31
31
  Requires-Dist: httpx>=0.28.1; extra == "dev"
@@ -33,7 +33,6 @@ src/argoproxy/utils/image_processing.py
33
33
  src/argoproxy/utils/logging.py
34
34
  src/argoproxy/utils/misc.py
35
35
  src/argoproxy/utils/models.py
36
- src/argoproxy/utils/tool_calls.py
37
36
  src/argoproxy/utils/transports.py
38
37
  test/test_chat_completions.py
39
38
  test/test_embeddings.py
@@ -41,4 +40,5 @@ test/test_function_calling_multiple.py
41
40
  test/test_function_calling_single.py
42
41
  test/test_leaked_tool_parser.py
43
42
  test/test_model_resolution.py
44
- tests/test_config_migrate.py
43
+ tests/test_config_migrate.py
44
+ tests/test_unix_socket.py
@@ -1,5 +1,5 @@
1
1
  aiohttp>=3.12.2
2
- llm-rosetta<0.7.0,>=0.6.8
2
+ llm-rosetta>=0.7.0a0
3
3
  pydantic>=2.11.7
4
4
  tiktoken>=0.9.0
5
5
  tqdm>=4.67.1
@@ -11,9 +11,9 @@ argo-proxy[dev,test]
11
11
  [dev]
12
12
  dotenv>=0.9.9
13
13
  openai>=1.79.0
14
- ruff>=0.15.0
15
- ty>=0.0.31
16
- complexipy>=5.2.0
14
+ ruff==0.15.20
15
+ ty==0.0.54
16
+ complexipy==5.6.1
17
17
  build>=1.2.2.post1
18
18
  twine>=6.1.0
19
19
  httpx>=0.28.1
@@ -0,0 +1 @@
1
+ __version__ = "3.2.0a0"
@@ -331,9 +331,10 @@ def create_app():
331
331
  context="app",
332
332
  )
333
333
 
334
- # Only register health check, version, and dev proxy routes
334
+ # Register basic utility routes available in dev mode
335
335
  app.router.add_get("/health", health_check)
336
336
  app.router.add_get("/version", get_version)
337
+ app.router.add_post("/refresh", refresh_models)
337
338
 
338
339
  # Register dev proxy routes using a temporary config for URL resolution
339
340
  dev_config = ArgoConfig()
@@ -366,7 +367,7 @@ def create_app():
366
367
  return app
367
368
 
368
369
 
369
- def run(*, host: str = "0.0.0.0", port: int = 8080):
370
+ def run(*, host: str = "0.0.0.0", port: int = 8080, socket: str = ""):
370
371
  app = create_app()
371
372
 
372
373
  # Add this to ensure signal handlers trigger a full shutdown
@@ -378,7 +379,68 @@ def run(*, host: str = "0.0.0.0", port: int = 8080):
378
379
  signal.signal(sig, _force_exit)
379
380
 
380
381
  try:
381
- web.run_app(app, host=host, port=port)
382
+ if socket:
383
+ _run_unix_socket(app, socket)
384
+ else:
385
+ web.run_app(app, host=host, port=port)
382
386
  except Exception as e:
383
387
  log_error(f"An error occurred while starting the server: {e}", context="app")
384
388
  sys.exit(1)
389
+
390
+
391
+ def _run_unix_socket(app: web.Application, socket_path: str):
392
+ """Start the server listening on a Unix domain socket.
393
+
394
+ Removes any stale socket file, starts the app, then restricts
395
+ permissions to owner-only (0o600) so other users on a shared
396
+ host cannot connect.
397
+ """
398
+ import stat
399
+
400
+ path = os.path.realpath(socket_path)
401
+
402
+ # Remove stale socket if present
403
+ if os.path.exists(path):
404
+ try:
405
+ st = os.stat(path)
406
+ if stat.S_ISSOCK(st.st_mode):
407
+ os.unlink(path)
408
+ log_info(f"Removed stale socket: {path}", context="app")
409
+ else:
410
+ log_error(
411
+ f"Socket path exists and is not a socket: {path}", context="app"
412
+ )
413
+ sys.exit(1)
414
+ except OSError as e:
415
+ log_error(f"Cannot remove stale socket {path}: {e}", context="app")
416
+ sys.exit(1)
417
+
418
+ # Ensure parent directory exists
419
+ parent = os.path.dirname(path)
420
+ if not os.path.isdir(parent):
421
+ log_error(f"Socket parent directory does not exist: {parent}", context="app")
422
+ sys.exit(1)
423
+
424
+ async def _set_socket_permissions(app_inner: web.Application):
425
+ """Set restrictive permissions on the socket after it is created."""
426
+ if os.path.exists(path):
427
+ os.chmod(path, 0o600)
428
+ log_info(
429
+ f"Socket permissions set to 0700 (owner-only): {path}",
430
+ context="app",
431
+ )
432
+
433
+ async def _cleanup_socket(app_inner: web.Application):
434
+ """Remove socket file on shutdown."""
435
+ if os.path.exists(path):
436
+ try:
437
+ os.unlink(path)
438
+ log_info(f"Removed socket: {path}", context="app")
439
+ except OSError:
440
+ pass
441
+
442
+ app.on_startup.append(_set_socket_permissions)
443
+ app.on_shutdown.append(_cleanup_socket)
444
+
445
+ log_info(f"Starting server on unix socket: {path}", context="app")
446
+ web.run_app(app, path=path)
@@ -12,7 +12,7 @@ from .._vendor.semver import version_parse
12
12
  from ..__init__ import __version__
13
13
  from ..config import PATHS_TO_TRY, validate_config
14
14
  from ..utils.attack_logger import get_attack_logger
15
- from ..utils.logging import log_error
15
+ from ..utils.logging import log_error, log_info
16
16
  from .display import CHANGELOG_URL, display_startup_banner
17
17
 
18
18
 
@@ -25,8 +25,12 @@ def set_config_envs(args: argparse.Namespace):
25
25
  """Set environment variables from serve CLI arguments."""
26
26
  if args.config:
27
27
  os.environ["CONFIG_PATH"] = args.config
28
+ if args.host:
29
+ os.environ["HOST"] = args.host
28
30
  if args.port:
29
31
  os.environ["PORT"] = str(args.port)
32
+ if getattr(args, "socket", None):
33
+ os.environ["SOCKET"] = args.socket
30
34
  if args.verbose:
31
35
  os.environ["VERBOSE"] = str(True)
32
36
  if args.quiet:
@@ -73,7 +77,22 @@ def handle_serve(args: argparse.Namespace):
73
77
  if config_path is not None:
74
78
  get_attack_logger().set_config_path(config_path)
75
79
 
76
- run(host=config_instance.host, port=config_instance.port)
80
+ if config_instance.socket:
81
+ log_info(
82
+ f"🌐 Listening on unix://{config_instance.socket}",
83
+ context="cli",
84
+ )
85
+ else:
86
+ log_info(
87
+ f"🌐 Listening on http://{config_instance.host}:{config_instance.port}",
88
+ context="cli",
89
+ )
90
+
91
+ run(
92
+ host=config_instance.host,
93
+ port=config_instance.port,
94
+ socket=config_instance.socket,
95
+ )
77
96
  except KeyError:
78
97
  log_error("Port not specified in configuration file.", context="cli")
79
98
  sys.exit(1)
@@ -40,6 +40,17 @@ def _add_serve_arguments(parser: argparse.ArgumentParser) -> None:
40
40
  type=int,
41
41
  help="Port number to bind the server to",
42
42
  )
43
+ parser.add_argument(
44
+ "--socket",
45
+ "-S",
46
+ type=str,
47
+ default=None,
48
+ help=(
49
+ "Unix socket path to listen on (overrides --host/--port).\n"
50
+ "Permissions are set to 0700 (owner-only) for security on\n"
51
+ "shared hosts. Example: /run/user/$(id -u)/argo-proxy.sock"
52
+ ),
53
+ )
43
54
 
44
55
  verbosity = parser.add_mutually_exclusive_group()
45
56
  verbosity.add_argument(
@@ -34,7 +34,15 @@ def _format_config_yaml(data: dict) -> str:
34
34
  groups: list[tuple[str, list[str]]] = [
35
35
  (
36
36
  "# Core settings",
37
- ["config_version", "user", "host", "port", "verbose", "log_to_file"],
37
+ [
38
+ "config_version",
39
+ "user",
40
+ "host",
41
+ "port",
42
+ "socket",
43
+ "verbose",
44
+ "log_to_file",
45
+ ],
38
46
  ),
39
47
  (
40
48
  "# Upstream",
@@ -118,6 +126,12 @@ def save_config(
118
126
 
119
127
  def _apply_env_overrides(config_data: ArgoConfig) -> ArgoConfig:
120
128
  """Apply environment variable overrides to the config."""
129
+ if env_host := os.getenv("HOST"):
130
+ config_data.host = env_host
131
+
132
+ if env_socket := os.getenv("SOCKET"):
133
+ config_data.socket = env_socket
134
+
121
135
  if env_port := os.getenv("PORT"):
122
136
  config_data.port = int(env_port)
123
137
 
@@ -30,6 +30,7 @@ class ArgoConfig:
30
30
  # Configuration fields with default values
31
31
  host: str = "0.0.0.0" # Default to 0.0.0.0
32
32
  port: int = 44497
33
+ socket: str = "" # Unix socket path; overrides host:port when set
33
34
  user: str = ""
34
35
  verbose: bool = True
35
36
 
@@ -248,6 +249,10 @@ class ArgoConfig:
248
249
  # Drop private fields
249
250
  serialized = {k: v for k, v in serialized.items() if not k.startswith("_")}
250
251
 
252
+ # Only persist socket when explicitly set
253
+ if not self.socket:
254
+ serialized.pop("socket", None)
255
+
251
256
  # Add the user-configurable base URL (stored as private field)
252
257
  if self._argo_base_url:
253
258
  serialized["argo_base_url"] = self.argo_base_url