ainize 0.1.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.
ainize/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """Call an Ainize node with the OpenAI code you already have.
2
+
3
+ import ainize
4
+
5
+ client = ainize.connect("https://node.example", private_key=...)
6
+ client.chat.completions.create(model="qwen3.8-flash-next", messages=[...])
7
+
8
+ `connect()` returns a real `openai.OpenAI`. What you pay with is a deposit: send AIN or sAIN to the node and your
9
+ share of its throughput is your share of what everyone asking at that moment has deposited.
10
+ """
11
+
12
+ from ._connect import await_deposit, connect, deposit_address
13
+
14
+ __all__ = ["connect", "deposit_address", "await_deposit"]
15
+ __version__ = "0.1.0"
ainize/_connect.py ADDED
@@ -0,0 +1,114 @@
1
+ """Point OpenAI at an Ainize node.
2
+
3
+ This library does one thing on top of `openai`: it proves which address is calling and gets a key back. It does
4
+ not wrap the client, subclass it, or re-export a narrowed version of it — it returns the real `openai.OpenAI`,
5
+ because the whole promise is that nothing after that line is different. A wrapper would have to grow a method
6
+ every time OpenAI's client does, and would be a second place for bugs to live.
7
+
8
+ It also never signs a transfer. `deposit_address()` says where to send AIN and `await_deposit()` waits for the
9
+ node to notice; moving funds stays with the wallet the person already trusts. A library that signs transfers is a
10
+ much larger thing to hand your private key to than one that signs a login, and the difference is not something a
11
+ caller can see from the import line.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import time
17
+
18
+ import httpx
19
+ import openai
20
+ from eth_account import Account
21
+ from eth_account.messages import encode_defunct
22
+
23
+ __all__ = ["connect", "deposit_address", "await_deposit"]
24
+
25
+ _DEFAULT_TIMEOUT = 30.0
26
+
27
+
28
+ def connect(
29
+ node_url: str,
30
+ *,
31
+ private_key: str | None = None,
32
+ api_key: str | None = None,
33
+ timeout: float = _DEFAULT_TIMEOUT,
34
+ ) -> openai.OpenAI:
35
+ """Return an `openai.OpenAI` pointed at `node_url`, signing in if it has to.
36
+
37
+ Pass `private_key` to sign in and be issued a key, or `api_key` to reuse one you already hold. The returned
38
+ client is the genuine article: every call, parameter and exception is OpenAI's.
39
+ """
40
+ node_url = node_url.rstrip("/")
41
+ if api_key is None:
42
+ if private_key is None:
43
+ raise ValueError(
44
+ "connect() needs either private_key (to sign in and be issued one) or api_key (one you already hold)"
45
+ )
46
+ api_key = _sign_in(node_url, private_key, timeout=timeout)
47
+ return openai.OpenAI(base_url=f"{node_url}/v1", api_key=api_key)
48
+
49
+
50
+ def _sign_in(node_url: str, private_key: str, *, timeout: float) -> str:
51
+ """Prove the address once and leave with a key.
52
+
53
+ The message signed is the one the node issued, verbatim — it is not rebuilt here. A rebuilt message is a
54
+ message the client had a hand in, and the node verifies against the bytes it handed out.
55
+ """
56
+ account = Account.from_key(private_key)
57
+ with httpx.Client(timeout=timeout) as http:
58
+ challenge = http.post(
59
+ f"{node_url}/v1/auth/nonce",
60
+ json={"address": account.address, "scheme": "eip191"},
61
+ )
62
+ challenge.raise_for_status()
63
+ issued_challenge = challenge.json()
64
+
65
+ signature = account.sign_message(
66
+ encode_defunct(text=issued_challenge["message"])
67
+ ).signature.hex()
68
+ if not signature.startswith("0x"):
69
+ signature = f"0x{signature}"
70
+
71
+ token = http.post(
72
+ f"{node_url}/v1/auth/token",
73
+ json={"nonce": issued_challenge["nonce"], "signature": signature},
74
+ )
75
+ token.raise_for_status()
76
+ return token.json()["api_key"]
77
+
78
+
79
+ def deposit_address(node_url: str, *, timeout: float = _DEFAULT_TIMEOUT) -> str:
80
+ """Where to send AIN or sAIN to buy a share of this node's throughput."""
81
+ with httpx.Client(timeout=timeout) as http:
82
+ response = http.get(f"{node_url.rstrip('/')}/v1/account/deposit-address")
83
+ response.raise_for_status()
84
+ return response.json()["address"]
85
+
86
+
87
+ def await_deposit(
88
+ node_url: str,
89
+ tx_hash: str,
90
+ *,
91
+ api_key: str,
92
+ timeout: float = 600.0,
93
+ poll_seconds: float = 5.0,
94
+ ) -> dict:
95
+ """Block until the node has credited `tx_hash`.
96
+
97
+ The node is the authority on when a transfer counts: it waits for its own confirmation depth before crediting,
98
+ so a transaction that a block explorer already shows is not yet a share here. Polling is honest about that;
99
+ guessing from the chain would not be.
100
+ """
101
+ deadline = time.monotonic() + timeout
102
+ headers = {"authorization": f"Bearer {api_key}"}
103
+ with httpx.Client(timeout=_DEFAULT_TIMEOUT) as http:
104
+ while True:
105
+ response = http.get(
106
+ f"{node_url.rstrip('/')}/v1/account/deposits/{tx_hash}", headers=headers
107
+ )
108
+ response.raise_for_status()
109
+ seen = response.json()
110
+ if seen.get("credited"):
111
+ return seen
112
+ if time.monotonic() > deadline:
113
+ raise TimeoutError(f"{tx_hash} was not credited within {timeout}s")
114
+ time.sleep(poll_seconds)
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: ainize
3
+ Version: 0.1.0
4
+ Summary: Call an Ainize node with ordinary OpenAI code; pay for throughput by staking AIN
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/ainblockchain/ainize
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: openai>=1.40
10
+ Requires-Dist: eth-account>=0.13
11
+ Requires-Dist: httpx>=0.27
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8; extra == "dev"
14
+
15
+ # ainize (Python)
16
+
17
+ Call an Ainize node with the OpenAI code you already have.
18
+
19
+ ```python
20
+ import ainize
21
+
22
+ client = ainize.connect("https://node.example", private_key="0x…")
23
+
24
+ client.chat.completions.create(
25
+ model="qwen3.8-flash-next",
26
+ messages=[{"role": "user", "content": "hello"}],
27
+ )
28
+ ```
29
+
30
+ `connect()` signs one challenge to prove your address, and returns a real `openai.OpenAI` with `base_url` and
31
+ `api_key` already set. Everything after that line is OpenAI's — its methods, its parameters, its exceptions.
32
+
33
+ Already hold a key? `ainize.connect(url, api_key="ainize-sk-…")` skips the signing.
34
+
35
+ ## What you pay with
36
+
37
+ A deposit, not a per-token charge. Send AIN or sAIN to the node; the operator holds it staked, and your share of
38
+ the node's throughput is your share of what everyone asking at that moment has deposited. An idle deposit costs
39
+ nobody anything, and the principal is not consumed.
40
+
41
+ ```python
42
+ ainize.deposit_address("https://node.example") # where to send
43
+ ainize.await_deposit(url, tx_hash, api_key=client.api_key) # wait until the node has credited it
44
+ ```
45
+
46
+ The library never signs a transfer. It tells you where to send and waits for the node to notice; moving funds
47
+ stays with the wallet you already trust.
48
+
49
+ ## Tests
50
+
51
+ They drive a real node and a stub model — mocking the transport would test our idea of the node's replies rather
52
+ than the node's replies, and the shapes are exactly where compatibility breaks. Run them from this directory, so
53
+ the package on disk is the one imported:
54
+
55
+ ```bash
56
+ python -m venv .venv && .venv/bin/pip install openai eth-account httpx pytest
57
+ .venv/bin/python -m pytest
58
+ ```
@@ -0,0 +1,6 @@
1
+ ainize/__init__.py,sha256=5TcxmtGyTU8G46WiflosO70D-gaJTOLGooita5z4kJY,585
2
+ ainize/_connect.py,sha256=PAz1TBeNxhXE7QVAotqUH3_Yx7QCkDV_vIFaOnRGX_k,4486
3
+ ainize-0.1.0.dist-info/METADATA,sha256=3KdfnzWxQAC22Uog9vLbYD3N29mBocltMBKSx5okg9Q,2072
4
+ ainize-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
5
+ ainize-0.1.0.dist-info/top_level.txt,sha256=DU7uxqX0J3XVwvbCIvPrei70QwI6StvoVg04UalgYo0,7
6
+ ainize-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ainize