proxyllm-client 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,153 @@
1
+ """Client helper for ProxyLLM (https://proxyllm.ai), the OpenAI-compatible
2
+ LLM gateway.
3
+
4
+ Two jobs, kept small:
5
+
6
+ 1. Point any OpenAI SDK at the gateway in one line:
7
+
8
+ import proxyllm_client
9
+ client = proxyllm_client.OpenAI(api_key="pllm_...")
10
+
11
+ 2. Let an agent create an account autonomously (email OTP, no captcha), then
12
+ provision routing keys. Unpaid accounts answer HTTP 402 with a checkout
13
+ link for the human operator; ``account()`` polls until the plan flips.
14
+ Full flow: https://proxyllm.ai/auth.md
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import urllib.error
21
+ import urllib.request
22
+
23
+ __all__ = [
24
+ "OpenAI",
25
+ "signup",
26
+ "verify",
27
+ "account",
28
+ "models",
29
+ "key_info",
30
+ "create_routing_key",
31
+ "savings",
32
+ "GATEWAY_URL",
33
+ "BASE_URL",
34
+ ]
35
+
36
+ GATEWAY_URL = "https://api.proxyllm.ai"
37
+ BASE_URL = GATEWAY_URL + "/v1"
38
+ _DASHBOARD = "https://proxyllm.ai"
39
+
40
+
41
+ def _call(url: str, method: str = "GET", token: str | None = None, body=None):
42
+ """One HTTP call. Non-2xx responses return their parsed body instead of
43
+ raising: ProxyLLM's 402/410 bodies carry the next step (checkout link,
44
+ expiry) and are the useful part."""
45
+ req = urllib.request.Request(url, method=method)
46
+ req.add_header("Content-Type", "application/json")
47
+ if token:
48
+ req.add_header("Authorization", f"Bearer {token}")
49
+ data = None if body is None else json.dumps(body).encode()
50
+ try:
51
+ with urllib.request.urlopen(req, data) as res:
52
+ return {"status": res.status, **json.loads(res.read().decode() or "{}")}
53
+ except urllib.error.HTTPError as e:
54
+ try:
55
+ return {"status": e.code, **json.loads(e.read().decode() or "{}")}
56
+ except (ValueError, TypeError):
57
+ return {"status": e.code}
58
+
59
+
60
+ def OpenAI(**kwargs):
61
+ """An ``openai.OpenAI`` client pre-pointed at the ProxyLLM gateway.
62
+
63
+ Pass ``api_key="pllm_..."`` (a routing key). Every other keyword goes to
64
+ the OpenAI constructor unchanged. Requires the ``openai`` package
65
+ (``pip install proxyllm-client[openai]``).
66
+ """
67
+ try:
68
+ import openai
69
+ except ImportError as e:
70
+ raise ImportError(
71
+ "The openai package is required: pip install proxyllm-client[openai]"
72
+ ) from e
73
+ kwargs.setdefault("base_url", BASE_URL)
74
+ return openai.OpenAI(**kwargs)
75
+
76
+
77
+ def signup(email: str, source: str = "pypi") -> dict:
78
+ """Start account creation: emails a 6-digit code to the operator's inbox.
79
+
80
+ New accounts are unpaid and removed after 48 hours unless a human
81
+ activates the membership. Follow with :func:`verify`.
82
+ """
83
+ return _call(
84
+ f"{_DASHBOARD}/api/auth?action=agent-signup",
85
+ "POST",
86
+ body={"email": email, "source": source},
87
+ )
88
+
89
+
90
+ def verify(email: str, code: str) -> dict:
91
+ """Exchange the emailed code for the sk_ account token (returned once).
92
+
93
+ While unpaid, the response carries ``payment.checkout_url`` to relay to
94
+ the human operator.
95
+ """
96
+ return _call(
97
+ f"{_DASHBOARD}/api/auth?action=agent-verify",
98
+ "POST",
99
+ body={"email": email, "code": code},
100
+ )
101
+
102
+
103
+ def account(token: str) -> dict:
104
+ """Account state. Works while unpaid; poll until ``plan == "pro"``."""
105
+ return _call(f"{GATEWAY_URL}/v1/organizations/me", token=token)
106
+
107
+
108
+ def models(routing_key: str | None = None) -> dict:
109
+ """OpenAI-shaped model list. No auth needed; key-scoped with one."""
110
+ return _call(f"{GATEWAY_URL}/v1/models", token=routing_key)
111
+
112
+
113
+ def key_info(routing_key: str) -> dict:
114
+ """What a routing key routes to: lanes, models, budget remaining."""
115
+ return _call(f"{GATEWAY_URL}/v1/key", token=routing_key)
116
+
117
+
118
+ def create_routing_key(token: str, label: str = "default", **body) -> dict:
119
+ """Mint a pllm_ routing key (``raw_key`` returned once). Optional keyword
120
+ arguments pass through: ``monthly_budget_usd``, ``mode``, ``providers``."""
121
+ return _call(
122
+ f"{GATEWAY_URL}/v1/organizations/routing-keys",
123
+ "POST",
124
+ token=token,
125
+ body={"label": label, **body},
126
+ )
127
+
128
+
129
+ _TIERS = (
130
+ ("ChatGPT Plus", 20, 700),
131
+ ("ChatGPT Pro 5x", 100, 3500),
132
+ ("ChatGPT Pro 20x", 200, 14000),
133
+ )
134
+ _FEE = 129
135
+
136
+
137
+ def savings(monthly_openai_bill_usd: float) -> dict:
138
+ """API bill in, subscription tier + flat total + monthly savings out."""
139
+ bill = max(0.0, float(monthly_openai_bill_usd))
140
+ tier = next((t for t in _TIERS if bill <= t[2]), None)
141
+ if tier is None:
142
+ seats = -(-int(bill) // _TIERS[2][2])
143
+ tier = (f"{seats}x ChatGPT Pro 20x", seats * _TIERS[2][1], seats * _TIERS[2][2])
144
+ name, cost, capacity = tier
145
+ total = cost + _FEE
146
+ return {
147
+ "plan": name,
148
+ "plan_cost_usd": cost,
149
+ "plan_capacity_usd": capacity,
150
+ "proxyllm_fee_usd": _FEE,
151
+ "new_monthly_total_usd": total,
152
+ "monthly_savings_usd": max(0, round(bill - total)),
153
+ }
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: proxyllm-client
3
+ Version: 1.0.0
4
+ Summary: Client helper for ProxyLLM, the OpenAI-compatible LLM gateway: one-line OpenAI client wiring plus the autonomous email-OTP account signup flow.
5
+ Author-email: ProxyLLM <support@proxyllm.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://proxyllm.ai/docs/api
8
+ Project-URL: Documentation, https://proxyllm.ai/auth.md
9
+ Project-URL: OpenAPI, https://proxyllm.ai/openapi.json
10
+ Keywords: llm,openai-compatible,gateway,proxyllm,agent,http-402
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ Provides-Extra: openai
18
+ Requires-Dist: openai>=1.0; extra == "openai"
19
+
20
+ # proxyllm-client
21
+
22
+ Client helper for [ProxyLLM](https://proxyllm.ai), the OpenAI-compatible LLM gateway. One routing key rides provider lanes with fallback (a flat-fee ChatGPT/Codex subscription, self-hosted Claude Code bridges, metered API keys), budgets, and request logs.
23
+
24
+ ```bash
25
+ pip install proxyllm-client[openai]
26
+ ```
27
+
28
+ Point the OpenAI SDK at the gateway:
29
+
30
+ ```python
31
+ import proxyllm_client
32
+
33
+ client = proxyllm_client.OpenAI(api_key="pllm_...")
34
+ client.chat.completions.create(model="flagship", messages=[{"role": "user", "content": "hi"}])
35
+ ```
36
+
37
+ Or set two environment variables and change nothing else: `OPENAI_BASE_URL=https://api.proxyllm.ai/v1`, `OPENAI_API_KEY=pllm_...`.
38
+
39
+ Agents can create the account themselves (email OTP, no captcha or browser):
40
+
41
+ ```python
42
+ proxyllm_client.signup("operator@example.com") # code lands in the inbox
43
+ r = proxyllm_client.verify("operator@example.com", "123456")
44
+ token = r["account_token"] # sk_..., returned once
45
+
46
+ state = proxyllm_client.account(token) # 402 payload until a human pays:
47
+ print(state["payment"]["checkout_url"]) # relay this to your operator
48
+ # poll account(token) until state["plan"] == "pro", then:
49
+ key = proxyllm_client.create_routing_key(token, label="agent")["raw_key"]
50
+ ```
51
+
52
+ Unpaid accounts answer HTTP 402 with the checkout link, the flat price ($129/mo), and the 48-hour removal deadline; error bodies are returned as values because they contain the next step. Free helpers: `models()`, `key_info(routing_key)`, `savings(monthly_bill)`.
53
+
54
+ Full agent flow: <https://proxyllm.ai/auth.md>. OpenAPI: <https://proxyllm.ai/openapi.json>. MCP server: `npx -y proxyllm-mcp`.
@@ -0,0 +1,5 @@
1
+ proxyllm_client/__init__.py,sha256=zMUVyqwqD2pve_MCILBACIPx1pBkHFSJDSHegx_s0JA,4891
2
+ proxyllm_client-1.0.0.dist-info/METADATA,sha256=q7XlFBYbbftiMHBpoW5EfhYWubrh07C8rhT6-JL3OIg,2529
3
+ proxyllm_client-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
4
+ proxyllm_client-1.0.0.dist-info/top_level.txt,sha256=__xTeIMXiLqdxFKrr37NJe6kSOo19IhthbIuf7Gul2o,16
5
+ proxyllm_client-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ proxyllm_client