dial-agentframework 0.16.0__tar.gz → 0.18.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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dial-agentframework
3
- Version: 0.16.0
3
+ Version: 0.18.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.22.0
7
+ Requires-Dist: dial-sdk>=0.28.0
8
8
  Description-Content-Type: text/markdown
9
9
 
10
10
  # dial-agentframework
@@ -85,7 +85,8 @@ Each builder takes your shared `DialClient`:
85
85
  | `set_number_properties_tool` | `set_number_properties` | Update a number's nickname / inbound instruction |
86
86
  | `send_message_tool` | `send_message` | Send an SMS (optionally MMS) |
87
87
  | `reply_to_message_tool` | `reply_to_message` | Reply or react to a message |
88
- | `list_messages_tool` | `list_messages` | List recent messages |
88
+ | `list_messages_tool` | `list_messages` | List recent messages (`contact` for one conversation, `search` by text) |
89
+ | `list_contacts_tool` | `list_contacts` | List the numbers your lines have talked to |
89
90
  | `make_call_tool` | `make_call` | Place an AI voice call |
90
91
  | `list_calls_tool` | `list_calls` | List recent calls |
91
92
  | `get_call_tool` | `get_call` | Fetch one call by id |
@@ -76,7 +76,8 @@ Each builder takes your shared `DialClient`:
76
76
  | `set_number_properties_tool` | `set_number_properties` | Update a number's nickname / inbound instruction |
77
77
  | `send_message_tool` | `send_message` | Send an SMS (optionally MMS) |
78
78
  | `reply_to_message_tool` | `reply_to_message` | Reply or react to a message |
79
- | `list_messages_tool` | `list_messages` | List recent messages |
79
+ | `list_messages_tool` | `list_messages` | List recent messages (`contact` for one conversation, `search` by text) |
80
+ | `list_contacts_tool` | `list_contacts` | List the numbers your lines have talked to |
80
81
  | `make_call_tool` | `make_call` | Place an AI voice call |
81
82
  | `list_calls_tool` | `list_calls` | List recent calls |
82
83
  | `get_call_tool` | `get_call` | Fetch one call by id |
@@ -5,6 +5,7 @@ from .tools import (
5
5
  set_number_properties_tool,
6
6
  list_messages_tool,
7
7
  list_groups_tool,
8
+ list_contacts_tool,
8
9
  send_message_tool,
9
10
  reply_to_message_tool,
10
11
  start_typing_tool,
@@ -23,6 +24,7 @@ __all__ = [
23
24
  "set_number_properties_tool",
24
25
  "list_messages_tool",
25
26
  "list_groups_tool",
27
+ "list_contacts_tool",
26
28
  "send_message_tool",
27
29
  "reply_to_message_tool",
28
30
  "start_typing_tool",
@@ -114,7 +114,8 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
114
114
  name="set_number_properties",
115
115
  description=(
116
116
  "Update a Dial phone number's properties: its inbound instruction (the system "
117
- "prompt its AI voice agent uses on inbound calls) and/or its nickname. "
117
+ "prompt its AI voice agent uses on inbound calls), its nickname, and/or whether "
118
+ "calling is switched on at all (calling_enabled). "
118
119
  "On iMessage numbers you can also set the display identity (first/last name "
119
120
  "and avatar photo) shown to recipients. Provide at least one."
120
121
  ),
@@ -164,6 +165,14 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
164
165
  "as the number's iMessage display photo. iMessage numbers only (400 "
165
166
  "otherwise). The photo can be replaced but not removed.",
166
167
  ] = None,
168
+ calling_enabled: Annotated[
169
+ Optional[bool],
170
+ "Switch calling on or off for this number, in both directions. False stops "
171
+ "inbound calls from being connected (the caller is never answered) and makes "
172
+ "place_call from this number fail with calling_disabled (409); messaging on "
173
+ "the number is unaffected. Takes effect on the next call — a call already in "
174
+ "progress is not ended. Omit to leave it unchanged.",
175
+ ] = None,
167
176
  ) -> str:
168
177
  kwargs: dict = {}
169
178
  if inbound_instruction is not None:
@@ -187,15 +196,20 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
187
196
  kwargs["last_name"] = last_name
188
197
  if avatar_url is not None:
189
198
  kwargs["avatar"] = avatar_url
199
+ # False is a real value here; only None means "leave unchanged".
200
+ if calling_enabled is not None:
201
+ kwargs["calling_enabled"] = calling_enabled
190
202
  if not kwargs:
191
203
  return (
192
204
  "Provide at least one of inbound_instruction, inbound_voice_gender, "
193
- "inbound_language, nickname, max_call_duration_seconds, first_name, "
194
- "last_name, or avatar_url."
205
+ "inbound_language, nickname, max_call_duration_seconds, calling_enabled, "
206
+ "first_name, last_name, or avatar_url."
195
207
  )
196
208
  number = await client.set_number_properties(number_id, **kwargs)
197
209
  nick = f' | nickname: "{number.nickname}"' if number.nickname else ""
198
- return f"Updated {number.number} (id: {number.id}){nick}"
210
+ # Reported only when off, so a messaging-only line isn't a silent surprise.
211
+ calling = "" if number.calling_enabled else " | calling: off (messaging only)"
212
+ return f"Updated {number.number} (id: {number.id}){nick}{calling}"
199
213
 
200
214
  return set_number_properties
201
215
 
@@ -207,8 +221,10 @@ def list_messages_tool(client: DialClient) -> FunctionTool:
207
221
  @tool(
208
222
  name="list_messages",
209
223
  description=(
210
- "List recent inbound and outbound messages on the account. Pass group_id "
211
- "to read one group conversation."
224
+ "List recent inbound and outbound messages on the account. Unfiltered this "
225
+ "is the newest 100 messages across every conversation, so pass contact to "
226
+ "read one person's conversation, group_id to read one group's, or search to "
227
+ "find a message by its text anywhere in the history."
212
228
  ),
213
229
  )
214
230
  async def list_messages(
@@ -223,9 +239,24 @@ def list_messages_tool(client: DialClient) -> FunctionTool:
223
239
  "Restrict to one group conversation (from list_groups). Combines with the "
224
240
  "other filters.",
225
241
  ] = None,
242
+ contact: Annotated[
243
+ Optional[str],
244
+ "One contact's conversation: messages exchanged with this number (E.164), "
245
+ "both directions, across every line on the account. Group messages are never "
246
+ "included - use group_id for those.",
247
+ ] = None,
248
+ search: Annotated[
249
+ Optional[str],
250
+ "Case-insensitive substring match on the message body. Searches the whole "
251
+ "history, then returns the 100 most recent matches.",
252
+ ] = None,
226
253
  ) -> str:
227
254
  messages = await client.list_messages(
228
- number_id=number_id, direction=direction, group_id=group_id
255
+ number_id=number_id,
256
+ direction=direction,
257
+ group_id=group_id,
258
+ contact=contact,
259
+ search=search,
229
260
  )
230
261
  if not messages:
231
262
  return "No messages found."
@@ -439,13 +470,69 @@ def stop_typing_tool(client: DialClient) -> FunctionTool:
439
470
  return stop_typing
440
471
 
441
472
 
473
+ # ── Contacts ─────────────────────────────────────────────────────────────────
474
+
475
+
476
+ def list_contacts_tool(client: DialClient) -> FunctionTool:
477
+ @tool(
478
+ name="list_contacts",
479
+ description=(
480
+ "List every number the account's lines have exchanged a message or a call "
481
+ "with, newest activity first, with per-contact counts and a summary of the "
482
+ "most recent interaction. Derived from history - there is no address book to "
483
+ "add anyone to. Counts span every line, so one person reached on two of your "
484
+ "numbers is one contact. Groups are not contacts; use list_groups for those. "
485
+ "Pass a contact's number to list_messages as contact to read that "
486
+ "conversation."
487
+ ),
488
+ )
489
+ async def list_contacts(
490
+ limit: Annotated[Optional[int], "Max contacts to return (1-1000; default 100)"] = None,
491
+ starting_after: Annotated[
492
+ Optional[str],
493
+ "ISO-8601 cursor - the last_at of the last contact you received. Only "
494
+ "contacts with strictly older activity are returned.",
495
+ ] = None,
496
+ ) -> str:
497
+ page = await client.list_contacts(limit=limit, starting_after=starting_after)
498
+ if not page.contacts:
499
+ return "No contacts found."
500
+ lines = []
501
+ for c in page.contacts:
502
+ if c.last_kind == "call":
503
+ preview = f"{c.last_direction} call"
504
+ if c.last_call_duration:
505
+ preview += f", {c.last_call_duration}s"
506
+ elif c.last_redacted:
507
+ preview = "(deleted by retention)"
508
+ elif not c.last_body:
509
+ preview = f"({c.last_media_count} media)" if c.last_media_count else ""
510
+ else:
511
+ preview = c.last_body[:50]
512
+ lines.append(
513
+ f"{c.number} | {c.message_count} msg, {c.call_count} call | "
514
+ f"{c.last_at} | {preview}"
515
+ )
516
+ if page.has_more:
517
+ # Named so the model can ask for the next page instead of assuming this is all.
518
+ lines.append(
519
+ f"(more contacts: call again with starting_after={page.contacts[-1].last_at})"
520
+ )
521
+ return "\n".join(lines)
522
+
523
+ return list_contacts
524
+
525
+
442
526
  # ── Calls ────────────────────────────────────────────────────────────────────
443
527
 
444
528
 
445
529
  def list_calls_tool(client: DialClient) -> FunctionTool:
446
530
  @tool(
447
531
  name="list_calls",
448
- description="List recent inbound and outbound calls on the account.",
532
+ description=(
533
+ "List recent inbound and outbound calls on the account. Pass contact to read "
534
+ "one person's call history."
535
+ ),
449
536
  )
450
537
  async def list_calls(
451
538
  number_id: Annotated[
@@ -454,8 +541,15 @@ def list_calls_tool(client: DialClient) -> FunctionTool:
454
541
  direction: Annotated[
455
542
  Optional[str], "Filter by direction: 'inbound' or 'outbound'"
456
543
  ] = None,
544
+ contact: Annotated[
545
+ Optional[str],
546
+ "One contact's calls: exchanged with this number (E.164), both directions, "
547
+ "across every line on the account.",
548
+ ] = None,
457
549
  ) -> str:
458
- calls = await client.list_calls(number_id=number_id, direction=direction)
550
+ calls = await client.list_calls(
551
+ number_id=number_id, direction=direction, contact=contact
552
+ )
459
553
  if not calls:
460
554
  return "No calls found."
461
555
  return "\n".join(
@@ -655,6 +749,7 @@ def dial_tools(client: DialClient) -> list[FunctionTool]:
655
749
  set_number_properties_tool(client),
656
750
  list_messages_tool(client),
657
751
  list_groups_tool(client),
752
+ list_contacts_tool(client),
658
753
  send_message_tool(client),
659
754
  reply_to_message_tool(client),
660
755
  start_typing_tool(client),
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dial-agentframework"
3
- version = "0.16.0"
3
+ version = "0.18.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.22.0",
11
+ "dial-sdk>=0.28.0",
12
12
  ]
13
13
 
14
14
  [tool.uv.sources]
@@ -33,6 +33,7 @@ class _FakeNumber:
33
33
  number = "+14155550000"
34
34
  country = "US"
35
35
  nickname = None
36
+ calling_enabled = True
36
37
 
37
38
 
38
39
  class FakeClient:
@@ -60,7 +61,7 @@ def _run(tool, **args) -> str:
60
61
  # ── Tools ────────────────────────────────────────────────────────────────────
61
62
 
62
63
 
63
- def test_dial_tools_bundles_all_fourteen():
64
+ def test_dial_tools_bundles_all_fifteen():
64
65
  names = sorted(t.name for t in dial_tools(FakeClient()))
65
66
  assert names == sorted(
66
67
  [
@@ -69,6 +70,7 @@ def test_dial_tools_bundles_all_fourteen():
69
70
  "set_number_properties",
70
71
  "list_messages",
71
72
  "list_groups",
73
+ "list_contacts",
72
74
  "send_message",
73
75
  "reply_to_message",
74
76
  "start_typing",
@@ -345,7 +345,7 @@ wheels = [
345
345
 
346
346
  [[package]]
347
347
  name = "dial-agentframework"
348
- version = "0.16.0"
348
+ version = "0.18.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.22.0"
371
+ version = "0.28.0"
372
372
  source = { editable = "../sdk-python" }
373
373
  dependencies = [
374
374
  { name = "httpx" },