oneshot-python 0.35.0__tar.gz → 0.36.0__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.
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/PKG-INFO +2 -2
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/README.md +23 -1
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/oneshot/client.py +26 -1
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/pyproject.toml +2 -2
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_linkedin.py +34 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/.gitignore +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/oneshot/__init__.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/oneshot/_errors.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/oneshot/_types.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/oneshot/physical_mail.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/oneshot/x402.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/__init__.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/conftest.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_access_token.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_approvals.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_balance.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_browser.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_budgets.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_charge_amount.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_compute.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_domains.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_email_payload.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_emergency_error.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_hunts.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_inbound_voice.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_max_cost_header.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_payment_rejection.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_phones_pending.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_physical_mail.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_poll_backoff.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_request_id.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_signed_job_and_analytics.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_tag_receipt_value.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_top_up_credits.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_wait_false.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/tests/test_x402.py +0 -0
- {oneshot_python-0.35.0 → oneshot_python-0.36.0}/uv.lock +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oneshot-python
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Core Python SDK for the OneShot API — HTTP client with x402 payment handling —
|
|
3
|
+
Version: 0.36.0
|
|
4
|
+
Summary: Core Python SDK for the OneShot API — HTTP client with x402 payment handling — 48 tools
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Requires-Dist: eth-account>=0.13.0
|
|
@@ -269,6 +269,28 @@ client.linkedin_withdraw_invitation(
|
|
|
269
269
|
|
|
270
270
|
Async equivalents are `alinkedin_invite()` and `alinkedin_withdraw_invitation()`. Use the OneShot `invitation_id`, not the provider invitation ID. Withdrawal can cancel a queued invitation and never disconnects an established connection. Invitations default to 25 per account per UTC day; withdrawal does not restore capacity for an invitation already sent. Reuse the same key when retrying an operation.
|
|
271
271
|
|
|
272
|
+
### LinkedIn: message a connection and read invitation status
|
|
273
|
+
|
|
274
|
+
Available in `oneshot-python` 0.36.0 and later.
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
# Accepted as soon as the person is a connection; no message needs to be exchanged first.
|
|
278
|
+
current = client.linkedin_invitation(account_id, invitation["invitation_id"])
|
|
279
|
+
if current["status"] == "accepted":
|
|
280
|
+
sent = client.linkedin_message(
|
|
281
|
+
account_id,
|
|
282
|
+
"https://www.linkedin.com/in/jane-doe/",
|
|
283
|
+
"Thanks for connecting.",
|
|
284
|
+
idempotency_key="event-jane-first-message",
|
|
285
|
+
)
|
|
286
|
+
conversation_id = sent["conversation_id"]
|
|
287
|
+
|
|
288
|
+
# Poll for changes instead of asking one by one:
|
|
289
|
+
changed = client.linkedin_invitations(account_id, status="accepted", changed_since="2026-10-01T00:00:00Z")
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`linkedin_message()` posts into the 1:1 conversation the account already has with that connection, or starts one. It needs the `reply` grant and shares the reply daily cap. A failed send carries an error code: `not_connected` (invite them first) and `rate_limited` mean defer, not give up. The two invitation reads are free. Statuses are `pending`, `accepted`, `withdrawn`, `declined_or_unknown` and `not_sent`. Async equivalents are `alinkedin_message()`, `alinkedin_invitation()` and `alinkedin_invitations()`.
|
|
293
|
+
|
|
272
294
|
### Soul Hunt
|
|
273
295
|
|
|
274
296
|
`client.hunts` talks to Soul Hunt (`api.soulhunt.ai` by default — override with `OneShotClient(..., hunt_url=...)`), a separate service from the main OneShot API:
|
|
@@ -301,7 +323,7 @@ Async variants prefix the method with `a` (`alist`, `astatus`, `apredict`, `acap
|
|
|
301
323
|
## Links
|
|
302
324
|
|
|
303
325
|
- [Documentation](https://docs.oneshotagent.com/sdk/installation#install-via-pip-python)
|
|
304
|
-
- [LangChain integration](https://pypi.org/project/langchain-oneshot/):
|
|
326
|
+
- [LangChain integration](https://pypi.org/project/langchain-oneshot/): 48 tools as LangChain BaseTool
|
|
305
327
|
- [GAME plugin](https://pypi.org/project/game-plugin-oneshot/): Virtuals Protocol integration
|
|
306
328
|
- [TypeScript SDK](https://www.npmjs.com/package/@oneshot-agent/sdk)
|
|
307
329
|
- [MCP Server](https://www.npmjs.com/package/@oneshot-agent/mcp-server)
|
|
@@ -59,7 +59,7 @@ try:
|
|
|
59
59
|
|
|
60
60
|
SDK_VERSION = _pkg_version("oneshot-python")
|
|
61
61
|
except Exception: # pragma: no cover - editable/source runs without dist metadata
|
|
62
|
-
SDK_VERSION = "0.
|
|
62
|
+
SDK_VERSION = "0.36.0"
|
|
63
63
|
|
|
64
64
|
_BASE_URL = "https://win.oneshotagent.com"
|
|
65
65
|
_HUNT_BASE_URL = "https://api.soulhunt.ai"
|
|
@@ -1667,6 +1667,20 @@ class OneShotClient:
|
|
|
1667
1667
|
async def alinkedin_messages(self, account_id: str, **params: Any) -> dict:
|
|
1668
1668
|
return await self.acall_free_get(f"/v1/tools/linkedin/accounts/{quote(account_id, safe='')}/messages", _linkedin_query(params))
|
|
1669
1669
|
|
|
1670
|
+
def linkedin_invitation(self, account_id: str, invitation_id: str) -> dict:
|
|
1671
|
+
"""One invitation sent through linkedin_invite: `accepted` (with `accepted_at`) as soon as the person is a connection. Free."""
|
|
1672
|
+
return self.call_free_get(f"/v1/tools/linkedin/accounts/{quote(account_id, safe='')}/invitations/{quote(invitation_id, safe='')}")
|
|
1673
|
+
|
|
1674
|
+
async def alinkedin_invitation(self, account_id: str, invitation_id: str) -> dict:
|
|
1675
|
+
return await self.acall_free_get(f"/v1/tools/linkedin/accounts/{quote(account_id, safe='')}/invitations/{quote(invitation_id, safe='')}")
|
|
1676
|
+
|
|
1677
|
+
def linkedin_invitations(self, account_id: str, **params: Any) -> dict:
|
|
1678
|
+
"""Invitations sent through linkedin_invite, most recently changed first. Params: status, changed_since, limit. Free."""
|
|
1679
|
+
return self.call_free_get(f"/v1/tools/linkedin/accounts/{quote(account_id, safe='')}/invitations", _linkedin_query(params))
|
|
1680
|
+
|
|
1681
|
+
async def alinkedin_invitations(self, account_id: str, **params: Any) -> dict:
|
|
1682
|
+
return await self.acall_free_get(f"/v1/tools/linkedin/accounts/{quote(account_id, safe='')}/invitations", _linkedin_query(params))
|
|
1683
|
+
|
|
1670
1684
|
def _linkedin_invitation_call(self, endpoint: str, payload: dict, **kwargs: Any) -> Any:
|
|
1671
1685
|
return asyncio.get_event_loop().run_until_complete(self._alinkedin_invitation_call(endpoint, payload, **kwargs))
|
|
1672
1686
|
|
|
@@ -1713,6 +1727,17 @@ class OneShotClient:
|
|
|
1713
1727
|
return await self.acall_tool("/v1/tools/linkedin/reply", {"account_id": account_id, "conversation_id": conversation_id, "text": text, **kwargs},
|
|
1714
1728
|
max_cost=max_cost, timeout_sec=timeout_sec, wait=wait, idempotency_key=idempotency_key or _new_idempotency_key())
|
|
1715
1729
|
|
|
1730
|
+
def linkedin_message(self, account_id: str, profile: str, text: str, *, idempotency_key: Optional[str] = None, max_cost: Optional[float] = None, timeout_sec: int = 180, wait: bool = True, **kwargs: Any) -> Any:
|
|
1731
|
+
"""Message a first-degree connection: into the 1:1 conversation the account already has with them, or a new one.
|
|
1732
|
+
Needs the `reply` grant and shares its daily cap. A failed send carries an error code: `not_connected` and
|
|
1733
|
+
`rate_limited` mean defer, not give up. An Idempotency-Key is generated when not supplied. Blocking."""
|
|
1734
|
+
return self.call_tool("/v1/tools/linkedin/message", {"account_id": account_id, "profile": profile, "text": text, **kwargs},
|
|
1735
|
+
max_cost=max_cost, timeout_sec=timeout_sec, wait=wait, idempotency_key=idempotency_key or _new_idempotency_key())
|
|
1736
|
+
|
|
1737
|
+
async def alinkedin_message(self, account_id: str, profile: str, text: str, *, idempotency_key: Optional[str] = None, max_cost: Optional[float] = None, timeout_sec: int = 180, wait: bool = True, **kwargs: Any) -> Any:
|
|
1738
|
+
return await self.acall_tool("/v1/tools/linkedin/message", {"account_id": account_id, "profile": profile, "text": text, **kwargs},
|
|
1739
|
+
max_cost=max_cost, timeout_sec=timeout_sec, wait=wait, idempotency_key=idempotency_key or _new_idempotency_key())
|
|
1740
|
+
|
|
1716
1741
|
def linkedin_view_profile(self, account_id: str, identifier: str, *, notify: bool = False, idempotency_key: Optional[str] = None, max_cost: Optional[float] = None, timeout_sec: int = 180, wait: bool = True, **kwargs: Any) -> Any:
|
|
1717
1742
|
"""View a profile through the connected account (public-safe summary). notify=True cannot be undone. Blocking."""
|
|
1718
1743
|
return self.call_tool("/v1/tools/linkedin/profile-view", {"account_id": account_id, "identifier": identifier, "notify": bool(notify), **kwargs},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "oneshot-python"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "Core Python SDK for the OneShot API — HTTP client with x402 payment handling —
|
|
3
|
+
version = "0.36.0"
|
|
4
|
+
description = "Core Python SDK for the OneShot API — HTTP client with x402 payment handling — 48 tools"
|
|
5
5
|
readme = {text = "Core Python SDK for the OneShot API", content-type = "text/plain"}
|
|
6
6
|
license = "MIT"
|
|
7
7
|
requires-python = ">=3.10"
|
|
@@ -187,3 +187,37 @@ def test_reconnect_sends_complete_upgrade_grant(asynchronous):
|
|
|
187
187
|
client.call_free_post = Mock(return_value=result)
|
|
188
188
|
assert client.reconnect_linkedin_account('account/id', requested_actions=actions) == result
|
|
189
189
|
client.call_free_post.assert_called_once_with('/v1/tools/linkedin/accounts/account%2Fid/reconnect', {'requested_actions': actions}, scope='write')
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.mark.asyncio
|
|
193
|
+
async def test_message_is_paid_and_idempotency_keyed():
|
|
194
|
+
client = OneShotClient(TEST_PRIVATE_KEY)
|
|
195
|
+
client.acall_tool = AsyncMock(return_value={"status": "sent", "conversation_id": "conv"})
|
|
196
|
+
|
|
197
|
+
await client.alinkedin_message("acc", "https://www.linkedin.com/in/jane/", "hi", memo="m")
|
|
198
|
+
args, kwargs = client.acall_tool.await_args
|
|
199
|
+
assert args == ("/v1/tools/linkedin/message", {"account_id": "acc", "profile": "https://www.linkedin.com/in/jane/", "text": "hi", "memo": "m"})
|
|
200
|
+
assert len(kwargs["idempotency_key"]) == 32 # auto-generated
|
|
201
|
+
assert kwargs["timeout_sec"] == 180 and kwargs["wait"] is True
|
|
202
|
+
|
|
203
|
+
await client.alinkedin_message("acc", "jane", "hi", idempotency_key="fixed")
|
|
204
|
+
assert client.acall_tool.await_args.kwargs["idempotency_key"] == "fixed"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@pytest.mark.asyncio
|
|
208
|
+
async def test_invitation_status_reads_are_free():
|
|
209
|
+
client = OneShotClient(TEST_PRIVATE_KEY)
|
|
210
|
+
client.acall_free_get = AsyncMock(return_value={"invitations": []})
|
|
211
|
+
client.acall_tool = AsyncMock()
|
|
212
|
+
|
|
213
|
+
await client.alinkedin_invitation("acc", "inv 1")
|
|
214
|
+
client.acall_free_get.assert_awaited_with("/v1/tools/linkedin/accounts/acc/invitations/inv%201")
|
|
215
|
+
await client.alinkedin_invitations("acc", status="accepted", changed_since="2026-10-01T00:00:00Z", limit=20)
|
|
216
|
+
client.acall_free_get.assert_awaited_with(
|
|
217
|
+
"/v1/tools/linkedin/accounts/acc/invitations",
|
|
218
|
+
{"status": "accepted", "changed_since": "2026-10-01T00:00:00Z", "limit": "20"},
|
|
219
|
+
)
|
|
220
|
+
await client.alinkedin_invitations("acc")
|
|
221
|
+
client.acall_free_get.assert_awaited_with("/v1/tools/linkedin/accounts/acc/invitations", None)
|
|
222
|
+
client.acall_tool.assert_not_awaited()
|
|
223
|
+
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|