amigo_sdk 0.62.0__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.
@@ -0,0 +1,361 @@
1
+ import asyncio
2
+ import threading
3
+ from collections.abc import AsyncGenerator, Iterator
4
+ from datetime import datetime
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import AnyUrl, BaseModel
8
+
9
+ from amigo_sdk.generated.model import (
10
+ ConversationCreateConversationRequest,
11
+ ConversationCreateConversationResponse,
12
+ ConversationGenerateConversationStarterRequest,
13
+ ConversationGenerateConversationStarterResponse,
14
+ ConversationGetConversationMessagesResponse,
15
+ ConversationGetConversationsResponse,
16
+ ConversationGetInteractionInsightsResponse,
17
+ ConversationInteractWithConversationResponse,
18
+ ConversationRecommendResponsesForInteractionResponse,
19
+ CreateConversationParametersQuery,
20
+ Format,
21
+ GetConversationMessagesParametersQuery,
22
+ GetConversationsParametersQuery,
23
+ InteractWithConversationParametersQuery,
24
+ )
25
+ from amigo_sdk.http_client import AmigoAsyncHttpClient, AmigoHttpClient
26
+
27
+
28
+ class GetMessageSourceResponse(BaseModel):
29
+ """
30
+ Response model for the `get_message_source` endpoint.
31
+ TODO: Remove once the OpenAPI spec contains the correct response model for this endpoint.
32
+ """
33
+
34
+ url: AnyUrl
35
+ expires_at: datetime
36
+ content_type: Literal["audio/mpeg", "audio/wav"]
37
+
38
+
39
+ class AsyncConversationResource:
40
+ """Conversation resource for Amigo API operations."""
41
+
42
+ def __init__(self, http_client: AmigoAsyncHttpClient, organization_id: str) -> None:
43
+ self._http = http_client
44
+ self._organization_id = organization_id
45
+
46
+ async def create_conversation(
47
+ self,
48
+ body: ConversationCreateConversationRequest,
49
+ params: CreateConversationParametersQuery,
50
+ abort_event: asyncio.Event | None = None,
51
+ ) -> "AsyncGenerator[ConversationCreateConversationResponse, None]":
52
+ """Create a new conversation and stream NDJSON events.
53
+
54
+ Returns an async generator yielding `ConversationCreateConversationResponse` events.
55
+ """
56
+
57
+ async def _generator():
58
+ async for line in self._http.stream_lines(
59
+ "POST",
60
+ f"/v1/{self._organization_id}/conversation/",
61
+ params=params.model_dump(mode="json", exclude_none=True),
62
+ json=body.model_dump(mode="json", exclude_none=True),
63
+ headers={"Accept": "application/x-ndjson"},
64
+ abort_event=abort_event,
65
+ ):
66
+ # Each line is a JSON object representing a discriminated union event
67
+ yield ConversationCreateConversationResponse.model_validate_json(line)
68
+
69
+ return _generator()
70
+
71
+ async def interact_with_conversation(
72
+ self,
73
+ conversation_id: str,
74
+ params: InteractWithConversationParametersQuery,
75
+ abort_event: asyncio.Event | None = None,
76
+ *,
77
+ text_message: str | None = None,
78
+ audio_bytes: bytes | None = None,
79
+ audio_content_type: Literal["audio/mpeg", "audio/wav"] | None = None,
80
+ ) -> "AsyncGenerator[ConversationInteractWithConversationResponse, None]":
81
+ """Interact with a conversation and stream NDJSON events.
82
+
83
+ Returns an async generator yielding `ConversationInteractWithConversationResponse` events.
84
+ """
85
+
86
+ async def _generator():
87
+ request_kwargs: dict[str, Any] = {
88
+ "params": params.model_dump(mode="json", exclude_none=True),
89
+ "abort_event": abort_event,
90
+ "headers": {"Accept": "application/x-ndjson"},
91
+ }
92
+ # Route based on requested format
93
+ req_format = getattr(params, "request_format", None)
94
+ if req_format == Format.text:
95
+ if text_message is None:
96
+ raise ValueError(
97
+ "text_message is required when request_format is 'text'"
98
+ )
99
+ text_bytes = text_message.encode("utf-8")
100
+ request_kwargs["files"] = {
101
+ "recorded_message": (
102
+ "message.txt",
103
+ text_bytes,
104
+ "text/plain; charset=utf-8",
105
+ )
106
+ }
107
+ elif req_format == Format.voice:
108
+ if audio_bytes is None or audio_content_type is None:
109
+ raise ValueError(
110
+ "audio_bytes and audio_content_type are required when request_format is 'voice'"
111
+ )
112
+ # Send raw bytes with appropriate content type
113
+ request_kwargs["content"] = audio_bytes
114
+ request_kwargs.setdefault("headers", {})
115
+ request_kwargs["headers"]["Content-Type"] = audio_content_type
116
+ else:
117
+ raise ValueError("Unsupported or missing request_format in params")
118
+
119
+ async for line in self._http.stream_lines(
120
+ "POST",
121
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/interact",
122
+ **request_kwargs,
123
+ ):
124
+ # Each line is a JSON object representing a discriminated union event
125
+ yield ConversationInteractWithConversationResponse.model_validate_json(
126
+ line
127
+ )
128
+
129
+ return _generator()
130
+
131
+ async def finish_conversation(self, conversation_id: str) -> None:
132
+ """Finish a conversation."""
133
+ await self._http.request(
134
+ "POST",
135
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/finish/",
136
+ )
137
+
138
+ async def get_conversations(
139
+ self, params: GetConversationsParametersQuery
140
+ ) -> ConversationGetConversationsResponse:
141
+ """Get conversations."""
142
+ response = await self._http.request(
143
+ "GET",
144
+ f"/v1/{self._organization_id}/conversation/",
145
+ params=params.model_dump(mode="json", exclude_none=True),
146
+ )
147
+ return ConversationGetConversationsResponse.model_validate_json(response.text)
148
+
149
+ async def get_conversation_messages(
150
+ self, conversation_id: str, params: GetConversationMessagesParametersQuery
151
+ ) -> ConversationGetConversationMessagesResponse:
152
+ """Get conversation messages."""
153
+ response = await self._http.request(
154
+ "GET",
155
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/messages/",
156
+ params=params.model_dump(
157
+ mode="json", exclude_none=True, exclude_defaults=True
158
+ ),
159
+ )
160
+ return ConversationGetConversationMessagesResponse.model_validate_json(
161
+ response.text
162
+ )
163
+
164
+ async def recommend_responses_for_interaction(
165
+ self, conversation_id: str, interaction_id: str
166
+ ) -> ConversationRecommendResponsesForInteractionResponse:
167
+ """Recommend responses for an interaction."""
168
+ response = await self._http.request(
169
+ "GET",
170
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/interaction/{interaction_id}/recommend_responses",
171
+ )
172
+ return ConversationRecommendResponsesForInteractionResponse.model_validate_json(
173
+ response.text
174
+ )
175
+
176
+ async def get_interaction_insights(
177
+ self, conversation_id: str, interaction_id: str
178
+ ) -> ConversationGetInteractionInsightsResponse:
179
+ """Get insights for an interaction."""
180
+ response = await self._http.request(
181
+ "GET",
182
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/interaction/{interaction_id}/insights",
183
+ )
184
+ return ConversationGetInteractionInsightsResponse.model_validate_json(
185
+ response.text
186
+ )
187
+
188
+ async def get_message_source(
189
+ self, conversation_id: str, message_id: str
190
+ ) -> GetMessageSourceResponse:
191
+ """Get the source of a message."""
192
+ response = await self._http.request(
193
+ "GET",
194
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/messages/{message_id}/source",
195
+ )
196
+ return GetMessageSourceResponse.model_validate_json(response.text)
197
+
198
+ async def generate_conversation_starters(
199
+ self, body: ConversationGenerateConversationStarterRequest
200
+ ) -> ConversationGenerateConversationStarterResponse:
201
+ """Generate conversation starters."""
202
+ response = await self._http.request(
203
+ "POST",
204
+ f"/v1/{self._organization_id}/conversation/conversation_starter",
205
+ json=body.model_dump(mode="json", exclude_none=True),
206
+ )
207
+ return ConversationGenerateConversationStarterResponse.model_validate_json(
208
+ response.text
209
+ )
210
+
211
+
212
+ class ConversationResource:
213
+ """Conversation resource for synchronous operations."""
214
+
215
+ def __init__(self, http_client: AmigoHttpClient, organization_id: str) -> None:
216
+ self._http = http_client
217
+ self._organization_id = organization_id
218
+
219
+ def create_conversation(
220
+ self,
221
+ body: ConversationCreateConversationRequest,
222
+ params: CreateConversationParametersQuery,
223
+ abort_event: threading.Event | None = None,
224
+ ) -> Iterator[ConversationCreateConversationResponse]:
225
+ def _iter():
226
+ for line in self._http.stream_lines(
227
+ "POST",
228
+ f"/v1/{self._organization_id}/conversation/",
229
+ params=params.model_dump(mode="json", exclude_none=True),
230
+ json=body.model_dump(mode="json", exclude_none=True),
231
+ headers={"Accept": "application/x-ndjson"},
232
+ abort_event=abort_event,
233
+ ):
234
+ yield ConversationCreateConversationResponse.model_validate_json(line)
235
+
236
+ return _iter()
237
+
238
+ def interact_with_conversation(
239
+ self,
240
+ conversation_id: str,
241
+ params: InteractWithConversationParametersQuery,
242
+ abort_event: threading.Event | None = None,
243
+ *,
244
+ text_message: str | None = None,
245
+ audio_bytes: bytes | None = None,
246
+ audio_content_type: Literal["audio/mpeg", "audio/wav"] | None = None,
247
+ ) -> Iterator[ConversationInteractWithConversationResponse]:
248
+ def _iter():
249
+ request_kwargs: dict[str, Any] = {
250
+ "params": params.model_dump(mode="json", exclude_none=True),
251
+ "headers": {"Accept": "application/x-ndjson"},
252
+ "abort_event": abort_event,
253
+ }
254
+ req_format = getattr(params, "request_format", None)
255
+ if req_format == Format.text:
256
+ if text_message is None:
257
+ raise ValueError(
258
+ "text_message is required when request_format is 'text'"
259
+ )
260
+ text_bytes = text_message.encode("utf-8")
261
+ request_kwargs["files"] = {
262
+ "recorded_message": (
263
+ "message.txt",
264
+ text_bytes,
265
+ "text/plain; charset=utf-8",
266
+ )
267
+ }
268
+ elif req_format == Format.voice:
269
+ if audio_bytes is None or audio_content_type is None:
270
+ raise ValueError(
271
+ "audio_bytes and audio_content_type are required when request_format is 'voice'"
272
+ )
273
+ request_kwargs["content"] = audio_bytes
274
+ request_kwargs.setdefault("headers", {})
275
+ request_kwargs["headers"]["Content-Type"] = audio_content_type
276
+ else:
277
+ raise ValueError("Unsupported or missing request_format in params")
278
+
279
+ for line in self._http.stream_lines(
280
+ "POST",
281
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/interact",
282
+ **request_kwargs,
283
+ ):
284
+ yield ConversationInteractWithConversationResponse.model_validate_json(
285
+ line
286
+ )
287
+
288
+ return _iter()
289
+
290
+ def finish_conversation(self, conversation_id: str) -> None:
291
+ self._http.request(
292
+ "POST",
293
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/finish/",
294
+ )
295
+
296
+ def get_conversations(
297
+ self, params: GetConversationsParametersQuery
298
+ ) -> ConversationGetConversationsResponse:
299
+ response = self._http.request(
300
+ "GET",
301
+ f"/v1/{self._organization_id}/conversation/",
302
+ params=params.model_dump(mode="json", exclude_none=True),
303
+ )
304
+ return ConversationGetConversationsResponse.model_validate_json(response.text)
305
+
306
+ def get_conversation_messages(
307
+ self, conversation_id: str, params: GetConversationMessagesParametersQuery
308
+ ) -> ConversationGetConversationMessagesResponse:
309
+ response = self._http.request(
310
+ "GET",
311
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/messages/",
312
+ params=params.model_dump(
313
+ mode="json", exclude_none=True, exclude_defaults=True
314
+ ),
315
+ )
316
+ return ConversationGetConversationMessagesResponse.model_validate_json(
317
+ response.text
318
+ )
319
+
320
+ def recommend_responses_for_interaction(
321
+ self, conversation_id: str, interaction_id: str
322
+ ) -> ConversationRecommendResponsesForInteractionResponse:
323
+ response = self._http.request(
324
+ "GET",
325
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/interaction/{interaction_id}/recommend_responses",
326
+ )
327
+ return ConversationRecommendResponsesForInteractionResponse.model_validate_json(
328
+ response.text
329
+ )
330
+
331
+ def get_interaction_insights(
332
+ self, conversation_id: str, interaction_id: str
333
+ ) -> ConversationGetInteractionInsightsResponse:
334
+ response = self._http.request(
335
+ "GET",
336
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/interaction/{interaction_id}/insights",
337
+ )
338
+ return ConversationGetInteractionInsightsResponse.model_validate_json(
339
+ response.text
340
+ )
341
+
342
+ def get_message_source(
343
+ self, conversation_id: str, message_id: str
344
+ ) -> GetMessageSourceResponse:
345
+ response = self._http.request(
346
+ "GET",
347
+ f"/v1/{self._organization_id}/conversation/{conversation_id}/messages/{message_id}/source",
348
+ )
349
+ return GetMessageSourceResponse.model_validate_json(response.text)
350
+
351
+ def generate_conversation_starters(
352
+ self, body: ConversationGenerateConversationStarterRequest
353
+ ) -> ConversationGenerateConversationStarterResponse:
354
+ response = self._http.request(
355
+ "POST",
356
+ f"/v1/{self._organization_id}/conversation/conversation_starter",
357
+ json=body.model_dump(mode="json", exclude_none=True),
358
+ )
359
+ return ConversationGenerateConversationStarterResponse.model_validate_json(
360
+ response.text
361
+ )
@@ -0,0 +1,34 @@
1
+ from amigo_sdk.generated.model import (
2
+ OrganizationGetOrganizationResponse,
3
+ )
4
+ from amigo_sdk.http_client import AmigoAsyncHttpClient, AmigoHttpClient
5
+
6
+
7
+ class AsyncOrganizationResource:
8
+ """Organization resource for Amigo API operations."""
9
+
10
+ def __init__(self, http_client: AmigoAsyncHttpClient, organization_id: str) -> None:
11
+ self._http = http_client
12
+ self._organization_id = organization_id
13
+
14
+ async def get(self) -> OrganizationGetOrganizationResponse:
15
+ """
16
+ Get the details of an organization.
17
+ """
18
+ response = await self._http.request(
19
+ "GET", f"/v1/{self._organization_id}/organization/"
20
+ )
21
+
22
+ return OrganizationGetOrganizationResponse.model_validate_json(response.text)
23
+
24
+
25
+ class OrganizationResource:
26
+ def __init__(self, http_client: AmigoHttpClient, organization_id: str) -> None:
27
+ self._http = http_client
28
+ self._organization_id = organization_id
29
+
30
+ def get(self) -> OrganizationGetOrganizationResponse:
31
+ response = self._http.request(
32
+ "GET", f"/v1/{self._organization_id}/organization/"
33
+ )
34
+ return OrganizationGetOrganizationResponse.model_validate_json(response.text)
@@ -0,0 +1,46 @@
1
+ from typing import Optional
2
+
3
+ from amigo_sdk.generated.model import (
4
+ GetServicesParametersQuery,
5
+ ServiceGetServicesResponse,
6
+ )
7
+ from amigo_sdk.http_client import AmigoAsyncHttpClient, AmigoHttpClient
8
+
9
+
10
+ class AsyncServiceResource:
11
+ """Service resource for Amigo API operations."""
12
+
13
+ def __init__(self, http_client: AmigoAsyncHttpClient, organization_id: str) -> None:
14
+ self._http = http_client
15
+ self._organization_id = organization_id
16
+
17
+ async def get_services(
18
+ self, params: Optional[GetServicesParametersQuery] = None
19
+ ) -> ServiceGetServicesResponse:
20
+ """Get all services."""
21
+ response = await self._http.request(
22
+ "GET",
23
+ f"/v1/{self._organization_id}/service/",
24
+ params=params.model_dump(mode="json", exclude_none=True)
25
+ if params
26
+ else None,
27
+ )
28
+ return ServiceGetServicesResponse.model_validate_json(response.text)
29
+
30
+
31
+ class ServiceResource:
32
+ def __init__(self, http_client: AmigoHttpClient, organization_id: str) -> None:
33
+ self._http = http_client
34
+ self._organization_id = organization_id
35
+
36
+ def get_services(
37
+ self, params: Optional[GetServicesParametersQuery] = None
38
+ ) -> ServiceGetServicesResponse:
39
+ response = self._http.request(
40
+ "GET",
41
+ f"/v1/{self._organization_id}/service/",
42
+ params=params.model_dump(mode="json", exclude_none=True)
43
+ if params
44
+ else None,
45
+ )
46
+ return ServiceGetServicesResponse.model_validate_json(response.text)
@@ -0,0 +1,97 @@
1
+ from typing import Optional
2
+
3
+ from amigo_sdk.generated.model import (
4
+ GetUsersParametersQuery,
5
+ UserCreateInvitedUserRequest,
6
+ UserCreateInvitedUserResponse,
7
+ UserGetUsersResponse,
8
+ UserUpdateUserInfoRequest,
9
+ )
10
+ from amigo_sdk.http_client import AmigoAsyncHttpClient, AmigoHttpClient
11
+
12
+
13
+ class AsyncUserResource:
14
+ """User resource for Amigo API operations."""
15
+
16
+ def __init__(self, http_client: AmigoAsyncHttpClient, organization_id: str) -> None:
17
+ self._http = http_client
18
+ self._organization_id = organization_id
19
+
20
+ async def get_users(
21
+ self, params: Optional[GetUsersParametersQuery] = None
22
+ ) -> UserGetUsersResponse:
23
+ """Get a list of users in the organization."""
24
+ response = await self._http.request(
25
+ "GET",
26
+ f"/v1/{self._organization_id}/user/",
27
+ params=params.model_dump(mode="json", exclude_none=True)
28
+ if params
29
+ else None,
30
+ )
31
+ return UserGetUsersResponse.model_validate_json(response.text)
32
+
33
+ async def create_user(
34
+ self, body: UserCreateInvitedUserRequest
35
+ ) -> UserCreateInvitedUserResponse:
36
+ """Create (invite) a new user to the organization."""
37
+ response = await self._http.request(
38
+ "POST",
39
+ f"/v1/{self._organization_id}/user/",
40
+ json=body.model_dump(mode="json", exclude_none=True),
41
+ )
42
+ return UserCreateInvitedUserResponse.model_validate_json(response.text)
43
+
44
+ async def delete_user(self, user_id: str) -> None:
45
+ """Delete a user by ID. Returns None on success (e.g., 204)."""
46
+ await self._http.request(
47
+ "DELETE",
48
+ f"/v1/{self._organization_id}/user/{user_id}",
49
+ )
50
+
51
+ async def update_user(self, user_id: str, body: UserUpdateUserInfoRequest) -> None:
52
+ """Update user information. Returns None on success (e.g., 204)."""
53
+ await self._http.request(
54
+ "POST",
55
+ f"/v1/{self._organization_id}/user/{user_id}/user",
56
+ json=body.model_dump(mode="json", exclude_none=True),
57
+ )
58
+
59
+
60
+ class UserResource:
61
+ """User resource (synchronous)."""
62
+
63
+ def __init__(self, http_client: AmigoHttpClient, organization_id: str) -> None:
64
+ self._http = http_client
65
+ self._organization_id = organization_id
66
+
67
+ def get_users(
68
+ self, params: Optional[GetUsersParametersQuery] = None
69
+ ) -> UserGetUsersResponse:
70
+ response = self._http.request(
71
+ "GET",
72
+ f"/v1/{self._organization_id}/user/",
73
+ params=params.model_dump(mode="json", exclude_none=True)
74
+ if params
75
+ else None,
76
+ )
77
+ return UserGetUsersResponse.model_validate_json(response.text)
78
+
79
+ def create_user(
80
+ self, body: UserCreateInvitedUserRequest
81
+ ) -> UserCreateInvitedUserResponse:
82
+ response = self._http.request(
83
+ "POST",
84
+ f"/v1/{self._organization_id}/user/",
85
+ json=body.model_dump(mode="json", exclude_none=True),
86
+ )
87
+ return UserCreateInvitedUserResponse.model_validate_json(response.text)
88
+
89
+ def delete_user(self, user_id: str) -> None:
90
+ self._http.request("DELETE", f"/v1/{self._organization_id}/user/{user_id}")
91
+
92
+ def update_user(self, user_id: str, body: UserUpdateUserInfoRequest) -> None:
93
+ self._http.request(
94
+ "POST",
95
+ f"/v1/{self._organization_id}/user/{user_id}/user",
96
+ json=body.model_dump(mode="json", exclude_none=True),
97
+ )