dial-agentframework 0.13.0__tar.gz → 0.14.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.
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/PKG-INFO +2 -2
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/dial_agentframework/__init__.py +4 -0
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/dial_agentframework/tools.py +77 -2
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/pyproject.toml +2 -2
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/tests/test_tools.py +59 -1
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/uv.lock +2 -2
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/.gitignore +0 -0
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/README.md +0 -0
- {dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/examples/signup_agentframework.ipynb +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dial-agentframework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: Official Dial tools for the Microsoft Agent Framework — phone numbers, SMS, OTP, and voice calls
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: agent-framework-core>=1.0
|
|
7
|
-
Requires-Dist: dial-sdk>=0.
|
|
7
|
+
Requires-Dist: dial-sdk>=0.16.0
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
|
|
10
10
|
# dial-agentframework
|
|
@@ -6,6 +6,8 @@ from .tools import (
|
|
|
6
6
|
list_messages_tool,
|
|
7
7
|
send_message_tool,
|
|
8
8
|
reply_to_message_tool,
|
|
9
|
+
start_typing_tool,
|
|
10
|
+
stop_typing_tool,
|
|
9
11
|
list_calls_tool,
|
|
10
12
|
make_call_tool,
|
|
11
13
|
get_call_tool,
|
|
@@ -21,6 +23,8 @@ __all__ = [
|
|
|
21
23
|
"list_messages_tool",
|
|
22
24
|
"send_message_tool",
|
|
23
25
|
"reply_to_message_tool",
|
|
26
|
+
"start_typing_tool",
|
|
27
|
+
"stop_typing_tool",
|
|
24
28
|
"list_calls_tool",
|
|
25
29
|
"make_call_tool",
|
|
26
30
|
"get_call_tool",
|
|
@@ -192,7 +192,16 @@ def send_message_tool(client: DialClient) -> FunctionTool:
|
|
|
192
192
|
)
|
|
193
193
|
async def send_message(
|
|
194
194
|
to: Annotated[str, "Destination phone number in E.164 format, e.g. +1234567890"],
|
|
195
|
-
|
|
195
|
+
from_number: Annotated[
|
|
196
|
+
Optional[str],
|
|
197
|
+
"Number to send from: a phone number ID, one of your numbers in E.164, or "
|
|
198
|
+
"a nickname. Exclusive with from_number_id — provide exactly one.",
|
|
199
|
+
] = None,
|
|
200
|
+
from_number_id: Annotated[
|
|
201
|
+
Optional[str],
|
|
202
|
+
"ID of the Dial phone number to send from. Exclusive with from_number — "
|
|
203
|
+
"provide exactly one.",
|
|
204
|
+
] = None,
|
|
196
205
|
body: Annotated[
|
|
197
206
|
str, "Message body text; optional when media_urls is given (media-only send)"
|
|
198
207
|
] = "",
|
|
@@ -211,6 +220,7 @@ def send_message_tool(client: DialClient) -> FunctionTool:
|
|
|
211
220
|
msg = await client.send_message(
|
|
212
221
|
SendMessageParams(
|
|
213
222
|
to=to,
|
|
223
|
+
from_number=from_number,
|
|
214
224
|
from_number_id=from_number_id,
|
|
215
225
|
body=body,
|
|
216
226
|
channel=channel,
|
|
@@ -263,6 +273,59 @@ def reply_to_message_tool(client: DialClient) -> FunctionTool:
|
|
|
263
273
|
return reply_to_message
|
|
264
274
|
|
|
265
275
|
|
|
276
|
+
def start_typing_tool(client: DialClient) -> FunctionTool:
|
|
277
|
+
@tool(
|
|
278
|
+
name="start_typing",
|
|
279
|
+
description=(
|
|
280
|
+
"Show a typing indicator to the recipient, as if composing a message from "
|
|
281
|
+
"your number. iMessage numbers display it; standard (SMS) numbers have no "
|
|
282
|
+
"typing concept and silently ignore it, so it is always safe to call before "
|
|
283
|
+
"send_message. Fire-and-forget and free. Delivering a message or reaction "
|
|
284
|
+
"clears the indicator natively on the recipient's device — call start_typing "
|
|
285
|
+
"again after a send to keep composing, and stop_typing when you stop without sending."
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
async def start_typing(
|
|
289
|
+
to_number: Annotated[
|
|
290
|
+
str, "Recipient phone number in E.164 format, e.g. +14155550123"
|
|
291
|
+
],
|
|
292
|
+
from_number: Annotated[
|
|
293
|
+
str,
|
|
294
|
+
"Number the indicator appears from: a phone number ID, one of your numbers "
|
|
295
|
+
"in E.164, or a nickname",
|
|
296
|
+
],
|
|
297
|
+
) -> str:
|
|
298
|
+
await client.start_typing(to_number=to_number, from_number=from_number)
|
|
299
|
+
return "Typing indicator shown (iMessage numbers only — SMS numbers ignore it)."
|
|
300
|
+
|
|
301
|
+
return start_typing
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def stop_typing_tool(client: DialClient) -> FunctionTool:
|
|
305
|
+
@tool(
|
|
306
|
+
name="stop_typing",
|
|
307
|
+
description=(
|
|
308
|
+
"Clear a typing indicator previously shown with start_typing. Delivering a "
|
|
309
|
+
"message or reaction already clears it natively on the recipient's device — "
|
|
310
|
+
"call this when you stop composing without sending."
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
async def stop_typing(
|
|
314
|
+
to_number: Annotated[
|
|
315
|
+
str, "Recipient phone number in E.164 format, e.g. +14155550123"
|
|
316
|
+
],
|
|
317
|
+
from_number: Annotated[
|
|
318
|
+
str,
|
|
319
|
+
"Number the indicator appears from: a phone number ID, one of your numbers "
|
|
320
|
+
"in E.164, or a nickname",
|
|
321
|
+
],
|
|
322
|
+
) -> str:
|
|
323
|
+
await client.stop_typing(to_number=to_number, from_number=from_number)
|
|
324
|
+
return "Typing indicator cleared."
|
|
325
|
+
|
|
326
|
+
return stop_typing
|
|
327
|
+
|
|
328
|
+
|
|
266
329
|
# ── Calls ────────────────────────────────────────────────────────────────────
|
|
267
330
|
|
|
268
331
|
|
|
@@ -298,10 +361,19 @@ def make_call_tool(client: DialClient) -> FunctionTool:
|
|
|
298
361
|
)
|
|
299
362
|
async def make_call(
|
|
300
363
|
to: Annotated[str, "Destination phone number in E.164 format, e.g. +1234567890"],
|
|
301
|
-
from_number_id: Annotated[str, "ID of the Dial phone number to call from"],
|
|
302
364
|
outbound_instruction: Annotated[
|
|
303
365
|
str, "System prompt for the AI voice agent during this call"
|
|
304
366
|
],
|
|
367
|
+
from_number: Annotated[
|
|
368
|
+
Optional[str],
|
|
369
|
+
"Number to call from: a phone number ID, one of your numbers in E.164, or "
|
|
370
|
+
"a nickname. Exclusive with from_number_id — provide exactly one.",
|
|
371
|
+
] = None,
|
|
372
|
+
from_number_id: Annotated[
|
|
373
|
+
Optional[str],
|
|
374
|
+
"ID of the Dial phone number to call from. Exclusive with from_number — "
|
|
375
|
+
"provide exactly one.",
|
|
376
|
+
] = None,
|
|
305
377
|
language: Annotated[
|
|
306
378
|
Optional[str],
|
|
307
379
|
"BCP-47 language tag, e.g. 'en-US', 'he-IL'. Omit to auto-detect from the "
|
|
@@ -332,6 +404,7 @@ def make_call_tool(client: DialClient) -> FunctionTool:
|
|
|
332
404
|
call = await client.make_call(
|
|
333
405
|
MakeCallParams(
|
|
334
406
|
to=to,
|
|
407
|
+
from_number=from_number,
|
|
335
408
|
from_number_id=from_number_id,
|
|
336
409
|
outbound_instruction=outbound_instruction,
|
|
337
410
|
language=language,
|
|
@@ -470,6 +543,8 @@ def dial_tools(client: DialClient) -> list[FunctionTool]:
|
|
|
470
543
|
list_messages_tool(client),
|
|
471
544
|
send_message_tool(client),
|
|
472
545
|
reply_to_message_tool(client),
|
|
546
|
+
start_typing_tool(client),
|
|
547
|
+
stop_typing_tool(client),
|
|
473
548
|
list_calls_tool(client),
|
|
474
549
|
make_call_tool(client),
|
|
475
550
|
get_call_tool(client),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dial-agentframework"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.14.0"
|
|
4
4
|
description = "Official Dial tools for the Microsoft Agent Framework — phone numbers, SMS, OTP, and voice calls"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -8,7 +8,7 @@ dependencies = [
|
|
|
8
8
|
# Core has `tool` + `FunctionTool` (all this package needs). Chat clients ship in
|
|
9
9
|
# separate integration packages (agent-framework-openai, …) the user installs.
|
|
10
10
|
"agent-framework-core>=1.0",
|
|
11
|
-
"dial-sdk>=0.
|
|
11
|
+
"dial-sdk>=0.16.0",
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
[tool.uv.sources]
|
|
@@ -6,6 +6,8 @@ from dial_agentframework import (
|
|
|
6
6
|
send_message_tool,
|
|
7
7
|
reply_to_message_tool,
|
|
8
8
|
make_call_tool,
|
|
9
|
+
start_typing_tool,
|
|
10
|
+
stop_typing_tool,
|
|
9
11
|
wait_for_message_tool,
|
|
10
12
|
)
|
|
11
13
|
|
|
@@ -55,7 +57,7 @@ def _run(tool, **args) -> str:
|
|
|
55
57
|
# ── Tools ────────────────────────────────────────────────────────────────────
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
def
|
|
60
|
+
def test_dial_tools_bundles_all_thirteen():
|
|
59
61
|
names = sorted(t.name for t in dial_tools(FakeClient()))
|
|
60
62
|
assert names == sorted(
|
|
61
63
|
[
|
|
@@ -65,6 +67,8 @@ def test_dial_tools_bundles_all_eleven():
|
|
|
65
67
|
"list_messages",
|
|
66
68
|
"send_message",
|
|
67
69
|
"reply_to_message",
|
|
70
|
+
"start_typing",
|
|
71
|
+
"stop_typing",
|
|
68
72
|
"list_calls",
|
|
69
73
|
"make_call",
|
|
70
74
|
"get_call",
|
|
@@ -104,6 +108,44 @@ def test_send_message_media_only_with_force_audio_file():
|
|
|
104
108
|
assert params.media == ["https://cdn.example.com/note.m4a"]
|
|
105
109
|
|
|
106
110
|
|
|
111
|
+
def test_send_message_passes_from_number():
|
|
112
|
+
client = FakeClient(send_message=_FakeMessage())
|
|
113
|
+
tool = send_message_tool(client)
|
|
114
|
+
out = _run(tool, to="+14155550123", from_number="Support line", body="hi")
|
|
115
|
+
|
|
116
|
+
assert "msg_1" in out
|
|
117
|
+
params = client.calls["send_message"]["args"][0]
|
|
118
|
+
assert params.from_number == "Support line"
|
|
119
|
+
assert params.from_number_id is None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_start_typing_invokes_client():
|
|
123
|
+
client = FakeClient(start_typing=None)
|
|
124
|
+
tool = start_typing_tool(client)
|
|
125
|
+
assert tool.name == "start_typing"
|
|
126
|
+
out = _run(tool, to_number="+14155550123", from_number="pn_9")
|
|
127
|
+
|
|
128
|
+
assert "Typing indicator shown" in out
|
|
129
|
+
call = client.calls["start_typing"]
|
|
130
|
+
assert call["args"] == ()
|
|
131
|
+
assert call["kwargs"] == {"to_number": "+14155550123", "from_number": "pn_9"}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_stop_typing_invokes_client():
|
|
135
|
+
client = FakeClient(stop_typing=None)
|
|
136
|
+
tool = stop_typing_tool(client)
|
|
137
|
+
assert tool.name == "stop_typing"
|
|
138
|
+
out = _run(tool, to_number="+14155550123", from_number="+14155559999")
|
|
139
|
+
|
|
140
|
+
assert "Typing indicator cleared" in out
|
|
141
|
+
call = client.calls["stop_typing"]
|
|
142
|
+
assert call["args"] == ()
|
|
143
|
+
assert call["kwargs"] == {
|
|
144
|
+
"to_number": "+14155550123",
|
|
145
|
+
"from_number": "+14155559999",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
107
149
|
def test_reply_to_message_sends_body_or_reaction():
|
|
108
150
|
client = FakeClient(reply_to_message=_FakeMessage())
|
|
109
151
|
tool = reply_to_message_tool(client)
|
|
@@ -176,6 +218,22 @@ def test_make_call_passes_idempotency_key():
|
|
|
176
218
|
assert params.outbound_instruction == "Confirm the booking."
|
|
177
219
|
|
|
178
220
|
|
|
221
|
+
def test_make_call_passes_from_number():
|
|
222
|
+
client = FakeClient(make_call=_FakeCall())
|
|
223
|
+
tool = make_call_tool(client)
|
|
224
|
+
out = _run(
|
|
225
|
+
tool,
|
|
226
|
+
to="+14155550123",
|
|
227
|
+
from_number="+14155559999",
|
|
228
|
+
outbound_instruction="Confirm the booking.",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
assert "call_x" in out
|
|
232
|
+
params = client.calls["make_call"]["args"][0]
|
|
233
|
+
assert params.from_number == "+14155559999"
|
|
234
|
+
assert params.from_number_id is None
|
|
235
|
+
|
|
236
|
+
|
|
179
237
|
def test_wait_for_message_unknown_id():
|
|
180
238
|
# list_numbers returns no match → reports the bad id without opening a stream.
|
|
181
239
|
tool = wait_for_message_tool(FakeClient(list_numbers=[]))
|
|
@@ -345,7 +345,7 @@ wheels = [
|
|
|
345
345
|
|
|
346
346
|
[[package]]
|
|
347
347
|
name = "dial-agentframework"
|
|
348
|
-
version = "0.
|
|
348
|
+
version = "0.14.0"
|
|
349
349
|
source = { editable = "." }
|
|
350
350
|
dependencies = [
|
|
351
351
|
{ name = "agent-framework-core" },
|
|
@@ -368,7 +368,7 @@ dev = [{ name = "pytest", specifier = ">=8" }]
|
|
|
368
368
|
|
|
369
369
|
[[package]]
|
|
370
370
|
name = "dial-sdk"
|
|
371
|
-
version = "0.
|
|
371
|
+
version = "0.16.0"
|
|
372
372
|
source = { editable = "../sdk-python" }
|
|
373
373
|
dependencies = [
|
|
374
374
|
{ name = "httpx" },
|
|
File without changes
|
|
File without changes
|
{dial_agentframework-0.13.0 → dial_agentframework-0.14.0}/examples/signup_agentframework.ipynb
RENAMED
|
File without changes
|