microsoft-agents-hosting-core 0.4.0.dev14__py3-none-any.whl → 0.4.0.dev16__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.
- microsoft_agents/hosting/core/channel_service_adapter.py +23 -26
- microsoft_agents/hosting/core/channel_service_client_factory_base.py +6 -1
- microsoft_agents/hosting/core/connector/client/connector_client.py +22 -2
- microsoft_agents/hosting/core/rest_channel_service_client_factory.py +78 -52
- microsoft_agents/hosting/core/storage/__init__.py +4 -0
- microsoft_agents/hosting/core/storage/transcript_file_store.py +267 -0
- microsoft_agents/hosting/core/storage/transcript_logger.py +12 -0
- microsoft_agents/hosting/core/storage/transcript_memory_store.py +9 -7
- {microsoft_agents_hosting_core-0.4.0.dev14.dist-info → microsoft_agents_hosting_core-0.4.0.dev16.dist-info}/METADATA +2 -2
- {microsoft_agents_hosting_core-0.4.0.dev14.dist-info → microsoft_agents_hosting_core-0.4.0.dev16.dist-info}/RECORD +12 -11
- {microsoft_agents_hosting_core-0.4.0.dev14.dist-info → microsoft_agents_hosting_core-0.4.0.dev16.dist-info}/WHEEL +0 -0
- {microsoft_agents_hosting_core-0.4.0.dev14.dist-info → microsoft_agents_hosting_core-0.4.0.dev16.dist-info}/top_level.txt +0 -0
|
@@ -213,21 +213,21 @@ class ChannelServiceAdapter(ChannelAdapter, ABC):
|
|
|
213
213
|
claims_identity = self.create_claims_identity(agent_app_id)
|
|
214
214
|
claims_identity.claims[AuthenticationConstants.SERVICE_URL_CLAIM] = service_url
|
|
215
215
|
|
|
216
|
-
# Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.)
|
|
217
|
-
user_token_client: UserTokenClient = (
|
|
218
|
-
await self._channel_service_client_factory.create_user_token_client(
|
|
219
|
-
claims_identity
|
|
220
|
-
)
|
|
221
|
-
)
|
|
222
|
-
|
|
223
216
|
# Create a turn context and run the pipeline.
|
|
224
217
|
context = self._create_turn_context(
|
|
225
218
|
claims_identity,
|
|
226
219
|
None,
|
|
227
|
-
user_token_client,
|
|
228
220
|
callback,
|
|
229
221
|
)
|
|
230
222
|
|
|
223
|
+
# Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.)
|
|
224
|
+
user_token_client: UserTokenClient = (
|
|
225
|
+
await self._channel_service_client_factory.create_user_token_client(
|
|
226
|
+
context, claims_identity
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client
|
|
230
|
+
|
|
231
231
|
# Create the connector client to use for outbound requests.
|
|
232
232
|
connector_client: ConnectorClient = (
|
|
233
233
|
await self._channel_service_client_factory.create_connector_client(
|
|
@@ -264,22 +264,21 @@ class ChannelServiceAdapter(ChannelAdapter, ABC):
|
|
|
264
264
|
callback: Callable[[TurnContext], Awaitable],
|
|
265
265
|
):
|
|
266
266
|
|
|
267
|
-
# Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.)
|
|
268
|
-
user_token_client: UserTokenClient = (
|
|
269
|
-
await self._channel_service_client_factory.create_user_token_client(
|
|
270
|
-
claims_identity
|
|
271
|
-
)
|
|
272
|
-
)
|
|
273
|
-
|
|
274
267
|
# Create a turn context and run the pipeline.
|
|
275
268
|
context = self._create_turn_context(
|
|
276
269
|
claims_identity,
|
|
277
270
|
audience,
|
|
278
|
-
user_token_client,
|
|
279
271
|
callback,
|
|
280
272
|
activity=continuation_activity,
|
|
281
273
|
)
|
|
282
274
|
|
|
275
|
+
user_token_client: UserTokenClient = (
|
|
276
|
+
await self._channel_service_client_factory.create_user_token_client(
|
|
277
|
+
context, claims_identity
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client
|
|
281
|
+
|
|
283
282
|
# Create the connector client to use for outbound requests.
|
|
284
283
|
connector_client: ConnectorClient = (
|
|
285
284
|
await self._channel_service_client_factory.create_connector_client(
|
|
@@ -338,22 +337,22 @@ class ChannelServiceAdapter(ChannelAdapter, ABC):
|
|
|
338
337
|
):
|
|
339
338
|
use_anonymous_auth_callback = True
|
|
340
339
|
|
|
341
|
-
# Create a UserTokenClient instance for the OAuth flow.
|
|
342
|
-
user_token_client: UserTokenClient = (
|
|
343
|
-
await self._channel_service_client_factory.create_user_token_client(
|
|
344
|
-
claims_identity, use_anonymous_auth_callback
|
|
345
|
-
)
|
|
346
|
-
)
|
|
347
|
-
|
|
348
340
|
# Create a turn context and run the pipeline.
|
|
349
341
|
context = self._create_turn_context(
|
|
350
342
|
claims_identity,
|
|
351
343
|
outgoing_audience,
|
|
352
|
-
user_token_client,
|
|
353
344
|
callback,
|
|
354
345
|
activity=activity,
|
|
355
346
|
)
|
|
356
347
|
|
|
348
|
+
# Create a UserTokenClient instance for the OAuth flow.
|
|
349
|
+
user_token_client: UserTokenClient = (
|
|
350
|
+
await self._channel_service_client_factory.create_user_token_client(
|
|
351
|
+
context, claims_identity, use_anonymous_auth_callback
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client
|
|
355
|
+
|
|
357
356
|
# Create the connector client to use for outbound requests.
|
|
358
357
|
connector_client: ConnectorClient = (
|
|
359
358
|
await self._channel_service_client_factory.create_connector_client(
|
|
@@ -425,14 +424,12 @@ class ChannelServiceAdapter(ChannelAdapter, ABC):
|
|
|
425
424
|
self,
|
|
426
425
|
claims_identity: ClaimsIdentity,
|
|
427
426
|
oauth_scope: str,
|
|
428
|
-
user_token_client: UserTokenClientBase,
|
|
429
427
|
callback: Callable[[TurnContext], Awaitable],
|
|
430
428
|
activity: Optional[Activity] = None,
|
|
431
429
|
) -> TurnContext:
|
|
432
430
|
context = TurnContext(self, activity, claims_identity)
|
|
433
431
|
|
|
434
432
|
context.turn_state[self.AGENT_IDENTITY_KEY] = claims_identity
|
|
435
|
-
context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client
|
|
436
433
|
context.turn_state[self.AGENT_CALLBACK_HANDLER_KEY] = callback
|
|
437
434
|
context.turn_state[self.CHANNEL_SERVICE_FACTORY_KEY] = (
|
|
438
435
|
self._channel_service_client_factory
|
|
@@ -6,12 +6,14 @@ from microsoft_agents.hosting.core.connector import (
|
|
|
6
6
|
ConnectorClientBase,
|
|
7
7
|
UserTokenClientBase,
|
|
8
8
|
)
|
|
9
|
+
from microsoft_agents.hosting.core.turn_context import TurnContext
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class ChannelServiceClientFactoryBase(Protocol):
|
|
12
13
|
@abstractmethod
|
|
13
14
|
async def create_connector_client(
|
|
14
15
|
self,
|
|
16
|
+
context: TurnContext,
|
|
15
17
|
claims_identity: ClaimsIdentity,
|
|
16
18
|
service_url: str,
|
|
17
19
|
audience: str,
|
|
@@ -32,7 +34,10 @@ class ChannelServiceClientFactoryBase(Protocol):
|
|
|
32
34
|
|
|
33
35
|
@abstractmethod
|
|
34
36
|
async def create_user_token_client(
|
|
35
|
-
self,
|
|
37
|
+
self,
|
|
38
|
+
context: TurnContext,
|
|
39
|
+
claims_identity: ClaimsIdentity,
|
|
40
|
+
use_anonymous: bool = False,
|
|
36
41
|
) -> UserTokenClientBase:
|
|
37
42
|
"""
|
|
38
43
|
Creates the appropriate UserTokenClientBase instance.
|
|
@@ -122,8 +122,12 @@ class AttachmentsOperations(AttachmentsBase):
|
|
|
122
122
|
|
|
123
123
|
class ConversationsOperations(ConversationsBase):
|
|
124
124
|
|
|
125
|
-
def __init__(self, client: ClientSession):
|
|
125
|
+
def __init__(self, client: ClientSession, **kwargs):
|
|
126
126
|
self.client = client
|
|
127
|
+
self._max_conversation_id_length = kwargs.get("max_conversation_id_length", 200)
|
|
128
|
+
|
|
129
|
+
def _normalize_conversation_id(self, conversation_id: str) -> str:
|
|
130
|
+
return conversation_id[: self._max_conversation_id_length]
|
|
127
131
|
|
|
128
132
|
async def get_conversations(
|
|
129
133
|
self, continuation_token: Optional[str] = None
|
|
@@ -193,11 +197,16 @@ class ConversationsOperations(ConversationsBase):
|
|
|
193
197
|
)
|
|
194
198
|
raise ValueError("conversationId and activityId are required")
|
|
195
199
|
|
|
200
|
+
print("\n*3")
|
|
201
|
+
print(conversation_id)
|
|
202
|
+
print("\n*3")
|
|
203
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
196
204
|
url = f"v3/conversations/{conversation_id}/activities/{activity_id}"
|
|
197
205
|
|
|
198
206
|
logger.info(
|
|
199
207
|
f"Replying to activity: {activity_id} in conversation: {conversation_id}. Activity type is {body.type}"
|
|
200
208
|
)
|
|
209
|
+
|
|
201
210
|
async with self.client.post(
|
|
202
211
|
url,
|
|
203
212
|
json=body.model_dump(
|
|
@@ -216,7 +225,8 @@ class ConversationsOperations(ConversationsBase):
|
|
|
216
225
|
logger.info(
|
|
217
226
|
f"Reply to conversation/activity: {result.get('id')}, {activity_id}"
|
|
218
227
|
)
|
|
219
|
-
|
|
228
|
+
|
|
229
|
+
return ResourceResponse.model_validate(result)
|
|
220
230
|
|
|
221
231
|
async def send_to_conversation(
|
|
222
232
|
self, conversation_id: str, body: Activity
|
|
@@ -235,6 +245,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
235
245
|
)
|
|
236
246
|
raise ValueError("conversationId is required")
|
|
237
247
|
|
|
248
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
238
249
|
url = f"v3/conversations/{conversation_id}/activities"
|
|
239
250
|
|
|
240
251
|
logger.info(
|
|
@@ -271,6 +282,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
271
282
|
)
|
|
272
283
|
raise ValueError("conversationId and activityId are required")
|
|
273
284
|
|
|
285
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
274
286
|
url = f"v3/conversations/{conversation_id}/activities/{activity_id}"
|
|
275
287
|
|
|
276
288
|
logger.info(
|
|
@@ -303,6 +315,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
303
315
|
)
|
|
304
316
|
raise ValueError("conversationId and activityId are required")
|
|
305
317
|
|
|
318
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
306
319
|
url = f"v3/conversations/{conversation_id}/activities/{activity_id}"
|
|
307
320
|
|
|
308
321
|
logger.info(
|
|
@@ -332,6 +345,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
332
345
|
)
|
|
333
346
|
raise ValueError("conversationId is required")
|
|
334
347
|
|
|
348
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
335
349
|
url = f"v3/conversations/{conversation_id}/attachments"
|
|
336
350
|
|
|
337
351
|
# Convert the AttachmentData to a dictionary
|
|
@@ -371,6 +385,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
371
385
|
)
|
|
372
386
|
raise ValueError("conversationId is required")
|
|
373
387
|
|
|
388
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
374
389
|
url = f"v3/conversations/{conversation_id}/members"
|
|
375
390
|
|
|
376
391
|
logger.info(f"Getting conversation members for conversation: {conversation_id}")
|
|
@@ -402,6 +417,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
402
417
|
)
|
|
403
418
|
raise ValueError("conversationId and memberId are required")
|
|
404
419
|
|
|
420
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
405
421
|
url = f"v3/conversations/{conversation_id}/members/{member_id}"
|
|
406
422
|
|
|
407
423
|
logger.info(
|
|
@@ -434,6 +450,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
434
450
|
)
|
|
435
451
|
raise ValueError("conversationId and memberId are required")
|
|
436
452
|
|
|
453
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
437
454
|
url = f"v3/conversations/{conversation_id}/members/{member_id}"
|
|
438
455
|
|
|
439
456
|
logger.info(
|
|
@@ -464,6 +481,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
464
481
|
)
|
|
465
482
|
raise ValueError("conversationId and activityId are required")
|
|
466
483
|
|
|
484
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
467
485
|
url = f"v3/conversations/{conversation_id}/activities/{activity_id}/members"
|
|
468
486
|
|
|
469
487
|
logger.info(
|
|
@@ -507,6 +525,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
507
525
|
if continuation_token is not None:
|
|
508
526
|
params["continuationToken"] = continuation_token
|
|
509
527
|
|
|
528
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
510
529
|
url = f"v3/conversations/{conversation_id}/pagedmembers"
|
|
511
530
|
|
|
512
531
|
logger.info(
|
|
@@ -540,6 +559,7 @@ class ConversationsOperations(ConversationsBase):
|
|
|
540
559
|
)
|
|
541
560
|
raise ValueError("conversationId is required")
|
|
542
561
|
|
|
562
|
+
conversation_id = self._normalize_conversation_id(conversation_id)
|
|
543
563
|
url = f"v3/conversations/{conversation_id}/activities/history"
|
|
544
564
|
|
|
545
565
|
logger.info(f"Sending conversation history to conversation: {conversation_id}")
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import re
|
|
2
1
|
from typing import Optional
|
|
3
2
|
import logging
|
|
4
3
|
|
|
@@ -33,6 +32,52 @@ class RestChannelServiceClientFactory(ChannelServiceClientFactoryBase):
|
|
|
33
32
|
self._token_service_endpoint = token_service_endpoint
|
|
34
33
|
self._token_service_audience = token_service_audience
|
|
35
34
|
|
|
35
|
+
async def _get_agentic_token(self, context: TurnContext, service_url: str) -> str:
|
|
36
|
+
logger.info(
|
|
37
|
+
"Creating connector client for agentic request to service_url: %s",
|
|
38
|
+
service_url,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if not context.identity:
|
|
42
|
+
raise ValueError("context.identity is required for agentic activities")
|
|
43
|
+
|
|
44
|
+
connection = self._connection_manager.get_token_provider(
|
|
45
|
+
context.identity, service_url
|
|
46
|
+
)
|
|
47
|
+
if not hasattr(connection, "_msal_configuration"):
|
|
48
|
+
raise TypeError(
|
|
49
|
+
"Connection does not support MSAL configuration for agentic token retrieval"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if connection._msal_configuration.ALT_BLUEPRINT_ID:
|
|
53
|
+
logger.debug(
|
|
54
|
+
"Using alternative blueprint ID for agentic token retrieval: %s",
|
|
55
|
+
connection._msal_configuration.ALT_BLUEPRINT_ID,
|
|
56
|
+
)
|
|
57
|
+
connection = self._connection_manager.get_connection(
|
|
58
|
+
connection._msal_configuration.ALT_BLUEPRINT_ID
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
agent_instance_id = context.activity.get_agentic_instance_id()
|
|
62
|
+
if not agent_instance_id:
|
|
63
|
+
raise ValueError("Agent instance ID is required for agentic identity role")
|
|
64
|
+
|
|
65
|
+
if context.activity.recipient.role == RoleTypes.agentic_identity:
|
|
66
|
+
token, _ = await connection.get_agentic_instance_token(agent_instance_id)
|
|
67
|
+
else:
|
|
68
|
+
agentic_user = context.activity.get_agentic_user()
|
|
69
|
+
if not agentic_user:
|
|
70
|
+
raise ValueError("Agentic user is required for agentic user role")
|
|
71
|
+
token = await connection.get_agentic_user_token(
|
|
72
|
+
agent_instance_id,
|
|
73
|
+
agentic_user,
|
|
74
|
+
[AuthenticationConstants.APX_PRODUCTION_SCOPE],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if not token:
|
|
78
|
+
raise ValueError("Failed to obtain token for agentic activity")
|
|
79
|
+
return token
|
|
80
|
+
|
|
36
81
|
async def create_connector_client(
|
|
37
82
|
self,
|
|
38
83
|
context: TurnContext,
|
|
@@ -42,6 +87,8 @@ class RestChannelServiceClientFactory(ChannelServiceClientFactoryBase):
|
|
|
42
87
|
scopes: Optional[list[str]] = None,
|
|
43
88
|
use_anonymous: bool = False,
|
|
44
89
|
) -> ConnectorClientBase:
|
|
90
|
+
if not context or not claims_identity:
|
|
91
|
+
raise TypeError("context and claims_identity are required")
|
|
45
92
|
if not service_url:
|
|
46
93
|
raise TypeError(
|
|
47
94
|
"RestChannelServiceClientFactory.create_connector_client: service_url can't be None or Empty"
|
|
@@ -52,50 +99,7 @@ class RestChannelServiceClientFactory(ChannelServiceClientFactoryBase):
|
|
|
52
99
|
)
|
|
53
100
|
|
|
54
101
|
if context.activity.is_agentic_request():
|
|
55
|
-
|
|
56
|
-
"Creating connector client for agentic request to service_url: %s",
|
|
57
|
-
service_url,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
if not context.identity:
|
|
61
|
-
raise ValueError("context.identity is required for agentic activities")
|
|
62
|
-
|
|
63
|
-
connection = self._connection_manager.get_token_provider(
|
|
64
|
-
context.identity, service_url
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# TODO: clean up linter
|
|
68
|
-
if connection._msal_configuration.ALT_BLUEPRINT_ID:
|
|
69
|
-
logger.debug(
|
|
70
|
-
"Using alternative blueprint ID for agentic token retrieval: %s",
|
|
71
|
-
connection._msal_configuration.ALT_BLUEPRINT_ID,
|
|
72
|
-
)
|
|
73
|
-
connection = self._connection_manager.get_connection(
|
|
74
|
-
connection._msal_configuration.ALT_BLUEPRINT_ID
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
agent_instance_id = context.activity.get_agentic_instance_id()
|
|
78
|
-
if not agent_instance_id:
|
|
79
|
-
raise ValueError(
|
|
80
|
-
"Agent instance ID is required for agentic identity role"
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
if context.activity.recipient.role == RoleTypes.agentic_identity:
|
|
84
|
-
token, _ = await connection.get_agentic_instance_token(
|
|
85
|
-
agent_instance_id
|
|
86
|
-
)
|
|
87
|
-
else:
|
|
88
|
-
agentic_user = context.activity.get_agentic_user()
|
|
89
|
-
if not agentic_user:
|
|
90
|
-
raise ValueError("Agentic user is required for agentic user role")
|
|
91
|
-
token = await connection.get_agentic_user_token(
|
|
92
|
-
agent_instance_id,
|
|
93
|
-
agentic_user,
|
|
94
|
-
[AuthenticationConstants.APX_PRODUCTION_SCOPE],
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
if not token:
|
|
98
|
-
raise ValueError("Failed to obtain token for agentic activity")
|
|
102
|
+
token = await self._get_agentic_token(context, service_url)
|
|
99
103
|
else:
|
|
100
104
|
token_provider: AccessTokenProviderBase = (
|
|
101
105
|
self._connection_manager.get_token_provider(
|
|
@@ -115,18 +119,40 @@ class RestChannelServiceClientFactory(ChannelServiceClientFactoryBase):
|
|
|
115
119
|
)
|
|
116
120
|
|
|
117
121
|
async def create_user_token_client(
|
|
118
|
-
self,
|
|
122
|
+
self,
|
|
123
|
+
context: TurnContext,
|
|
124
|
+
claims_identity: ClaimsIdentity,
|
|
125
|
+
use_anonymous: bool = False,
|
|
119
126
|
) -> UserTokenClient:
|
|
127
|
+
"""Create a UserTokenClient for the given context and claims identity.
|
|
128
|
+
|
|
129
|
+
:param context: The TurnContext for the current turn of conversation.
|
|
130
|
+
:param claims_identity: The ClaimsIdentity of the user.
|
|
131
|
+
:param use_anonymous: Whether to use an anonymous token provider.
|
|
132
|
+
"""
|
|
133
|
+
if not context or not claims_identity:
|
|
134
|
+
raise ValueError("context and claims_identity are required")
|
|
135
|
+
|
|
120
136
|
if use_anonymous:
|
|
121
137
|
return UserTokenClient(endpoint=self._token_service_endpoint, token="")
|
|
122
138
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
if context.activity.is_agentic_request():
|
|
140
|
+
token = await self._get_agentic_token(context, self._token_service_endpoint)
|
|
141
|
+
else:
|
|
142
|
+
scopes = [f"{self._token_service_audience}/.default"]
|
|
143
|
+
|
|
144
|
+
token_provider = self._connection_manager.get_token_provider(
|
|
145
|
+
claims_identity, self._token_service_endpoint
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
token = await token_provider.get_access_token(
|
|
149
|
+
self._token_service_audience, scopes
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if not token:
|
|
153
|
+
logger.error("Failed to obtain token for user token client")
|
|
154
|
+
raise ValueError("Failed to obtain token for user token client")
|
|
126
155
|
|
|
127
|
-
token = await token_provider.get_access_token(
|
|
128
|
-
self._token_service_audience, [f"{self._token_service_audience}/.default"]
|
|
129
|
-
)
|
|
130
156
|
return UserTokenClient(
|
|
131
157
|
endpoint=self._token_service_endpoint,
|
|
132
158
|
token=token,
|
|
@@ -7,8 +7,10 @@ from .transcript_logger import (
|
|
|
7
7
|
ConsoleTranscriptLogger,
|
|
8
8
|
TranscriptLoggerMiddleware,
|
|
9
9
|
FileTranscriptLogger,
|
|
10
|
+
PagedResult,
|
|
10
11
|
)
|
|
11
12
|
from .transcript_store import TranscriptStore
|
|
13
|
+
from .transcript_file_store import FileTranscriptStore
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
14
16
|
"StoreItem",
|
|
@@ -21,4 +23,6 @@ __all__ = [
|
|
|
21
23
|
"TranscriptLoggerMiddleware",
|
|
22
24
|
"TranscriptStore",
|
|
23
25
|
"FileTranscriptLogger",
|
|
26
|
+
"FileTranscriptStore",
|
|
27
|
+
"PagedResult",
|
|
24
28
|
]
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
|
14
|
+
|
|
15
|
+
from .transcript_logger import TranscriptLogger
|
|
16
|
+
from .transcript_logger import PagedResult
|
|
17
|
+
from .transcript_info import TranscriptInfo
|
|
18
|
+
|
|
19
|
+
from microsoft_agents.activity import Activity # type: ignore
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FileTranscriptStore(TranscriptLogger):
|
|
23
|
+
"""
|
|
24
|
+
Python port of the .NET FileTranscriptStore which creates a single
|
|
25
|
+
`.transcript` file per conversation and appends each Activity as newline-delimited JSON.
|
|
26
|
+
|
|
27
|
+
Layout on disk:
|
|
28
|
+
<root>/<channelId>/<conversationId>.transcript
|
|
29
|
+
|
|
30
|
+
- Each line is a JSON object representing one Activity.
|
|
31
|
+
- Methods are async to match the Agents SDK shape.
|
|
32
|
+
|
|
33
|
+
Notes
|
|
34
|
+
-----
|
|
35
|
+
* Continuation tokens are simple integer byte offsets encoded as strings.
|
|
36
|
+
* Activities are written using UTF-8 with newline separators (JSONL).
|
|
37
|
+
* Filenames are sanitized to avoid path traversal and invalid characters.
|
|
38
|
+
|
|
39
|
+
Inspired by the .NET design for FileTranscriptLogger. See:
|
|
40
|
+
- Microsoft.Bot.Builder FileTranscriptLogger docs (for behavior) [DOTNET]
|
|
41
|
+
- Microsoft.Agents.Storage.Transcript namespace overview [AGENTS]
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, root_folder: Union[str, Path]) -> None:
|
|
45
|
+
self._root = Path(root_folder).expanduser().resolve()
|
|
46
|
+
self._root.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
# precompiled regex for safe names (letters, digits, dash, underscore, dot)
|
|
49
|
+
self._safe = re.compile(r"[^A-Za-z0-9._-]+")
|
|
50
|
+
|
|
51
|
+
# -------- Logger surface --------
|
|
52
|
+
|
|
53
|
+
async def log_activity(self, activity: Activity) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Asynchronously persist a transcript activity to the file system.
|
|
56
|
+
This method computes the transcript file path based on the activity’s channel
|
|
57
|
+
and conversation identifiers, ensures the directory exists, and appends the
|
|
58
|
+
activity data to the transcript file in JSON format using a background thread.
|
|
59
|
+
If the activity lacks a timestamp, one is assigned prior to serialization.
|
|
60
|
+
:param activity: The activity to log.
|
|
61
|
+
"""
|
|
62
|
+
if not activity:
|
|
63
|
+
raise ValueError("Activity is required")
|
|
64
|
+
|
|
65
|
+
channel_id, conversation_id = _get_ids(activity)
|
|
66
|
+
file_path = self._file_path(channel_id, conversation_id)
|
|
67
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
# Ensure a stable timestamp property if absent
|
|
70
|
+
# Write in a background thread to avoid blocking the event loop
|
|
71
|
+
def _write() -> None:
|
|
72
|
+
# Normalize to a dict to ensure json serializable content.
|
|
73
|
+
if not activity.timestamp:
|
|
74
|
+
activity.timestamp = _utc_iso_now()
|
|
75
|
+
|
|
76
|
+
with open(file_path, "a", encoding="utf-8", newline="\n") as f:
|
|
77
|
+
f.write(activity.model_dump_json(exclude_none=True, exclude_unset=True))
|
|
78
|
+
f.write("\n")
|
|
79
|
+
|
|
80
|
+
await asyncio.to_thread(_write)
|
|
81
|
+
|
|
82
|
+
# -------- Store surface --------
|
|
83
|
+
|
|
84
|
+
async def list_transcripts(self, channel_id: str) -> PagedResult[TranscriptInfo]:
|
|
85
|
+
"""
|
|
86
|
+
List transcripts (conversations) for a channel.
|
|
87
|
+
:param channel_id: The channel ID to list transcripts for."""
|
|
88
|
+
channel_dir = self._channel_dir(channel_id)
|
|
89
|
+
|
|
90
|
+
def _list() -> List[TranscriptInfo]:
|
|
91
|
+
if not channel_dir.exists():
|
|
92
|
+
return []
|
|
93
|
+
results: List[TranscriptInfo] = []
|
|
94
|
+
for p in channel_dir.glob("*.transcript"):
|
|
95
|
+
# mtime is a reasonable proxy for 'created/updated'
|
|
96
|
+
created = datetime.fromtimestamp(p.stat().st_mtime, tz=timezone.utc)
|
|
97
|
+
results.append(
|
|
98
|
+
TranscriptInfo(
|
|
99
|
+
channel_id=_sanitize(self._safe, channel_id),
|
|
100
|
+
conversation_id=p.stem,
|
|
101
|
+
created_on=created,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
# Sort newest first (consistent, useful default)
|
|
105
|
+
results.sort(key=lambda t: t.created_on, reverse=True)
|
|
106
|
+
return results
|
|
107
|
+
|
|
108
|
+
items = await asyncio.to_thread(_list)
|
|
109
|
+
return PagedResult(items=items, continuation_token=None)
|
|
110
|
+
|
|
111
|
+
async def get_transcript_activities(
|
|
112
|
+
self,
|
|
113
|
+
channel_id: str,
|
|
114
|
+
conversation_id: str,
|
|
115
|
+
continuation_token: Optional[str] = None,
|
|
116
|
+
start_date: Optional[datetime] = None,
|
|
117
|
+
page_bytes: int = 512 * 1024,
|
|
118
|
+
) -> PagedResult[Activity]:
|
|
119
|
+
"""
|
|
120
|
+
Read activities from the transcript file (paged by byte size).
|
|
121
|
+
:param channel_id: The channel ID of the conversation.
|
|
122
|
+
:param conversation_id: The conversation ID to read activities from.
|
|
123
|
+
:param continuation_token: Optional continuation token (byte offset as string).
|
|
124
|
+
:param start_date: Optional filter to only include activities on or after this date.
|
|
125
|
+
:param page_bytes: Maximum number of bytes to read (default: 512kB).
|
|
126
|
+
:return: A PagedResult containing a list of Activities and an optional continuation token.
|
|
127
|
+
"""
|
|
128
|
+
file_path = self._file_path(channel_id, conversation_id)
|
|
129
|
+
|
|
130
|
+
def _read_page() -> Tuple[List[Activity], Optional[str]]:
|
|
131
|
+
if not file_path.exists():
|
|
132
|
+
return [], None
|
|
133
|
+
|
|
134
|
+
offset = int(continuation_token) if continuation_token else 0
|
|
135
|
+
results: List[Activity] = []
|
|
136
|
+
|
|
137
|
+
with open(file_path, "rb") as f:
|
|
138
|
+
f.seek(0, os.SEEK_END)
|
|
139
|
+
end = f.tell()
|
|
140
|
+
if offset > end:
|
|
141
|
+
return [], None
|
|
142
|
+
f.seek(offset)
|
|
143
|
+
# Read a chunk
|
|
144
|
+
raw = f.read(page_bytes)
|
|
145
|
+
# Extend to end of current line to avoid cutting a JSON record in half
|
|
146
|
+
# (read until newline or EOF)
|
|
147
|
+
while True:
|
|
148
|
+
ch = f.read(1)
|
|
149
|
+
if not ch:
|
|
150
|
+
break
|
|
151
|
+
raw += ch
|
|
152
|
+
if ch == b"\n":
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
next_offset = f.tell()
|
|
156
|
+
# Decode and split lines
|
|
157
|
+
text = raw.decode("utf-8", errors="ignore")
|
|
158
|
+
lines = [ln for ln in text.splitlines() if ln.strip()]
|
|
159
|
+
|
|
160
|
+
# Parse JSONL
|
|
161
|
+
for ln in lines:
|
|
162
|
+
try:
|
|
163
|
+
a = Activity.model_validate_json(ln)
|
|
164
|
+
except Exception:
|
|
165
|
+
# Skip malformed lines
|
|
166
|
+
continue
|
|
167
|
+
if start_date:
|
|
168
|
+
if a.timestamp and a.timestamp < start_date.astimezone(
|
|
169
|
+
timezone.utc
|
|
170
|
+
):
|
|
171
|
+
continue
|
|
172
|
+
results.append(a)
|
|
173
|
+
|
|
174
|
+
token = str(next_offset) if next_offset < end else None
|
|
175
|
+
return results, token
|
|
176
|
+
|
|
177
|
+
items, token = await asyncio.to_thread(_read_page)
|
|
178
|
+
return PagedResult(items=items, continuation_token=token)
|
|
179
|
+
|
|
180
|
+
async def delete_transcript(self, channel_id: str, conversation_id: str) -> None:
|
|
181
|
+
"""Delete the specified conversation transcript file (no-op if absent)."""
|
|
182
|
+
file_path = self._file_path(channel_id, conversation_id)
|
|
183
|
+
|
|
184
|
+
def _delete() -> None:
|
|
185
|
+
try:
|
|
186
|
+
file_path.unlink(missing_ok=True)
|
|
187
|
+
except Exception:
|
|
188
|
+
# Best-effort deletion: ignore failures (locked file, etc.)
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
await asyncio.to_thread(_delete)
|
|
192
|
+
|
|
193
|
+
# ----------------------------
|
|
194
|
+
# Helpers
|
|
195
|
+
# ----------------------------
|
|
196
|
+
|
|
197
|
+
def _channel_dir(self, channel_id: str) -> Path:
|
|
198
|
+
return self._root / _sanitize(self._safe, channel_id)
|
|
199
|
+
|
|
200
|
+
def _file_path(self, channel_id: str, conversation_id: str) -> Path:
|
|
201
|
+
safe_channel = _sanitize(self._safe, channel_id)
|
|
202
|
+
safe_conv = _sanitize(self._safe, conversation_id)
|
|
203
|
+
return self._root / safe_channel / f"{safe_conv}.transcript"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ----------------------------
|
|
207
|
+
# Module-level helpers
|
|
208
|
+
# ----------------------------
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _sanitize(pattern: re.Pattern[str], value: str) -> str:
|
|
212
|
+
# Replace path-separators and illegal filename chars with '-'
|
|
213
|
+
value = (value or "").strip().replace(os.sep, "-").replace("/", "-")
|
|
214
|
+
value = pattern.sub("-", value)
|
|
215
|
+
return value or "unknown"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _get_ids(activity: Activity) -> Tuple[str, str]:
|
|
219
|
+
# Works with both dict-like and object-like Activity
|
|
220
|
+
def _get(obj: Any, *path: str) -> Optional[Any]:
|
|
221
|
+
cur = obj
|
|
222
|
+
for key in path:
|
|
223
|
+
if cur is None:
|
|
224
|
+
return None
|
|
225
|
+
if isinstance(cur, dict):
|
|
226
|
+
cur = cur.get(key)
|
|
227
|
+
else:
|
|
228
|
+
cur = getattr(cur, key, None)
|
|
229
|
+
return cur
|
|
230
|
+
|
|
231
|
+
channel_id = _get(activity, "channel_id") or _get(activity, "channelId")
|
|
232
|
+
conversation_id = _get(activity, "conversation", "id")
|
|
233
|
+
if not channel_id or not conversation_id:
|
|
234
|
+
raise ValueError("Activity must include channel_id and conversation.id")
|
|
235
|
+
return str(channel_id), str(conversation_id)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _to_plain_dict(activity: Activity) -> Dict[str, Any]:
|
|
239
|
+
|
|
240
|
+
if isinstance(activity, dict):
|
|
241
|
+
return activity
|
|
242
|
+
# Best-effort conversion for dataclass/attr/objects
|
|
243
|
+
try:
|
|
244
|
+
import dataclasses
|
|
245
|
+
|
|
246
|
+
if dataclasses.is_dataclass(activity):
|
|
247
|
+
return dataclasses.asdict(activity) # type: ignore[arg-type]
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
try:
|
|
251
|
+
return json.loads(
|
|
252
|
+
json.dumps(activity, default=lambda o: getattr(o, "__dict__", str(o)))
|
|
253
|
+
)
|
|
254
|
+
except Exception:
|
|
255
|
+
# Fallback: minimal projection
|
|
256
|
+
channel_id, conversation_id = _get_ids(activity)
|
|
257
|
+
return {
|
|
258
|
+
"type": getattr(activity, "type", "message"),
|
|
259
|
+
"id": getattr(activity, "id", None),
|
|
260
|
+
"channel_id": channel_id,
|
|
261
|
+
"conversation": {"id": conversation_id},
|
|
262
|
+
"text": getattr(activity, "text", None),
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _utc_iso_now() -> str:
|
|
267
|
+
return datetime.now(timezone.utc).isoformat()
|
|
@@ -5,16 +5,28 @@ import random
|
|
|
5
5
|
import string
|
|
6
6
|
import json
|
|
7
7
|
|
|
8
|
+
from typing import Any, Optional
|
|
8
9
|
from abc import ABC, abstractmethod
|
|
9
10
|
from datetime import datetime, timezone
|
|
10
11
|
from queue import Queue
|
|
11
12
|
from typing import Awaitable, Callable, List, Optional
|
|
13
|
+
from dataclasses import dataclass
|
|
12
14
|
|
|
13
15
|
from microsoft_agents.activity import Activity, ChannelAccount
|
|
14
16
|
from microsoft_agents.activity.activity import ConversationReference
|
|
15
17
|
from microsoft_agents.activity.activity_types import ActivityTypes
|
|
16
18
|
from microsoft_agents.activity.conversation_reference import ActivityEventNames
|
|
17
19
|
from microsoft_agents.hosting.core.middleware_set import Middleware, TurnContext
|
|
20
|
+
from typing import Generic, TypeVar
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class PagedResult(Generic[T]):
|
|
28
|
+
items: List[T]
|
|
29
|
+
continuation_token: Optional[str] = None
|
|
18
30
|
|
|
19
31
|
|
|
20
32
|
class TranscriptLogger(ABC):
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from threading import Lock
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
from typing import List
|
|
7
|
-
from .transcript_logger import TranscriptLogger
|
|
7
|
+
from .transcript_logger import TranscriptLogger, PagedResult
|
|
8
8
|
from .transcript_info import TranscriptInfo
|
|
9
9
|
from microsoft_agents.activity import Activity
|
|
10
10
|
|
|
@@ -52,7 +52,7 @@ class TranscriptMemoryStore(TranscriptLogger):
|
|
|
52
52
|
conversation_id: str,
|
|
53
53
|
continuation_token: str = None,
|
|
54
54
|
start_date: datetime = datetime.min.replace(tzinfo=timezone.utc),
|
|
55
|
-
) ->
|
|
55
|
+
) -> PagedResult[Activity]:
|
|
56
56
|
"""
|
|
57
57
|
Retrieves activities for a given channel and conversation, optionally filtered by start_date.
|
|
58
58
|
|
|
@@ -60,7 +60,7 @@ class TranscriptMemoryStore(TranscriptLogger):
|
|
|
60
60
|
:param conversation_id: The conversation ID to filter activities.
|
|
61
61
|
:param continuation_token: (Unused) Token for pagination.
|
|
62
62
|
:param start_date: Only activities with timestamp >= start_date are returned. None timestamps are treated as datetime.min.
|
|
63
|
-
:return: A
|
|
63
|
+
:return: A PagedResult containing the filtered list of Activity objects and a continuation token (always None).
|
|
64
64
|
:raises ValueError: If channel_id or conversation_id is None.
|
|
65
65
|
"""
|
|
66
66
|
if not channel_id:
|
|
@@ -98,7 +98,9 @@ class TranscriptMemoryStore(TranscriptLogger):
|
|
|
98
98
|
>= start_date
|
|
99
99
|
]
|
|
100
100
|
|
|
101
|
-
return
|
|
101
|
+
return PagedResult(
|
|
102
|
+
items=filtered_sorted_activities, continuation_token=None
|
|
103
|
+
)
|
|
102
104
|
|
|
103
105
|
async def delete_transcript(self, channel_id: str, conversation_id: str) -> None:
|
|
104
106
|
"""
|
|
@@ -126,13 +128,13 @@ class TranscriptMemoryStore(TranscriptLogger):
|
|
|
126
128
|
|
|
127
129
|
async def list_transcripts(
|
|
128
130
|
self, channel_id: str, continuation_token: str = None
|
|
129
|
-
) ->
|
|
131
|
+
) -> PagedResult[TranscriptInfo]:
|
|
130
132
|
"""
|
|
131
133
|
Lists all transcripts (unique conversation IDs) for a given channel.
|
|
132
134
|
|
|
133
135
|
:param channel_id: The channel ID to list transcripts for.
|
|
134
136
|
:param continuation_token: (Unused) Token for pagination.
|
|
135
|
-
:return: A
|
|
137
|
+
:return: A PagedResult containing a list of TranscriptInfo objects and a continuation token (always None).
|
|
136
138
|
:raises ValueError: If channel_id is None.
|
|
137
139
|
"""
|
|
138
140
|
if not channel_id:
|
|
@@ -151,4 +153,4 @@ class TranscriptMemoryStore(TranscriptLogger):
|
|
|
151
153
|
TranscriptInfo(channel_id=channel_id, conversation_id=conversation_id)
|
|
152
154
|
for conversation_id in conversations
|
|
153
155
|
]
|
|
154
|
-
return transcript_infos, None
|
|
156
|
+
return PagedResult(items=transcript_infos, continuation_token=None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microsoft-agents-hosting-core
|
|
3
|
-
Version: 0.4.0.
|
|
3
|
+
Version: 0.4.0.dev16
|
|
4
4
|
Summary: Core library for Microsoft Agents
|
|
5
5
|
Author: Microsoft Corporation
|
|
6
6
|
Project-URL: Homepage, https://github.com/microsoft/Agents
|
|
@@ -8,7 +8,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Requires-Python: >=3.9
|
|
11
|
-
Requires-Dist: microsoft-agents-activity==0.4.0.
|
|
11
|
+
Requires-Dist: microsoft-agents-activity==0.4.0.dev16
|
|
12
12
|
Requires-Dist: pyjwt>=2.10.1
|
|
13
13
|
Requires-Dist: isodate>=0.6.1
|
|
14
14
|
Requires-Dist: azure-core>=1.30.0
|
|
@@ -4,11 +4,11 @@ microsoft_agents/hosting/core/agent.py,sha256=K8v84y8ULP7rbcMKg8LxaM3haAq7f1oHFC
|
|
|
4
4
|
microsoft_agents/hosting/core/card_factory.py,sha256=UDmPEpOk2SpEr9ShN9Q0CiaI_GTD3qjHgkDMOWinW9I,6926
|
|
5
5
|
microsoft_agents/hosting/core/channel_adapter.py,sha256=zEfQILXagYujO_dTsqvTbqdegeOW4qEHP8SdJb_IPEY,10088
|
|
6
6
|
microsoft_agents/hosting/core/channel_api_handler_protocol.py,sha256=Nl4aOqpADWLAeIueI166TEmPwZX4XYnOiVGH9lzBuaY,4411
|
|
7
|
-
microsoft_agents/hosting/core/channel_service_adapter.py,sha256=
|
|
8
|
-
microsoft_agents/hosting/core/channel_service_client_factory_base.py,sha256=
|
|
7
|
+
microsoft_agents/hosting/core/channel_service_adapter.py,sha256=UdJKZIcp0xR8oMDpQWhbdc6MtqBsU-gVcN7ksHZVbHY,17412
|
|
8
|
+
microsoft_agents/hosting/core/channel_service_client_factory_base.py,sha256=UKYz8gCJmmwTRwCBle8O_dSGhflVsRRf2fPFZgYgzWo,1609
|
|
9
9
|
microsoft_agents/hosting/core/message_factory.py,sha256=F9QJBF4yBupHXxOW984ZzZomVEG57t9IUnTHwub-lX0,7822
|
|
10
10
|
microsoft_agents/hosting/core/middleware_set.py,sha256=TBsBs4KwAfKyHlQTlG4bl1y5UjkBzeMDs5w7LNB-Bi4,2585
|
|
11
|
-
microsoft_agents/hosting/core/rest_channel_service_client_factory.py,sha256=
|
|
11
|
+
microsoft_agents/hosting/core/rest_channel_service_client_factory.py,sha256=TBdgg_5nsmH1Ouhpsb7bV4OzdS-jKQxb9t9j45PevP4,6122
|
|
12
12
|
microsoft_agents/hosting/core/turn_context.py,sha256=df7TB1uXurgoAk338OF6taVfVgS58v662A9D9-GLP64,14794
|
|
13
13
|
microsoft_agents/hosting/core/_oauth/__init__.py,sha256=7KvwQEiINtA6KFlpgOLWF0acxtkS4hu-qlqHlQWH7AI,310
|
|
14
14
|
microsoft_agents/hosting/core/_oauth/_flow_state.py,sha256=WgvwVbEVsVb5ax75BleCVfGDs-Zw3beDZGlzW6jraCg,2185
|
|
@@ -68,7 +68,7 @@ microsoft_agents/hosting/core/connector/get_product_info.py,sha256=SDxPqBCzzQLEU
|
|
|
68
68
|
microsoft_agents/hosting/core/connector/user_token_base.py,sha256=kEJxGMMl-RYzqDKqjwjQPvc-acih0cJo3hOEjmmom5I,1195
|
|
69
69
|
microsoft_agents/hosting/core/connector/user_token_client_base.py,sha256=JTkbG70YXEnAQA_2yJn9dUGQ-hSCwNgU0C-V8y7kGEI,376
|
|
70
70
|
microsoft_agents/hosting/core/connector/client/__init__.py,sha256=6JdKhmm7btmo0omxMBd8PJbtGFk0cnMwVUoStyW7Ft0,143
|
|
71
|
-
microsoft_agents/hosting/core/connector/client/connector_client.py,sha256=
|
|
71
|
+
microsoft_agents/hosting/core/connector/client/connector_client.py,sha256=kN9SFbZTReXHjfJji1mJsFmlmvSyaq3hI-bV09W32r8,23473
|
|
72
72
|
microsoft_agents/hosting/core/connector/client/user_token_client.py,sha256=a6Y4pD8Ae-pEFA8FrXgb_mb0TzWjo81TMeDaRPqcGIw,10896
|
|
73
73
|
microsoft_agents/hosting/core/connector/teams/__init__.py,sha256=3ZMPGYyZ15EwvfQzfJJQy1J58oIt4InSxibl3BN6R54,100
|
|
74
74
|
microsoft_agents/hosting/core/connector/teams/teams_connector_client.py,sha256=XGQDTYHrA_I9n9JlxGST5eesjsFhz2dnSaMSuyoFnKU,12676
|
|
@@ -76,17 +76,18 @@ microsoft_agents/hosting/core/state/__init__.py,sha256=yckKi1wg_86ng-DL9Q3R49QiW
|
|
|
76
76
|
microsoft_agents/hosting/core/state/agent_state.py,sha256=p6AoKSPNpPR6Ubw_APjrQ_KyaQ9AqRYS63n4HcmMnzs,13211
|
|
77
77
|
microsoft_agents/hosting/core/state/state_property_accessor.py,sha256=kpiNnzkZ6el-oRITRbRkk1Faa_CPFxpJQdvSGxIJP70,1392
|
|
78
78
|
microsoft_agents/hosting/core/state/user_state.py,sha256=zEigX-sroNAyoQAxQjG1OgmJQKjk1zOkdeqylFg7M2E,1484
|
|
79
|
-
microsoft_agents/hosting/core/storage/__init__.py,sha256=
|
|
79
|
+
microsoft_agents/hosting/core/storage/__init__.py,sha256=Df_clI0uMRgcr4Td-xkP83bU_mGae7_gRMhtVDPZDmE,729
|
|
80
80
|
microsoft_agents/hosting/core/storage/_type_aliases.py,sha256=VCKtjiCBrhEsGSm3zVVSSccdoiY02GYhABvrLjhAcz8,72
|
|
81
81
|
microsoft_agents/hosting/core/storage/error_handling.py,sha256=zH34d7s4pJG_uajpBWhrtTpH2eMy88kSKaqvOqtbgzY,1265
|
|
82
82
|
microsoft_agents/hosting/core/storage/memory_storage.py,sha256=NADem1wQE1MOG1qMriYw4NjILHEBDbIG5HT6wvHfG2M,2353
|
|
83
83
|
microsoft_agents/hosting/core/storage/storage.py,sha256=vft_Kw4pkzo8NnBEyDx7gAn1Ndg2I9ePaxnuxbKVHzs,3227
|
|
84
84
|
microsoft_agents/hosting/core/storage/store_item.py,sha256=4LSkuI0H0lgWig88YoHFn6BP8Bx44YbyuvqBvaBNdEM,276
|
|
85
|
+
microsoft_agents/hosting/core/storage/transcript_file_store.py,sha256=7-ngOW5AuVPEHiAZJeZe_a5Qn4lQxlAiexjAwPiOJJU,10112
|
|
85
86
|
microsoft_agents/hosting/core/storage/transcript_info.py,sha256=5VN32j99tshChAffvuZ6D3GH3ABCZsQGHC_bYDAwFOk,328
|
|
86
|
-
microsoft_agents/hosting/core/storage/transcript_logger.py,sha256=
|
|
87
|
-
microsoft_agents/hosting/core/storage/transcript_memory_store.py,sha256=
|
|
87
|
+
microsoft_agents/hosting/core/storage/transcript_logger.py,sha256=_atDk3CJ05fIVMhlWGNa91IiM9bGLmOhasFko8Lxjhk,8237
|
|
88
|
+
microsoft_agents/hosting/core/storage/transcript_memory_store.py,sha256=v1Ud9LSs8m5c9_Fa8i49SuAjw80dX1hDciqbRduDEOE,6444
|
|
88
89
|
microsoft_agents/hosting/core/storage/transcript_store.py,sha256=ka74o0WvI5GhMZcFqSxVdamBhGzZcDZe6VNkG-sMy74,1944
|
|
89
|
-
microsoft_agents_hosting_core-0.4.0.
|
|
90
|
-
microsoft_agents_hosting_core-0.4.0.
|
|
91
|
-
microsoft_agents_hosting_core-0.4.0.
|
|
92
|
-
microsoft_agents_hosting_core-0.4.0.
|
|
90
|
+
microsoft_agents_hosting_core-0.4.0.dev16.dist-info/METADATA,sha256=xGmh4XPcm_fgm2ehlFAy0l959WNGh8575S5zhiHADsI,586
|
|
91
|
+
microsoft_agents_hosting_core-0.4.0.dev16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
92
|
+
microsoft_agents_hosting_core-0.4.0.dev16.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
|
|
93
|
+
microsoft_agents_hosting_core-0.4.0.dev16.dist-info/RECORD,,
|
|
File without changes
|