dial-agentframework 0.14.0__tar.gz → 0.16.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.14.0 → dial_agentframework-0.16.0}/PKG-INFO +3 -3
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/dial_agentframework/__init__.py +2 -0
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/dial_agentframework/tools.py +126 -12
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/pyproject.toml +2 -2
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/tests/test_tools.py +94 -2
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/uv.lock +2 -2
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/.gitignore +0 -0
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/README.md +0 -0
- {dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/examples/signup_agentframework.ipynb +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: dial-agentframework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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.22.0
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
|
|
10
10
|
# dial-agentframework
|
|
@@ -4,6 +4,7 @@ from .tools import (
|
|
|
4
4
|
purchase_number_tool,
|
|
5
5
|
set_number_properties_tool,
|
|
6
6
|
list_messages_tool,
|
|
7
|
+
list_groups_tool,
|
|
7
8
|
send_message_tool,
|
|
8
9
|
reply_to_message_tool,
|
|
9
10
|
start_typing_tool,
|
|
@@ -21,6 +22,7 @@ __all__ = [
|
|
|
21
22
|
"purchase_number_tool",
|
|
22
23
|
"set_number_properties_tool",
|
|
23
24
|
"list_messages_tool",
|
|
25
|
+
"list_groups_tool",
|
|
24
26
|
"send_message_tool",
|
|
25
27
|
"reply_to_message_tool",
|
|
26
28
|
"start_typing_tool",
|
|
@@ -76,6 +76,12 @@ def purchase_number_tool(client: DialClient) -> FunctionTool:
|
|
|
76
76
|
"Voice gender for inbound calls to the new number: 'male' or 'female'. "
|
|
77
77
|
"Omit to use the default female voice.",
|
|
78
78
|
] = None,
|
|
79
|
+
inbound_language: Annotated[
|
|
80
|
+
Optional[str],
|
|
81
|
+
"BCP-47 language tag (e.g. 'es-ES') pinning inbound calls to this number to "
|
|
82
|
+
"one language. Omit to detect the language from the caller's country prefix "
|
|
83
|
+
"on each call.",
|
|
84
|
+
] = None,
|
|
79
85
|
area_code: Annotated[
|
|
80
86
|
Optional[str],
|
|
81
87
|
"Preferred US area code to provision in, e.g. '415'. Omit for any available "
|
|
@@ -94,6 +100,7 @@ def purchase_number_tool(client: DialClient) -> FunctionTool:
|
|
|
94
100
|
explicit_programmatic_consent=explicit_programmatic_consent,
|
|
95
101
|
area_code=area_code,
|
|
96
102
|
inbound_voice_gender=inbound_voice_gender,
|
|
103
|
+
inbound_language=inbound_language,
|
|
97
104
|
include_imessage=include_imessage,
|
|
98
105
|
)
|
|
99
106
|
)
|
|
@@ -108,7 +115,8 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
|
|
|
108
115
|
description=(
|
|
109
116
|
"Update a Dial phone number's properties: its inbound instruction (the system "
|
|
110
117
|
"prompt its AI voice agent uses on inbound calls) and/or its nickname. "
|
|
111
|
-
"
|
|
118
|
+
"On iMessage numbers you can also set the display identity (first/last name "
|
|
119
|
+
"and avatar photo) shown to recipients. Provide at least one."
|
|
112
120
|
),
|
|
113
121
|
)
|
|
114
122
|
async def set_number_properties(
|
|
@@ -121,6 +129,12 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
|
|
|
121
129
|
"Voice gender for inbound calls to this number: 'male' or 'female'. "
|
|
122
130
|
"Pass an empty string to clear it (reverts to the default, female).",
|
|
123
131
|
] = None,
|
|
132
|
+
inbound_language: Annotated[
|
|
133
|
+
Optional[str],
|
|
134
|
+
"BCP-47 language tag (e.g. 'es-ES') pinning inbound calls to this number to "
|
|
135
|
+
"one language. Pass an empty string to clear it (reverts to detecting the "
|
|
136
|
+
"language from the caller's country prefix on each call).",
|
|
137
|
+
] = None,
|
|
124
138
|
nickname: Annotated[
|
|
125
139
|
Optional[str],
|
|
126
140
|
"Human-readable label for the number, e.g. 'Support line'. "
|
|
@@ -132,6 +146,24 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
|
|
|
132
146
|
"ceiling to both inbound and outbound calls. The effective cap on a call is "
|
|
133
147
|
"the smallest of the per-number, account, and per-call limits.",
|
|
134
148
|
] = None,
|
|
149
|
+
first_name: Annotated[
|
|
150
|
+
Optional[str],
|
|
151
|
+
"First name of the iMessage display identity — the name shown beside this "
|
|
152
|
+
"number's messages in recipients' Messages apps. iMessage numbers only "
|
|
153
|
+
"(400 otherwise). Max 30 chars; pass an empty string to clear it.",
|
|
154
|
+
] = None,
|
|
155
|
+
last_name: Annotated[
|
|
156
|
+
Optional[str],
|
|
157
|
+
"Last name of the iMessage display identity, shown beside first_name. "
|
|
158
|
+
"iMessage numbers only (400 otherwise). Max 30 chars; pass an empty string "
|
|
159
|
+
"to clear it.",
|
|
160
|
+
] = None,
|
|
161
|
+
avatar_url: Annotated[
|
|
162
|
+
Optional[str],
|
|
163
|
+
"Public URL of a jpeg/png/gif/webp image (≤5 MB) the server downloads to use "
|
|
164
|
+
"as the number's iMessage display photo. iMessage numbers only (400 "
|
|
165
|
+
"otherwise). The photo can be replaced but not removed.",
|
|
166
|
+
] = None,
|
|
135
167
|
) -> str:
|
|
136
168
|
kwargs: dict = {}
|
|
137
169
|
if inbound_instruction is not None:
|
|
@@ -139,14 +171,27 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
|
|
|
139
171
|
# Empty string clears the override (reverts to the default, female); else set it.
|
|
140
172
|
if inbound_voice_gender is not None:
|
|
141
173
|
kwargs["inbound_voice_gender"] = inbound_voice_gender or None
|
|
174
|
+
# Empty string clears the pin (reverts to per-call detection from the caller's country prefix); else set it.
|
|
175
|
+
if inbound_language is not None:
|
|
176
|
+
kwargs["inbound_language"] = inbound_language or None
|
|
142
177
|
if nickname is not None:
|
|
143
178
|
kwargs["nickname"] = nickname
|
|
144
179
|
if max_call_duration_seconds is not None:
|
|
145
180
|
kwargs["max_call_duration_seconds"] = max_call_duration_seconds
|
|
181
|
+
# Only forward names the caller actually provided — the client leaves them
|
|
182
|
+
# untouched when unset, but forwarding None would clear the existing name.
|
|
183
|
+
# An empty string is forwarded intentionally to clear the name.
|
|
184
|
+
if first_name is not None:
|
|
185
|
+
kwargs["first_name"] = first_name
|
|
186
|
+
if last_name is not None:
|
|
187
|
+
kwargs["last_name"] = last_name
|
|
188
|
+
if avatar_url is not None:
|
|
189
|
+
kwargs["avatar"] = avatar_url
|
|
146
190
|
if not kwargs:
|
|
147
191
|
return (
|
|
148
192
|
"Provide at least one of inbound_instruction, inbound_voice_gender, "
|
|
149
|
-
"nickname,
|
|
193
|
+
"inbound_language, nickname, max_call_duration_seconds, first_name, "
|
|
194
|
+
"last_name, or avatar_url."
|
|
150
195
|
)
|
|
151
196
|
number = await client.set_number_properties(number_id, **kwargs)
|
|
152
197
|
nick = f' | nickname: "{number.nickname}"' if number.nickname else ""
|
|
@@ -161,7 +206,10 @@ def set_number_properties_tool(client: DialClient) -> FunctionTool:
|
|
|
161
206
|
def list_messages_tool(client: DialClient) -> FunctionTool:
|
|
162
207
|
@tool(
|
|
163
208
|
name="list_messages",
|
|
164
|
-
description=
|
|
209
|
+
description=(
|
|
210
|
+
"List recent inbound and outbound messages on the account. Pass group_id "
|
|
211
|
+
"to read one group conversation."
|
|
212
|
+
),
|
|
165
213
|
)
|
|
166
214
|
async def list_messages(
|
|
167
215
|
number_id: Annotated[
|
|
@@ -170,28 +218,73 @@ def list_messages_tool(client: DialClient) -> FunctionTool:
|
|
|
170
218
|
direction: Annotated[
|
|
171
219
|
Optional[str], "Filter by direction: 'inbound' or 'outbound'"
|
|
172
220
|
] = None,
|
|
221
|
+
group_id: Annotated[
|
|
222
|
+
Optional[str],
|
|
223
|
+
"Restrict to one group conversation (from list_groups). Combines with the "
|
|
224
|
+
"other filters.",
|
|
225
|
+
] = None,
|
|
173
226
|
) -> str:
|
|
174
|
-
messages = await client.list_messages(
|
|
227
|
+
messages = await client.list_messages(
|
|
228
|
+
number_id=number_id, direction=direction, group_id=group_id
|
|
229
|
+
)
|
|
175
230
|
if not messages:
|
|
176
231
|
return "No messages found."
|
|
232
|
+
# A group message has no `to` — the destination is the group — so name the
|
|
233
|
+
# group rather than rendering "None" at the recipient.
|
|
177
234
|
return "\n".join(
|
|
178
|
-
f"[{m.direction}] {m.from_} → {m.to}: {m.body[:50]}"
|
|
235
|
+
f"[{m.direction}] {m.from_} → {m.to or f'group {m.group_id}'}: {m.body[:50]}"
|
|
236
|
+
for m in messages
|
|
179
237
|
)
|
|
180
238
|
|
|
181
239
|
return list_messages
|
|
182
240
|
|
|
183
241
|
|
|
242
|
+
def list_groups_tool(client: DialClient) -> FunctionTool:
|
|
243
|
+
@tool(
|
|
244
|
+
name="list_groups",
|
|
245
|
+
description=(
|
|
246
|
+
"List the group conversations your lines are in. A group is a conversation "
|
|
247
|
+
"that isn't a phone number, so it has an id of its own: pass it as group_id "
|
|
248
|
+
"to send_message, or to list_messages to read that conversation. Groups "
|
|
249
|
+
"exist on WhatsApp lines. A group's name can be empty — it is read live "
|
|
250
|
+
"from the line holding the conversation, so a line that can't answer in "
|
|
251
|
+
"time yields no name rather than hiding the group. There is no join event: "
|
|
252
|
+
"list again to see a group your line was just added to."
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
async def list_groups() -> str:
|
|
256
|
+
groups = await client.list_groups()
|
|
257
|
+
if not groups:
|
|
258
|
+
return "No groups found."
|
|
259
|
+
return "\n".join(
|
|
260
|
+
f"{g.id}: {g.name or '(unnamed)'} (since {g.created_at})" for g in groups
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return list_groups
|
|
264
|
+
|
|
265
|
+
|
|
184
266
|
def send_message_tool(client: DialClient) -> FunctionTool:
|
|
185
267
|
@tool(
|
|
186
268
|
name="send_message",
|
|
187
269
|
description=(
|
|
188
|
-
"Send a message to a phone number
|
|
189
|
-
"On an iMessage number, a single audio
|
|
190
|
-
"message unless force_audio_file is
|
|
270
|
+
"Send a message to a phone number or into a group conversation, optionally "
|
|
271
|
+
"with media attachments (MMS). On an iMessage number, a single audio "
|
|
272
|
+
"attachment is delivered as a voice message unless force_audio_file is "
|
|
273
|
+
"true. Address it with exactly one of to or group_id."
|
|
191
274
|
),
|
|
192
275
|
)
|
|
193
276
|
async def send_message(
|
|
194
|
-
to: Annotated[
|
|
277
|
+
to: Annotated[
|
|
278
|
+
str,
|
|
279
|
+
"Destination phone number in E.164 format, e.g. +1234567890. Provide "
|
|
280
|
+
"exactly one of to or group_id.",
|
|
281
|
+
] = "",
|
|
282
|
+
group_id: Annotated[
|
|
283
|
+
Optional[str],
|
|
284
|
+
"A group conversation to send into (from list_groups), instead of a `to` "
|
|
285
|
+
"number. The sending line comes from the group, so no from-number is "
|
|
286
|
+
"needed. Provide exactly one of to or group_id.",
|
|
287
|
+
] = None,
|
|
195
288
|
from_number: Annotated[
|
|
196
289
|
Optional[str],
|
|
197
290
|
"Number to send from: a phone number ID, one of your numbers in E.164, or "
|
|
@@ -205,7 +298,12 @@ def send_message_tool(client: DialClient) -> FunctionTool:
|
|
|
205
298
|
body: Annotated[
|
|
206
299
|
str, "Message body text; optional when media_urls is given (media-only send)"
|
|
207
300
|
] = "",
|
|
208
|
-
channel: Annotated[
|
|
301
|
+
channel: Annotated[
|
|
302
|
+
Optional[str],
|
|
303
|
+
"Which channel to send on, for a line that carries more than one: "
|
|
304
|
+
"'imessage' or 'whatsapp'. Omit to use the number's own default. "
|
|
305
|
+
"WhatsApp sends are text-only.",
|
|
306
|
+
] = None,
|
|
209
307
|
media_urls: Annotated[
|
|
210
308
|
Optional[list[str]],
|
|
211
309
|
"Publicly reachable http(s) URLs of media to attach (MMS), max 10. "
|
|
@@ -220,6 +318,7 @@ def send_message_tool(client: DialClient) -> FunctionTool:
|
|
|
220
318
|
msg = await client.send_message(
|
|
221
319
|
SendMessageParams(
|
|
222
320
|
to=to,
|
|
321
|
+
group_id=group_id,
|
|
223
322
|
from_number=from_number,
|
|
224
323
|
from_number_id=from_number_id,
|
|
225
324
|
body=body,
|
|
@@ -294,8 +393,16 @@ def start_typing_tool(client: DialClient) -> FunctionTool:
|
|
|
294
393
|
"Number the indicator appears from: a phone number ID, one of your numbers "
|
|
295
394
|
"in E.164, or a nickname",
|
|
296
395
|
],
|
|
396
|
+
channel: Annotated[
|
|
397
|
+
Optional[str],
|
|
398
|
+
"Which channel to show it on, for a line that carries more than one: "
|
|
399
|
+
"'imessage' or 'whatsapp'. Omit to use the number's own default. Typing "
|
|
400
|
+
"inside a group is not supported.",
|
|
401
|
+
] = None,
|
|
297
402
|
) -> str:
|
|
298
|
-
await client.start_typing(
|
|
403
|
+
await client.start_typing(
|
|
404
|
+
to_number=to_number, from_number=from_number, channel=channel
|
|
405
|
+
)
|
|
299
406
|
return "Typing indicator shown (iMessage numbers only — SMS numbers ignore it)."
|
|
300
407
|
|
|
301
408
|
return start_typing
|
|
@@ -319,8 +426,14 @@ def stop_typing_tool(client: DialClient) -> FunctionTool:
|
|
|
319
426
|
"Number the indicator appears from: a phone number ID, one of your numbers "
|
|
320
427
|
"in E.164, or a nickname",
|
|
321
428
|
],
|
|
429
|
+
channel: Annotated[
|
|
430
|
+
Optional[str],
|
|
431
|
+
"Which channel to clear it on. Pass the same channel start_typing was given.",
|
|
432
|
+
] = None,
|
|
322
433
|
) -> str:
|
|
323
|
-
await client.stop_typing(
|
|
434
|
+
await client.stop_typing(
|
|
435
|
+
to_number=to_number, from_number=from_number, channel=channel
|
|
436
|
+
)
|
|
324
437
|
return "Typing indicator cleared."
|
|
325
438
|
|
|
326
439
|
return stop_typing
|
|
@@ -541,6 +654,7 @@ def dial_tools(client: DialClient) -> list[FunctionTool]:
|
|
|
541
654
|
purchase_number_tool(client),
|
|
542
655
|
set_number_properties_tool(client),
|
|
543
656
|
list_messages_tool(client),
|
|
657
|
+
list_groups_tool(client),
|
|
544
658
|
send_message_tool(client),
|
|
545
659
|
reply_to_message_tool(client),
|
|
546
660
|
start_typing_tool(client),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dial-agentframework"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.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.22.0",
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
[tool.uv.sources]
|
|
@@ -3,12 +3,14 @@ import asyncio
|
|
|
3
3
|
from dial_agentframework import (
|
|
4
4
|
dial_tools,
|
|
5
5
|
purchase_number_tool,
|
|
6
|
+
set_number_properties_tool,
|
|
6
7
|
send_message_tool,
|
|
7
8
|
reply_to_message_tool,
|
|
8
9
|
make_call_tool,
|
|
9
10
|
start_typing_tool,
|
|
10
11
|
stop_typing_tool,
|
|
11
12
|
wait_for_message_tool,
|
|
13
|
+
list_groups_tool,
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -30,6 +32,7 @@ class _FakeNumber:
|
|
|
30
32
|
id = "pn_new"
|
|
31
33
|
number = "+14155550000"
|
|
32
34
|
country = "US"
|
|
35
|
+
nickname = None
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
class FakeClient:
|
|
@@ -57,7 +60,7 @@ def _run(tool, **args) -> str:
|
|
|
57
60
|
# ── Tools ────────────────────────────────────────────────────────────────────
|
|
58
61
|
|
|
59
62
|
|
|
60
|
-
def
|
|
63
|
+
def test_dial_tools_bundles_all_fourteen():
|
|
61
64
|
names = sorted(t.name for t in dial_tools(FakeClient()))
|
|
62
65
|
assert names == sorted(
|
|
63
66
|
[
|
|
@@ -65,6 +68,7 @@ def test_dial_tools_bundles_all_thirteen():
|
|
|
65
68
|
"purchase_number",
|
|
66
69
|
"set_number_properties",
|
|
67
70
|
"list_messages",
|
|
71
|
+
"list_groups",
|
|
68
72
|
"send_message",
|
|
69
73
|
"reply_to_message",
|
|
70
74
|
"start_typing",
|
|
@@ -128,7 +132,48 @@ def test_start_typing_invokes_client():
|
|
|
128
132
|
assert "Typing indicator shown" in out
|
|
129
133
|
call = client.calls["start_typing"]
|
|
130
134
|
assert call["args"] == ()
|
|
131
|
-
assert call["kwargs"] == {
|
|
135
|
+
assert call["kwargs"] == {
|
|
136
|
+
"to_number": "+14155550123",
|
|
137
|
+
"from_number": "pn_9",
|
|
138
|
+
"channel": None,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_typing_forwards_an_explicit_channel():
|
|
143
|
+
client = FakeClient(start_typing=None)
|
|
144
|
+
_run(start_typing_tool(client), to_number="+14155550123", from_number="pn_9", channel="whatsapp")
|
|
145
|
+
assert client.calls["start_typing"]["kwargs"]["channel"] == "whatsapp"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_send_message_into_a_group():
|
|
149
|
+
client = FakeClient(send_message=_FakeMessage())
|
|
150
|
+
out = _run(send_message_tool(client), group_id="grp_1", body="hi")
|
|
151
|
+
|
|
152
|
+
assert "Message sent successfully" in out
|
|
153
|
+
params = client.calls["send_message"]["args"][0]
|
|
154
|
+
assert params.group_id == "grp_1"
|
|
155
|
+
# A group send names no recipient number: the group is the destination.
|
|
156
|
+
assert params.to == ""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_list_groups_renders_ids_names_and_an_unnamed_group():
|
|
160
|
+
class _FakeGroup:
|
|
161
|
+
def __init__(self, id, name, created_at):
|
|
162
|
+
self.id = id
|
|
163
|
+
self.name = name
|
|
164
|
+
self.created_at = created_at
|
|
165
|
+
|
|
166
|
+
client = FakeClient(
|
|
167
|
+
list_groups=[
|
|
168
|
+
_FakeGroup("grp_1", "Planning bday party", "2026-09-01T00:00:00Z"),
|
|
169
|
+
_FakeGroup("grp_2", None, "2026-09-02T00:00:00Z"),
|
|
170
|
+
]
|
|
171
|
+
)
|
|
172
|
+
out = _run(list_groups_tool(client))
|
|
173
|
+
|
|
174
|
+
assert "grp_1: Planning bday party" in out
|
|
175
|
+
# A group no line could name is still listed — never hidden.
|
|
176
|
+
assert "grp_2: (unnamed)" in out
|
|
132
177
|
|
|
133
178
|
|
|
134
179
|
def test_stop_typing_invokes_client():
|
|
@@ -143,6 +188,7 @@ def test_stop_typing_invokes_client():
|
|
|
143
188
|
assert call["kwargs"] == {
|
|
144
189
|
"to_number": "+14155550123",
|
|
145
190
|
"from_number": "+14155559999",
|
|
191
|
+
"channel": None,
|
|
146
192
|
}
|
|
147
193
|
|
|
148
194
|
|
|
@@ -195,10 +241,56 @@ def test_purchase_number_include_imessage():
|
|
|
195
241
|
inbound_instruction="Greet the caller.",
|
|
196
242
|
explicit_programmatic_consent="Account holder approved programmatic provisioning.",
|
|
197
243
|
include_imessage=True,
|
|
244
|
+
inbound_language="es-ES",
|
|
198
245
|
)
|
|
199
246
|
|
|
200
247
|
params = client.calls["purchase_number"]["args"][0]
|
|
201
248
|
assert params.include_imessage is True
|
|
249
|
+
assert params.inbound_language == "es-ES"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def test_set_number_properties_forwards_display_identity():
|
|
253
|
+
client = FakeClient(set_number_properties=_FakeNumber())
|
|
254
|
+
tool = set_number_properties_tool(client)
|
|
255
|
+
out = _run(
|
|
256
|
+
tool,
|
|
257
|
+
number_id="pn_9",
|
|
258
|
+
first_name="Ada",
|
|
259
|
+
last_name="Lovelace",
|
|
260
|
+
avatar_url="https://cdn.example.com/ada.png",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
assert "pn_new" in out
|
|
264
|
+
call = client.calls["set_number_properties"]
|
|
265
|
+
assert call["args"] == ("pn_9",)
|
|
266
|
+
assert call["kwargs"]["first_name"] == "Ada"
|
|
267
|
+
assert call["kwargs"]["last_name"] == "Lovelace"
|
|
268
|
+
# avatar_url forwards to the client's `avatar` param (a URL string).
|
|
269
|
+
assert call["kwargs"]["avatar"] == "https://cdn.example.com/ada.png"
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def test_set_number_properties_empty_name_clears():
|
|
273
|
+
# An explicit empty string is forwarded (the server clears the name); it must
|
|
274
|
+
# not be swallowed like an omitted argument.
|
|
275
|
+
client = FakeClient(set_number_properties=_FakeNumber())
|
|
276
|
+
tool = set_number_properties_tool(client)
|
|
277
|
+
_run(tool, number_id="pn_9", first_name="")
|
|
278
|
+
|
|
279
|
+
assert client.calls["set_number_properties"]["kwargs"]["first_name"] == ""
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_set_number_properties_omits_unprovided_name_fields():
|
|
283
|
+
# Not passing first_name/last_name/avatar_url must NOT forward them — the client
|
|
284
|
+
# uses an _UNSET sentinel and forwarding None would clear the existing identity.
|
|
285
|
+
client = FakeClient(set_number_properties=_FakeNumber())
|
|
286
|
+
tool = set_number_properties_tool(client)
|
|
287
|
+
_run(tool, number_id="pn_9", nickname="Support")
|
|
288
|
+
|
|
289
|
+
kwargs = client.calls["set_number_properties"]["kwargs"]
|
|
290
|
+
assert "first_name" not in kwargs
|
|
291
|
+
assert "last_name" not in kwargs
|
|
292
|
+
assert "avatar" not in kwargs
|
|
293
|
+
assert kwargs["nickname"] == "Support"
|
|
202
294
|
|
|
203
295
|
|
|
204
296
|
def test_make_call_passes_idempotency_key():
|
|
@@ -345,7 +345,7 @@ wheels = [
|
|
|
345
345
|
|
|
346
346
|
[[package]]
|
|
347
347
|
name = "dial-agentframework"
|
|
348
|
-
version = "0.
|
|
348
|
+
version = "0.16.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.22.0"
|
|
372
372
|
source = { editable = "../sdk-python" }
|
|
373
373
|
dependencies = [
|
|
374
374
|
{ name = "httpx" },
|
|
File without changes
|
|
File without changes
|
{dial_agentframework-0.14.0 → dial_agentframework-0.16.0}/examples/signup_agentframework.ipynb
RENAMED
|
File without changes
|