wiil-python 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl

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.
@@ -181,9 +181,9 @@ class HttpClient:
181
181
  try:
182
182
  if isinstance(data, dict):
183
183
  validated_data = schema(**data)
184
- data = validated_data.model_dump(exclude_none=True)
184
+ data = validated_data.model_dump(by_alias=True, exclude_none=True)
185
185
  elif isinstance(data, BaseModel):
186
- data = data.model_dump(exclude_none=True)
186
+ data = data.model_dump(by_alias=True, exclude_none=True)
187
187
  except ValidationError as e:
188
188
  raise WiilValidationError(
189
189
  'Request validation failed',
@@ -275,9 +275,9 @@ class HttpClient:
275
275
  try:
276
276
  if isinstance(data, dict):
277
277
  validated_data = schema(**data)
278
- data = validated_data.model_dump(exclude_none=True)
278
+ data = validated_data.model_dump(by_alias=True, exclude_none=True)
279
279
  elif isinstance(data, BaseModel):
280
- data = data.model_dump(exclude_none=True)
280
+ data = data.model_dump(by_alias=True, exclude_none=True)
281
281
  except ValidationError as e:
282
282
  raise WiilValidationError(
283
283
  'Request validation failed',
@@ -369,9 +369,9 @@ class HttpClient:
369
369
  try:
370
370
  if isinstance(data, dict):
371
371
  validated_data = schema(**data)
372
- data = validated_data.model_dump(exclude_none=True)
372
+ data = validated_data.model_dump(by_alias=True, exclude_none=True)
373
373
  elif isinstance(data, BaseModel):
374
- data = data.model_dump(exclude_none=True)
374
+ data = data.model_dump(by_alias=True, exclude_none=True)
375
375
  except ValidationError as e:
376
376
  raise WiilValidationError(
377
377
  'Request validation failed',
@@ -6,7 +6,7 @@ calendar integration and service provider assignment.
6
6
 
7
7
  from typing import Optional
8
8
 
9
- from pydantic import BaseModel as PydanticBaseModel
9
+ from pydantic import BaseModel as PydanticBaseModel
10
10
  from pydantic import ConfigDict, EmailStr, Field
11
11
 
12
12
  from wiil.models.base import BaseModel
@@ -154,12 +154,9 @@ class CreateServiceAppointment(PydanticBaseModel):
154
154
  create_data = CreateServiceAppointment(
155
155
  business_service_id="service_123",
156
156
  customer_id="cust_456",
157
- customer_name="Jane Smith",
158
- customer_email="jane@example.com",
159
157
  start_time=1234567890,
160
158
  duration=45,
161
- total_price=75.00,
162
- status=AppointmentStatus.PENDING
159
+ total_price=75.00
163
160
  )
164
161
  ```
165
162
  """
@@ -172,19 +169,15 @@ class CreateServiceAppointment(PydanticBaseModel):
172
169
 
173
170
  business_service_id: str = Field(..., alias="businessServiceId")
174
171
  customer_id: str = Field(..., alias="customerId")
175
- customer_name: Optional[str] = Field(None, alias="customerName")
176
- customer_email: Optional[EmailStr] = Field(None, alias="customerEmail")
177
172
  start_time: int = Field(..., alias="startTime")
178
173
  end_time: Optional[int] = Field(None, alias="endTime")
179
- duration: Optional[int] = Field(30, gt=0)
180
- total_price: Optional[float] = Field(0.0, ge=0, alias="totalPrice")
174
+ duration: Optional[int] = Field(None, gt=0)
175
+ total_price: Optional[float] = Field(None, ge=0, alias="totalPrice")
181
176
  deposit_paid: float = Field(0.0, ge=0, alias="depositPaid")
182
- status: AppointmentStatus = AppointmentStatus.PENDING
183
177
  assigned_user_account_id: Optional[str] = Field(None, alias="assignedUserAccountId")
184
178
  calendar_id: Optional[str] = Field(None, alias="calendarId")
185
179
  calendar_event_id: Optional[str] = Field(None, alias="calendarEventId")
186
180
  calendar_provider: Optional[CalendarProvider] = Field(None, alias="calendarProvider")
187
- service_conversation_config_id: Optional[str] = Field(None, alias="serviceConversationConfigId")
188
181
 
189
182
 
190
183
  class UpdateServiceAppointment(PydanticBaseModel):
@@ -196,7 +189,6 @@ class UpdateServiceAppointment(PydanticBaseModel):
196
189
  ```python
197
190
  update_data = UpdateServiceAppointment(
198
191
  id="appt_123",
199
- status=AppointmentStatus.COMPLETED,
200
192
  end_time=1234567950
201
193
  )
202
194
  ```
@@ -211,17 +203,13 @@ class UpdateServiceAppointment(PydanticBaseModel):
211
203
  id: str
212
204
  business_service_id: Optional[str] = Field(None, alias="businessServiceId")
213
205
  customer_id: Optional[str] = Field(None, alias="customerId")
214
- customer_name: Optional[str] = Field(None, alias="customerName")
215
- customer_email: Optional[EmailStr] = Field(None, alias="customerEmail")
216
206
  start_time: Optional[int] = Field(None, alias="startTime")
217
207
  end_time: Optional[int] = Field(None, alias="endTime")
218
208
  duration: Optional[int] = Field(None, gt=0)
219
209
  total_price: Optional[float] = Field(None, ge=0, alias="totalPrice")
220
210
  deposit_paid: Optional[float] = Field(None, ge=0, alias="depositPaid")
221
- status: Optional[AppointmentStatus] = None
222
211
  assigned_user_account_id: Optional[str] = Field(None, alias="assignedUserAccountId")
223
212
  calendar_id: Optional[str] = Field(None, alias="calendarId")
224
213
  calendar_event_id: Optional[str] = Field(None, alias="calendarEventId")
225
214
  calendar_provider: Optional[CalendarProvider] = Field(None, alias="calendarProvider")
226
215
  cancel_reason: Optional[str] = Field(None, alias="cancelReason")
227
- service_conversation_config_id: Optional[str] = Field(None, alias="serviceConversationConfigId")
@@ -19,9 +19,18 @@ from wiil.resources.service_mgt.translation_sessions import TranslationSessionsR
19
19
  from wiil.resources.service_mgt.knowledge_sources import KnowledgeSourcesResource
20
20
  from wiil.resources.service_mgt.support_models import SupportModelsResource
21
21
  from wiil.resources.service_mgt.telephony_provider import TelephonyProviderResource
22
- from wiil.resources.service_mgt.dynamic_agent_status import DynamicAgentStatusResource
23
- from wiil.resources.service_mgt.dynamic_web_agent import DynamicWebAgentResource
24
- from wiil.resources.service_mgt.dynamic_phone_agent import DynamicPhoneAgentResource
22
+ from wiil.resources.service_mgt.dynamic_agent_status import (
23
+ DynamicAgentStatusResource,
24
+ PollTimeoutError,
25
+ )
26
+ from wiil.resources.service_mgt.dynamic_web_agent import (
27
+ DynamicWebAgentResource,
28
+ WebAgentCreateOptions,
29
+ )
30
+ from wiil.resources.service_mgt.dynamic_phone_agent import (
31
+ DynamicPhoneAgentResource,
32
+ PhoneAgentCreateOptions,
33
+ )
25
34
 
26
35
  __all__ = [
27
36
  'AgentConfigurationsResource',
@@ -36,6 +45,9 @@ __all__ = [
36
45
  'SupportModelsResource',
37
46
  'TelephonyProviderResource',
38
47
  'DynamicAgentStatusResource',
48
+ 'PollTimeoutError',
39
49
  'DynamicWebAgentResource',
50
+ 'WebAgentCreateOptions',
40
51
  'DynamicPhoneAgentResource',
52
+ 'PhoneAgentCreateOptions',
41
53
  ]
@@ -2,10 +2,19 @@
2
2
 
3
3
  from wiil.client.http_client import HttpClient
4
4
  from wiil.models.service_mgt.dynamic_setup import (
5
+ DynamicAgentProcessingState,
5
6
  DynamicAgentSetupResult,
6
7
  )
7
8
 
8
9
 
10
+ class PollTimeoutError(Exception):
11
+ """Raised when polling times out before completion."""
12
+
13
+ def __init__(self, message: str, last_state: DynamicAgentProcessingState):
14
+ super().__init__(message)
15
+ self.last_state = last_state
16
+
17
+
9
18
  class DynamicAgentStatusResource:
10
19
  """Resource class for checking dynamic agent setup status.
11
20
 
@@ -32,4 +41,4 @@ class DynamicAgentStatusResource:
32
41
  )
33
42
 
34
43
 
35
- __all__ = ['DynamicAgentStatusResource']
44
+ __all__ = ['DynamicAgentStatusResource', 'PollTimeoutError']
@@ -1,74 +1,190 @@
1
- """Dynamic Phone Agent resource for managing phone agent configurations."""
1
+ """Dynamic Phone Agent resource for phone-based AI agents."""
2
2
 
3
- from typing import Any, Dict, Optional
4
- from urllib.parse import urlencode
3
+ import time
4
+ from dataclasses import dataclass
5
+ from typing import Callable, Optional
5
6
 
6
7
  from wiil.client.http_client import HttpClient
8
+ from wiil.errors import WiilValidationError
7
9
  from wiil.models.service_mgt.dynamic_setup import (
10
+ DynamicAgentProcessingState,
8
11
  DynamicPhoneAgentSetup,
9
12
  DynamicPhoneAgentSetupResult,
13
+ DynamicSTTModelConfiguration,
14
+ DynamicTTSModelConfiguration,
10
15
  UpdateDynamicPhoneAgent,
11
16
  )
12
- from wiil.types import PaginatedResult, PaginationRequest
17
+ from wiil.resources.service_mgt.dynamic_agent_status import PollTimeoutError
18
+
19
+
20
+ @dataclass
21
+ class PhoneAgentCreateOptions:
22
+ """Options for configuring agent creation behavior."""
23
+
24
+ poll_until_complete: bool = True
25
+ """Whether to poll until setup completes."""
26
+
27
+ poll_interval: float = 5.0
28
+ """Polling interval in seconds."""
29
+
30
+ poll_timeout: float = 120.0
31
+ """Maximum wait time in seconds before timing out."""
32
+
33
+ on_progress: Optional[Callable[[DynamicAgentProcessingState], None]] = None
34
+ """Callback invoked on each poll with the current processing state."""
35
+
36
+ silent: bool = False
37
+ """Whether to suppress console logging."""
13
38
 
14
39
 
15
40
  class DynamicPhoneAgentResource:
16
- """Resource class for managing dynamic phone agents in the WIIL Platform.
41
+ """Resource class for managing dynamic phone agent provisioning.
42
+
43
+ Provides methods for creating and updating dynamic phone agent
44
+ configurations. Dynamic phone agents enable AI-powered phone
45
+ interactions with configurable STT and TTS capabilities.
17
46
 
18
- Provides methods for creating, retrieving, updating, deleting, and listing
19
- dynamic phone agent configurations. Phone agents are deployed on telephony
20
- channels for voice-based customer interactions.
47
+ Example:
48
+ >>> from wiil.models.type_definitions import (
49
+ ... AgentCapabilities,
50
+ ... AgentRoleTemplateIdentifier,
51
+ ... )
52
+ >>>
53
+ >>> client = WiilClient(api_key='your-api-key')
54
+ >>>
55
+ >>> # Create with polling (waits for completion)
56
+ >>> result = client.dynamic_phone_agent.create(
57
+ ... DynamicPhoneAgentSetup(
58
+ ... assistant_name='Alex',
59
+ ... capabilities=[AgentCapabilities.APPOINTMENT_MANAGEMENT],
60
+ ... role_template_identifier=(
61
+ ... AgentRoleTemplateIdentifier.CUSTOMER_SUPPORT_GENERAL
62
+ ... ),
63
+ ... )
64
+ ... )
65
+ >>>
66
+ >>> # Create without waiting
67
+ >>> result = client.dynamic_phone_agent.create(
68
+ ... data,
69
+ ... options=PhoneAgentCreateOptions(poll_until_complete=False)
70
+ ... )
21
71
  """
22
72
 
23
73
  def __init__(self, http: HttpClient):
24
74
  self._http = http
25
- self._base_path = '/dynamic-phone-agent'
75
+ self._base_path = '/dynamic-setup/phone-agent'
76
+ self._status_path = '/dynamic-setup'
26
77
 
27
78
  def create(
28
79
  self,
29
- data: DynamicPhoneAgentSetup
80
+ data: DynamicPhoneAgentSetup,
81
+ options: Optional[PhoneAgentCreateOptions] = None
30
82
  ) -> DynamicPhoneAgentSetupResult:
31
- """Create a new dynamic phone agent.
83
+ """Create and provision a new dynamic phone agent.
32
84
 
33
85
  Args:
34
- data: Phone agent setup data
86
+ data: Phone agent configuration data
87
+ options: Creation options for polling and logging behavior
35
88
 
36
89
  Returns:
37
- The setup result with processing status
90
+ The setup result including provisioned phone number
91
+
92
+ Raises:
93
+ WiilValidationError: When validation fails or model not supported
94
+ WiilAPIError: When the API returns an error
95
+ PollTimeoutError: When polling times out before completion
38
96
  """
39
- return self._http.post(
97
+ opts = options or PhoneAgentCreateOptions()
98
+ start_time = time.time()
99
+ agent_name = data.assistant_name
100
+
101
+ # Validate model configurations before proceeding
102
+ self._validate_model_configurations(
103
+ data.stt_configuration,
104
+ data.tts_configuration
105
+ )
106
+
107
+ # Log initial creation
108
+ if not opts.silent:
109
+ self._log(f'Creating agent "{agent_name}"...')
110
+
111
+ # Create the agent
112
+ initial_result = self._http.post(
40
113
  self._base_path,
41
114
  data.model_dump(by_alias=True, exclude_none=True),
42
115
  schema=DynamicPhoneAgentSetup,
43
116
  response_model=DynamicPhoneAgentSetupResult
44
117
  )
45
118
 
46
- def get(self, agent_id: str) -> DynamicPhoneAgentSetupResult:
47
- """Retrieve a dynamic phone agent by ID.
119
+ # If not polling, return immediately
120
+ if not opts.poll_until_complete:
121
+ if not opts.silent:
122
+ self._log(f'Setup initiated (ID: {initial_result.id})')
123
+ return initial_result
48
124
 
49
- Args:
50
- agent_id: Phone agent ID
125
+ # Check if already complete
126
+ if initial_result.processing_state.status in ('completed', 'failed'):
127
+ self._log_final_result(initial_result, start_time, opts.silent)
128
+ return initial_result
51
129
 
52
- Returns:
53
- The phone agent setup result
54
- """
55
- return self._http.get(
56
- f'{self._base_path}/{agent_id}',
57
- response_model=DynamicPhoneAgentSetupResult
58
- )
130
+ # Poll until complete
131
+ while True:
132
+ result = self._http.get(
133
+ f'{self._status_path}/{initial_result.id}',
134
+ response_model=DynamicPhoneAgentSetupResult
135
+ )
136
+ last_state = result.processing_state
137
+
138
+ # Log progress
139
+ if not opts.silent:
140
+ self._log_progress(last_state)
141
+
142
+ # Invoke callback
143
+ if opts.on_progress:
144
+ opts.on_progress(last_state)
145
+
146
+ # Check for terminal states
147
+ if last_state.status in ('completed', 'failed'):
148
+ self._log_final_result(result, start_time, opts.silent)
149
+ return result
150
+
151
+ # Check for timeout
152
+ elapsed = time.time() - start_time
153
+ if elapsed >= opts.poll_timeout:
154
+ if not opts.silent:
155
+ self._log(f'Timeout after {self._format_duration(elapsed)}')
156
+ msg = (
157
+ f'Polling timed out after {opts.poll_timeout}s. '
158
+ f'Last status: {last_state.status} '
159
+ f'at {last_state.progress_percentage}%'
160
+ )
161
+ raise PollTimeoutError(msg, last_state)
162
+
163
+ # Wait before next poll
164
+ time.sleep(opts.poll_interval)
59
165
 
60
166
  def update(
61
167
  self,
62
168
  data: UpdateDynamicPhoneAgent
63
169
  ) -> DynamicPhoneAgentSetupResult:
64
- """Update an existing dynamic phone agent.
170
+ """Update an existing dynamic phone agent configuration.
65
171
 
66
172
  Args:
67
173
  data: Phone agent update data (must include id)
68
174
 
69
175
  Returns:
70
176
  The updated phone agent setup result
177
+
178
+ Raises:
179
+ WiilValidationError: When input validation fails
180
+ WiilAPIError: When phone agent is not found or API error
71
181
  """
182
+ # Validate model configurations before proceeding
183
+ self._validate_model_configurations(
184
+ data.stt_configuration,
185
+ data.tts_configuration
186
+ )
187
+
72
188
  return self._http.patch(
73
189
  self._base_path,
74
190
  data.model_dump(by_alias=True, exclude_none=True),
@@ -76,39 +192,121 @@ class DynamicPhoneAgentResource:
76
192
  response_model=DynamicPhoneAgentSetupResult
77
193
  )
78
194
 
79
- def delete(self, agent_id: str) -> bool:
80
- """Delete a dynamic phone agent.
195
+ def _validate_model_configurations(
196
+ self,
197
+ stt_config: Optional[DynamicSTTModelConfiguration],
198
+ tts_config: Optional[DynamicTTSModelConfiguration]
199
+ ) -> None:
200
+ """Validate STT and TTS model configs against support registry."""
201
+ has_stt = (
202
+ stt_config
203
+ and stt_config.provider_type
204
+ and stt_config.provider_model_id
205
+ )
206
+ if has_stt:
207
+ self._validate_model(
208
+ stt_config.provider_type,
209
+ stt_config.provider_model_id,
210
+ 'STT'
211
+ )
81
212
 
82
- Args:
83
- agent_id: Phone agent ID
213
+ has_tts = (
214
+ tts_config
215
+ and tts_config.provider_type
216
+ and tts_config.provider_model_id
217
+ )
218
+ if has_tts:
219
+ self._validate_model(
220
+ tts_config.provider_type,
221
+ tts_config.provider_model_id,
222
+ 'TTS'
223
+ )
84
224
 
85
- Returns:
86
- True if deletion was successful
87
- """
88
- return self._http.delete(f'{self._base_path}/{agent_id}')
225
+ def _validate_model(
226
+ self,
227
+ provider_type: str,
228
+ provider_model_id: str,
229
+ model_type: str
230
+ ) -> None:
231
+ """Validate a single model against the support registry."""
232
+ is_supported = self._http.get(
233
+ f'/support-models/supports/{provider_type}/{provider_model_id}',
234
+ response_model=bool
235
+ )
236
+
237
+ if not is_supported:
238
+ msg = (
239
+ f'Unsupported {model_type} model: '
240
+ f'{provider_type}/{provider_model_id}. '
241
+ f'Verify the model is available in the support registry.'
242
+ )
243
+ raise WiilValidationError(msg)
244
+
245
+ def _log(self, message: str) -> None:
246
+ """Log a message with agent context."""
247
+ print(f'[Phone Agent] {message}')
89
248
 
90
- def list(
249
+ def _log_progress(self, state: DynamicAgentProcessingState) -> None:
250
+ """Log progress with a visual progress bar."""
251
+ bar = self._create_progress_bar(state.progress_percentage)
252
+ status_msg = state.message or self._get_status_message(state.status)
253
+ pct = state.progress_percentage
254
+ print(f'[Phone Agent] {bar} {pct}% | {status_msg}')
255
+
256
+ def _log_final_result(
91
257
  self,
92
- params: Optional[PaginationRequest] = None
93
- ) -> PaginatedResult[DynamicPhoneAgentSetupResult]:
94
- """List dynamic phone agents with pagination.
258
+ result: DynamicPhoneAgentSetupResult,
259
+ start_time: float,
260
+ silent: bool
261
+ ) -> None:
262
+ """Log the final result with summary."""
263
+ if silent:
264
+ return
95
265
 
96
- Args:
97
- params: Pagination parameters
266
+ elapsed = time.time() - start_time
267
+ duration = self._format_duration(elapsed)
98
268
 
99
- Returns:
100
- Paginated list of phone agent setup results
101
- """
102
- query_params: Dict[str, Any] = {}
103
- if params:
104
- query_params['page'] = params.page
105
- query_params['pageSize'] = params.page_size
106
-
107
- query_string = f'?{urlencode(query_params)}' if query_params else ''
108
- return self._http.get(
109
- f'{self._base_path}{query_string}',
110
- response_model=PaginatedResult[DynamicPhoneAgentSetupResult]
269
+ is_success = (
270
+ result.processing_state.status == 'completed' and result.success
111
271
  )
272
+ if is_success:
273
+ bar = self._create_progress_bar(100)
274
+ print(f'[Phone Agent] {bar} 100% | Setup complete')
275
+ print(f'[Phone Agent] Ready in {duration}')
276
+ if result.phone_number:
277
+ print(f' -> Phone: {result.phone_number}')
278
+ if result.agent_configuration_id:
279
+ print(f' -> Agent ID: {result.agent_configuration_id}')
280
+ if result.instruction_configuration_id:
281
+ inst_id = result.instruction_configuration_id
282
+ print(f' -> Instruction ID: {inst_id}')
283
+ else:
284
+ print(f'[Phone Agent] Setup failed after {duration}')
285
+ if result.error_message:
286
+ print(f' -> Error: {result.error_message}')
287
+
288
+ def _create_progress_bar(self, percentage: int) -> str:
289
+ """Create a visual progress bar."""
290
+ total = 20
291
+ filled = round((percentage / 100) * total)
292
+ empty = total - filled
293
+ return '█' * filled + '░' * empty
294
+
295
+ def _get_status_message(self, status: str) -> str:
296
+ """Get a human-readable status message."""
297
+ messages = {
298
+ 'pending': 'Initializing...',
299
+ 'in_progress': 'Processing...',
300
+ 'completed': 'Setup complete',
301
+ 'failed': 'Setup failed',
302
+ }
303
+ return messages.get(status, status)
304
+
305
+ def _format_duration(self, seconds: float) -> str:
306
+ """Format duration in human-readable form."""
307
+ if seconds < 1:
308
+ return f'{int(seconds * 1000)}ms'
309
+ return f'{seconds:.1f}s'
112
310
 
113
311
 
114
- __all__ = ['DynamicPhoneAgentResource']
312
+ __all__ = ['DynamicPhoneAgentResource', 'PhoneAgentCreateOptions']