dial-crewai 0.10.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.
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ dist/
3
+ __pycache__/
4
+ *.egg-info/
5
+ .pytest_cache/
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: dial-crewai
3
+ Version: 0.10.0
4
+ Summary: Official Dial CrewAI tools — phone numbers, SMS, OTP, and voice calls for multi-agent crews
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: crewai>=0.80
7
+ Requires-Dist: dial-sdk
8
+ Description-Content-Type: text/markdown
9
+
10
+ # dial-crewai
11
+
12
+ Official Dial tools for [CrewAI](https://crewai.com) — give a multi-agent crew the
13
+ ability to send SMS, receive OTP codes, and place AI voice calls through
14
+ [Dial](https://getdial.ai). Agents automating signups and web tasks get a real
15
+ phone identity.
16
+
17
+ It's the CrewAI sibling of [`dial-langchain`](https://pypi.org/project/dial-langchain/):
18
+ each tool is a `crewai.tools.BaseTool` subclass wrapping the [`dial-sdk`](https://pypi.org/project/dial-sdk/)
19
+ client. It adds nothing to the REST contract — it just shapes Dial's operations into
20
+ CrewAI tools.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install dial-crewai
26
+ ```
27
+
28
+ This pulls in `dial-sdk` and `crewai`.
29
+
30
+ ## Give the tools to a crew
31
+
32
+ Build one `DialClient`, pass it to `dial_tools`, and hand the list to an agent. Every
33
+ tool shares that one client — a single connection pool for the whole crew:
34
+
35
+ ```python
36
+ from crewai import Agent, Crew, Task
37
+ from dial_sdk import DialClient, DialConfig
38
+ from dial_crewai import dial_tools
39
+
40
+ dial = DialClient(DialConfig(api_key="sk_live_..."))
41
+
42
+ phone_agent = Agent(
43
+ role="Phone identity agent",
44
+ goal="Send and receive SMS so the crew can complete phone-verified signups",
45
+ backstory="You operate the crew's Dial phone number.",
46
+ tools=dial_tools(dial),
47
+ )
48
+ ```
49
+
50
+ `DialConfig` also takes an optional `base_url` to target a non-default deployment.
51
+
52
+ ### Or pick individual tools
53
+
54
+ ```python
55
+ from dial_crewai import SendMessageTool, WaitForMessageTool
56
+
57
+ tools = [
58
+ SendMessageTool(client=dial),
59
+ WaitForMessageTool(client=dial),
60
+ ]
61
+ ```
62
+
63
+ ## Available tools
64
+
65
+ Each tool takes your shared `DialClient` (`client=`):
66
+
67
+ | Tool | Action |
68
+ |---|---|
69
+ | `ListNumbersTool` | List your phone numbers |
70
+ | `PurchaseNumberTool` | Provision a new number (billable) |
71
+ | `SetNumberPropertiesTool` | Update a number's nickname / inbound instruction |
72
+ | `SendMessageTool` | Send an SMS (optionally MMS) |
73
+ | `ListMessagesTool` | List recent messages |
74
+ | `MakeCallTool` | Place an AI voice call |
75
+ | `ListCallsTool` | List recent calls |
76
+ | `GetCallTool` | Fetch one call by id |
77
+ | `GetBillingTool` | Credit balance, subscription, per-number mode |
78
+ | `WaitForMessageTool` | Block until the next inbound SMS arrives, or time out |
79
+
80
+ ## OTP flow
81
+
82
+ The point of phone identity for a crew: send a code and read the reply.
83
+ `WaitForMessageTool` blocks until the next inbound SMS arrives, so an agent can:
84
+
85
+ 1. trigger a signup that texts a code to your Dial number,
86
+ 2. call `WaitForMessageTool` to read the inbound code,
87
+ 3. enter it back into the signup form.
88
+
89
+ See [`examples/signup_crew.py`](./examples/signup_crew.py) for a runnable crew.
90
+
91
+ ## Notes
92
+
93
+ - The `dial-sdk` client is async; these tools are **sync-first** (`_run`) with an
94
+ async→sync bridge, and also expose `_arun` for async crews. The shared client's
95
+ requests run on a single background event loop, so the connection pool is reused
96
+ across calls; a sync crew needn't close the client explicitly.
97
+ - `SendMessageTool` is a write action and **isn't idempotent** — a re-invoke after a
98
+ failure can send a duplicate. `MakeCallTool` accepts an `idempotency_key`.
99
+ - `WaitForMessageTool` is backed by Dial's **presence-based** event stream — for
100
+ durable, at-least-once delivery, register a
101
+ [webhook](https://docs.getdial.ai/documentation/platform/webhooks).
102
+
103
+ See the [CrewAI integration docs](https://docs.getdial.ai/documentation/sdks/crewai)
104
+ for the full guide.
@@ -0,0 +1,95 @@
1
+ # dial-crewai
2
+
3
+ Official Dial tools for [CrewAI](https://crewai.com) — give a multi-agent crew the
4
+ ability to send SMS, receive OTP codes, and place AI voice calls through
5
+ [Dial](https://getdial.ai). Agents automating signups and web tasks get a real
6
+ phone identity.
7
+
8
+ It's the CrewAI sibling of [`dial-langchain`](https://pypi.org/project/dial-langchain/):
9
+ each tool is a `crewai.tools.BaseTool` subclass wrapping the [`dial-sdk`](https://pypi.org/project/dial-sdk/)
10
+ client. It adds nothing to the REST contract — it just shapes Dial's operations into
11
+ CrewAI tools.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install dial-crewai
17
+ ```
18
+
19
+ This pulls in `dial-sdk` and `crewai`.
20
+
21
+ ## Give the tools to a crew
22
+
23
+ Build one `DialClient`, pass it to `dial_tools`, and hand the list to an agent. Every
24
+ tool shares that one client — a single connection pool for the whole crew:
25
+
26
+ ```python
27
+ from crewai import Agent, Crew, Task
28
+ from dial_sdk import DialClient, DialConfig
29
+ from dial_crewai import dial_tools
30
+
31
+ dial = DialClient(DialConfig(api_key="sk_live_..."))
32
+
33
+ phone_agent = Agent(
34
+ role="Phone identity agent",
35
+ goal="Send and receive SMS so the crew can complete phone-verified signups",
36
+ backstory="You operate the crew's Dial phone number.",
37
+ tools=dial_tools(dial),
38
+ )
39
+ ```
40
+
41
+ `DialConfig` also takes an optional `base_url` to target a non-default deployment.
42
+
43
+ ### Or pick individual tools
44
+
45
+ ```python
46
+ from dial_crewai import SendMessageTool, WaitForMessageTool
47
+
48
+ tools = [
49
+ SendMessageTool(client=dial),
50
+ WaitForMessageTool(client=dial),
51
+ ]
52
+ ```
53
+
54
+ ## Available tools
55
+
56
+ Each tool takes your shared `DialClient` (`client=`):
57
+
58
+ | Tool | Action |
59
+ |---|---|
60
+ | `ListNumbersTool` | List your phone numbers |
61
+ | `PurchaseNumberTool` | Provision a new number (billable) |
62
+ | `SetNumberPropertiesTool` | Update a number's nickname / inbound instruction |
63
+ | `SendMessageTool` | Send an SMS (optionally MMS) |
64
+ | `ListMessagesTool` | List recent messages |
65
+ | `MakeCallTool` | Place an AI voice call |
66
+ | `ListCallsTool` | List recent calls |
67
+ | `GetCallTool` | Fetch one call by id |
68
+ | `GetBillingTool` | Credit balance, subscription, per-number mode |
69
+ | `WaitForMessageTool` | Block until the next inbound SMS arrives, or time out |
70
+
71
+ ## OTP flow
72
+
73
+ The point of phone identity for a crew: send a code and read the reply.
74
+ `WaitForMessageTool` blocks until the next inbound SMS arrives, so an agent can:
75
+
76
+ 1. trigger a signup that texts a code to your Dial number,
77
+ 2. call `WaitForMessageTool` to read the inbound code,
78
+ 3. enter it back into the signup form.
79
+
80
+ See [`examples/signup_crew.py`](./examples/signup_crew.py) for a runnable crew.
81
+
82
+ ## Notes
83
+
84
+ - The `dial-sdk` client is async; these tools are **sync-first** (`_run`) with an
85
+ async→sync bridge, and also expose `_arun` for async crews. The shared client's
86
+ requests run on a single background event loop, so the connection pool is reused
87
+ across calls; a sync crew needn't close the client explicitly.
88
+ - `SendMessageTool` is a write action and **isn't idempotent** — a re-invoke after a
89
+ failure can send a duplicate. `MakeCallTool` accepts an `idempotency_key`.
90
+ - `WaitForMessageTool` is backed by Dial's **presence-based** event stream — for
91
+ durable, at-least-once delivery, register a
92
+ [webhook](https://docs.getdial.ai/documentation/platform/webhooks).
93
+
94
+ See the [CrewAI integration docs](https://docs.getdial.ai/documentation/sdks/crewai)
95
+ for the full guide.
@@ -0,0 +1,27 @@
1
+ from .tools import (
2
+ dial_tools,
3
+ ListNumbersTool,
4
+ PurchaseNumberTool,
5
+ SetNumberPropertiesTool,
6
+ ListMessagesTool,
7
+ SendMessageTool,
8
+ ListCallsTool,
9
+ MakeCallTool,
10
+ GetCallTool,
11
+ GetBillingTool,
12
+ WaitForMessageTool,
13
+ )
14
+
15
+ __all__ = [
16
+ "dial_tools",
17
+ "ListNumbersTool",
18
+ "PurchaseNumberTool",
19
+ "SetNumberPropertiesTool",
20
+ "ListMessagesTool",
21
+ "SendMessageTool",
22
+ "ListCallsTool",
23
+ "MakeCallTool",
24
+ "GetCallTool",
25
+ "GetBillingTool",
26
+ "WaitForMessageTool",
27
+ ]
@@ -0,0 +1,43 @@
1
+ """Drive async ``dial-sdk`` coroutines from CrewAI's synchronous ``_run``.
2
+
3
+ ``dial-sdk`` is async, but CrewAI invokes tools synchronously via ``_run``. We run every
4
+ coroutine on a single, lazily-started **background event loop** (a daemon thread) rather
5
+ than calling ``asyncio.run`` per tool call.
6
+
7
+ Why one persistent loop instead of a fresh loop per call: the tools share one
8
+ ``DialClient`` (passed in by the caller), and its underlying ``httpx.AsyncClient`` binds
9
+ its connection pool to the event loop it first runs on. ``asyncio.run`` creates and tears
10
+ down a new loop on every call, so a reused client would fail on the second call ("Event
11
+ loop is closed"). Running all coroutines on one long-lived loop keeps the shared client's
12
+ pool valid and reused across calls.
13
+ """
14
+
15
+ import asyncio
16
+ import threading
17
+ from typing import Awaitable, Optional, TypeVar
18
+
19
+ T = TypeVar("T")
20
+
21
+ _loop: Optional[asyncio.AbstractEventLoop] = None
22
+ _lock = threading.Lock()
23
+
24
+
25
+ def _background_loop() -> asyncio.AbstractEventLoop:
26
+ """Return the shared background event loop, starting it on first use."""
27
+ global _loop
28
+ if _loop is None:
29
+ with _lock:
30
+ if _loop is None:
31
+ loop = asyncio.new_event_loop()
32
+ threading.Thread(
33
+ target=loop.run_forever,
34
+ name="dial-crewai",
35
+ daemon=True,
36
+ ).start()
37
+ _loop = loop
38
+ return _loop
39
+
40
+
41
+ def run_sync(coro: Awaitable[T]) -> T:
42
+ """Block the calling thread until ``coro`` completes on the background loop."""
43
+ return asyncio.run_coroutine_threadsafe(coro, _background_loop()).result()