microsoft-agents-hosting-fastapi 0.7.0__py3-none-any.whl → 0.7.0.dev0__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.
@@ -5,9 +5,7 @@ from .cloud_adapter import CloudAdapter
5
5
  from .jwt_authorization_middleware import (
6
6
  JwtAuthorizationMiddleware,
7
7
  )
8
-
9
- # Import streaming utilities from core for backward compatibility
10
- from microsoft_agents.hosting.core.app.streaming import (
8
+ from .app.streaming import (
11
9
  Citation,
12
10
  CitationUtil,
13
11
  StreamingResponse,
@@ -1,58 +1,64 @@
1
1
  # Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  # Licensed under the MIT License.
3
+ import json
4
+ from typing import List, Union, Type
3
5
 
4
- from fastapi import APIRouter, Request, Response
6
+ from fastapi import APIRouter, Request, Response, HTTPException, Depends
5
7
  from fastapi.responses import JSONResponse
6
8
 
9
+ from microsoft_agents.activity import (
10
+ AgentsModel,
11
+ Activity,
12
+ AttachmentData,
13
+ ConversationParameters,
14
+ Transcript,
15
+ )
7
16
  from microsoft_agents.hosting.core import ChannelApiHandlerProtocol
8
- from microsoft_agents.hosting.core.http import ChannelServiceRoutes
9
17
 
10
18
 
11
- class FastApiRequestAdapter:
12
- """Adapter for FastAPI requests to use with ChannelServiceRoutes."""
19
+ async def deserialize_from_body(
20
+ request: Request, target_model: Type[AgentsModel]
21
+ ) -> AgentsModel:
22
+ content_type = request.headers.get("Content-Type", "")
23
+ if "application/json" in content_type:
24
+ body = await request.json()
25
+ else:
26
+ raise HTTPException(status_code=415, detail="Unsupported Media Type")
13
27
 
14
- def __init__(self, request: Request):
15
- self._request = request
28
+ return target_model.model_validate(body)
16
29
 
17
- @property
18
- def method(self) -> str:
19
- return self._request.method
20
30
 
21
- @property
22
- def headers(self):
23
- return self._request.headers
24
-
25
- async def json(self):
26
- return await self._request.json()
27
-
28
- def get_claims_identity(self):
29
- return getattr(self._request.state, "claims_identity", None)
31
+ def get_serialized_response(
32
+ model_or_list: Union[AgentsModel, List[AgentsModel]],
33
+ ) -> JSONResponse:
34
+ if isinstance(model_or_list, AgentsModel):
35
+ json_obj = model_or_list.model_dump(
36
+ mode="json", exclude_unset=True, by_alias=True
37
+ )
38
+ else:
39
+ json_obj = [
40
+ model.model_dump(mode="json", exclude_unset=True, by_alias=True)
41
+ for model in model_or_list
42
+ ]
30
43
 
31
- def get_path_param(self, name: str) -> str:
32
- return self._request.path_params.get(name, "")
44
+ return JSONResponse(content=json_obj)
33
45
 
34
46
 
35
47
  def channel_service_route_table(
36
48
  handler: ChannelApiHandlerProtocol, base_url: str = ""
37
49
  ) -> APIRouter:
38
- """Create FastAPI router for Channel Service API.
39
-
40
- Args:
41
- handler: The handler that implements the Channel API protocol.
42
- base_url: Optional base URL prefix for all routes.
43
-
44
- Returns:
45
- APIRouter with all channel service routes.
46
- """
47
50
  router = APIRouter()
48
- service_routes = ChannelServiceRoutes(handler, base_url)
49
51
 
50
52
  @router.post(base_url + "/v3/conversations/{conversation_id}/activities")
51
53
  async def send_to_conversation(conversation_id: str, request: Request):
52
- result = await service_routes.send_to_conversation(
53
- FastApiRequestAdapter(request)
54
+ activity = await deserialize_from_body(request, Activity)
55
+ result = await handler.on_send_to_conversation(
56
+ getattr(request.state, "claims_identity", None),
57
+ conversation_id,
58
+ activity,
54
59
  )
55
- return JSONResponse(content=result)
60
+
61
+ return get_serialized_response(result)
56
62
 
57
63
  @router.post(
58
64
  base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}"
@@ -60,21 +66,40 @@ def channel_service_route_table(
60
66
  async def reply_to_activity(
61
67
  conversation_id: str, activity_id: str, request: Request
62
68
  ):
63
- result = await service_routes.reply_to_activity(FastApiRequestAdapter(request))
64
- return JSONResponse(content=result)
69
+ activity = await deserialize_from_body(request, Activity)
70
+ result = await handler.on_reply_to_activity(
71
+ getattr(request.state, "claims_identity", None),
72
+ conversation_id,
73
+ activity_id,
74
+ activity,
75
+ )
76
+
77
+ return get_serialized_response(result)
65
78
 
66
79
  @router.put(
67
80
  base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}"
68
81
  )
69
82
  async def update_activity(conversation_id: str, activity_id: str, request: Request):
70
- result = await service_routes.update_activity(FastApiRequestAdapter(request))
71
- return JSONResponse(content=result)
83
+ activity = await deserialize_from_body(request, Activity)
84
+ result = await handler.on_update_activity(
85
+ getattr(request.state, "claims_identity", None),
86
+ conversation_id,
87
+ activity_id,
88
+ activity,
89
+ )
90
+
91
+ return get_serialized_response(result)
72
92
 
73
93
  @router.delete(
74
94
  base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}"
75
95
  )
76
96
  async def delete_activity(conversation_id: str, activity_id: str, request: Request):
77
- await service_routes.delete_activity(FastApiRequestAdapter(request))
97
+ await handler.on_delete_activity(
98
+ getattr(request.state, "claims_identity", None),
99
+ conversation_id,
100
+ activity_id,
101
+ )
102
+
78
103
  return Response(status_code=200)
79
104
 
80
105
  @router.get(
@@ -84,65 +109,97 @@ def channel_service_route_table(
84
109
  async def get_activity_members(
85
110
  conversation_id: str, activity_id: str, request: Request
86
111
  ):
87
- result = await service_routes.get_activity_members(
88
- FastApiRequestAdapter(request)
112
+ result = await handler.on_get_activity_members(
113
+ getattr(request.state, "claims_identity", None),
114
+ conversation_id,
115
+ activity_id,
89
116
  )
90
- return JSONResponse(content=result)
117
+
118
+ return get_serialized_response(result)
91
119
 
92
120
  @router.post(base_url + "/")
93
121
  async def create_conversation(request: Request):
94
- result = await service_routes.create_conversation(
95
- FastApiRequestAdapter(request)
122
+ conversation_parameters = await deserialize_from_body(
123
+ request, ConversationParameters
124
+ )
125
+ result = await handler.on_create_conversation(
126
+ getattr(request.state, "claims_identity", None), conversation_parameters
96
127
  )
97
- return JSONResponse(content=result)
128
+
129
+ return get_serialized_response(result)
98
130
 
99
131
  @router.get(base_url + "/")
100
132
  async def get_conversation(request: Request):
101
- result = await service_routes.get_conversations(FastApiRequestAdapter(request))
102
- return JSONResponse(content=result)
133
+ # TODO: continuation token? conversation_id?
134
+ result = await handler.on_get_conversations(
135
+ getattr(request.state, "claims_identity", None), None
136
+ )
137
+
138
+ return get_serialized_response(result)
103
139
 
104
140
  @router.get(base_url + "/v3/conversations/{conversation_id}/members")
105
141
  async def get_conversation_members(conversation_id: str, request: Request):
106
- result = await service_routes.get_conversation_members(
107
- FastApiRequestAdapter(request)
142
+ result = await handler.on_get_conversation_members(
143
+ getattr(request.state, "claims_identity", None),
144
+ conversation_id,
108
145
  )
109
- return JSONResponse(content=result)
146
+
147
+ return get_serialized_response(result)
110
148
 
111
149
  @router.get(base_url + "/v3/conversations/{conversation_id}/members/{member_id}")
112
150
  async def get_conversation_member(
113
151
  conversation_id: str, member_id: str, request: Request
114
152
  ):
115
- result = await service_routes.get_conversation_member(
116
- FastApiRequestAdapter(request)
153
+ result = await handler.on_get_conversation_member(
154
+ getattr(request.state, "claims_identity", None),
155
+ member_id,
156
+ conversation_id,
117
157
  )
118
- return JSONResponse(content=result)
158
+
159
+ return get_serialized_response(result)
119
160
 
120
161
  @router.get(base_url + "/v3/conversations/{conversation_id}/pagedmembers")
121
162
  async def get_conversation_paged_members(conversation_id: str, request: Request):
122
- result = await service_routes.get_conversation_paged_members(
123
- FastApiRequestAdapter(request)
163
+ # TODO: continuation token? page size?
164
+ result = await handler.on_get_conversation_paged_members(
165
+ getattr(request.state, "claims_identity", None),
166
+ conversation_id,
124
167
  )
125
- return JSONResponse(content=result)
168
+
169
+ return get_serialized_response(result)
126
170
 
127
171
  @router.delete(base_url + "/v3/conversations/{conversation_id}/members/{member_id}")
128
172
  async def delete_conversation_member(
129
173
  conversation_id: str, member_id: str, request: Request
130
174
  ):
131
- result = await service_routes.delete_conversation_member(
132
- FastApiRequestAdapter(request)
175
+ result = await handler.on_delete_conversation_member(
176
+ getattr(request.state, "claims_identity", None),
177
+ conversation_id,
178
+ member_id,
133
179
  )
134
- return JSONResponse(content=result)
180
+
181
+ return get_serialized_response(result)
135
182
 
136
183
  @router.post(base_url + "/v3/conversations/{conversation_id}/activities/history")
137
184
  async def send_conversation_history(conversation_id: str, request: Request):
138
- result = await service_routes.send_conversation_history(
139
- FastApiRequestAdapter(request)
185
+ transcript = await deserialize_from_body(request, Transcript)
186
+ result = await handler.on_send_conversation_history(
187
+ getattr(request.state, "claims_identity", None),
188
+ conversation_id,
189
+ transcript,
140
190
  )
141
- return JSONResponse(content=result)
191
+
192
+ return get_serialized_response(result)
142
193
 
143
194
  @router.post(base_url + "/v3/conversations/{conversation_id}/attachments")
144
195
  async def upload_attachment(conversation_id: str, request: Request):
145
- result = await service_routes.upload_attachment(FastApiRequestAdapter(request))
146
- return JSONResponse(content=result)
196
+ attachment_data = await deserialize_from_body(request, AttachmentData)
197
+ result = await handler.on_upload_attachment(
198
+ getattr(request.state, "claims_identity", None),
199
+ conversation_id,
200
+ attachment_data,
201
+ )
202
+
203
+ return get_serialized_response(result)
147
204
 
148
205
  return router
@@ -1,48 +1,32 @@
1
1
  # Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  # Licensed under the MIT License.
3
+ from traceback import format_exc
3
4
  from typing import Optional
4
5
 
5
- from fastapi import Request, Response
6
+ from fastapi import Request, Response, HTTPException
6
7
  from fastapi.responses import JSONResponse
7
-
8
- from microsoft_agents.hosting.core import Agent
9
- from microsoft_agents.hosting.core.authorization import Connections
10
- from microsoft_agents.hosting.core.http import (
11
- HttpAdapterBase,
12
- HttpResponse,
8
+ from microsoft_agents.hosting.core import error_resources
9
+ from microsoft_agents.hosting.core.authorization import (
10
+ ClaimsIdentity,
11
+ Connections,
12
+ )
13
+ from microsoft_agents.activity import (
14
+ Activity,
15
+ DeliveryModes,
16
+ )
17
+ from microsoft_agents.hosting.core import (
18
+ Agent,
19
+ ChannelServiceAdapter,
20
+ ChannelServiceClientFactoryBase,
21
+ MessageFactory,
22
+ RestChannelServiceClientFactory,
23
+ TurnContext,
13
24
  )
14
- from microsoft_agents.hosting.core import ChannelServiceClientFactoryBase
15
25
 
16
26
  from .agent_http_adapter import AgentHttpAdapter
17
27
 
18
28
 
19
- class FastApiRequestAdapter:
20
- """Adapter to make FastAPI Request compatible with HttpRequestProtocol."""
21
-
22
- def __init__(self, request: Request):
23
- self._request = request
24
-
25
- @property
26
- def method(self) -> str:
27
- return self._request.method
28
-
29
- @property
30
- def headers(self):
31
- return self._request.headers
32
-
33
- async def json(self):
34
- return await self._request.json()
35
-
36
- def get_claims_identity(self):
37
- return getattr(self._request.state, "claims_identity", None)
38
-
39
- def get_path_param(self, name: str) -> str:
40
- return self._request.path_params.get(name, "")
41
-
42
-
43
- class CloudAdapter(HttpAdapterBase, AgentHttpAdapter):
44
- """CloudAdapter for FastAPI web framework."""
45
-
29
+ class CloudAdapter(ChannelServiceAdapter, AgentHttpAdapter):
46
30
  def __init__(
47
31
  self,
48
32
  *,
@@ -52,43 +36,78 @@ class CloudAdapter(HttpAdapterBase, AgentHttpAdapter):
52
36
  """
53
37
  Initializes a new instance of the CloudAdapter class.
54
38
 
55
- :param connection_manager: Optional connection manager for OAuth.
56
39
  :param channel_service_client_factory: The factory to use to create the channel service client.
57
40
  """
58
- super().__init__(
59
- connection_manager=connection_manager,
60
- channel_service_client_factory=channel_service_client_factory,
61
- )
62
41
 
63
- async def process(self, request: Request, agent: Agent) -> Optional[Response]:
64
- """Process a FastAPI request.
42
+ async def on_turn_error(context: TurnContext, error: Exception):
43
+ error_message = f"Exception caught : {error}"
44
+ print(format_exc())
65
45
 
66
- Args:
67
- request: The FastAPI request.
68
- agent: The agent to handle the request.
46
+ await context.send_activity(MessageFactory.text(error_message))
69
47
 
70
- Returns:
71
- FastAPI Response object.
72
- """
73
- # Adapt request to protocol
74
- adapted_request = FastApiRequestAdapter(request)
75
-
76
- # Process using base implementation
77
- http_response: HttpResponse = await self.process_request(adapted_request, agent)
78
-
79
- # Convert HttpResponse to FastAPI Response
80
- return self._to_fastapi_response(http_response)
81
-
82
- @staticmethod
83
- def _to_fastapi_response(http_response: HttpResponse) -> Response:
84
- """Convert HttpResponse to FastAPI Response."""
85
- if http_response.body is not None:
86
- return JSONResponse(
87
- content=http_response.body,
88
- status_code=http_response.status_code,
89
- headers=http_response.headers,
48
+ # Send a trace activity
49
+ await context.send_trace_activity(
50
+ "OnTurnError Trace",
51
+ error_message,
52
+ "https://www.botframework.com/schemas/error",
53
+ "TurnError",
90
54
  )
91
- return Response(
92
- status_code=http_response.status_code,
93
- headers=http_response.headers,
55
+
56
+ self.on_turn_error = on_turn_error
57
+
58
+ channel_service_client_factory = (
59
+ channel_service_client_factory
60
+ or RestChannelServiceClientFactory(connection_manager)
94
61
  )
62
+
63
+ super().__init__(channel_service_client_factory)
64
+
65
+ async def process(self, request: Request, agent: Agent) -> Optional[Response]:
66
+ if not request:
67
+ raise TypeError(str(error_resources.RequestRequired))
68
+ if not agent:
69
+ raise TypeError(str(error_resources.AgentRequired))
70
+
71
+ if request.method == "POST":
72
+ # Deserialize the incoming Activity
73
+ content_type = request.headers.get("Content-Type", "")
74
+ if "application/json" in content_type:
75
+ body = await request.json()
76
+ else:
77
+ raise HTTPException(status_code=415, detail="Unsupported Media Type")
78
+
79
+ activity: Activity = Activity.model_validate(body)
80
+
81
+ # default to anonymous identity with no claims
82
+ claims_identity: ClaimsIdentity = getattr(
83
+ request.state, "claims_identity", ClaimsIdentity({}, False)
84
+ )
85
+
86
+ # A POST request must contain an Activity
87
+ if (
88
+ not activity.type
89
+ or not activity.conversation
90
+ or not activity.conversation.id
91
+ ):
92
+ raise HTTPException(status_code=400, detail="Bad Request")
93
+
94
+ try:
95
+ # Process the inbound activity with the agent
96
+ invoke_response = await self.process_activity(
97
+ claims_identity, activity, agent.on_turn
98
+ )
99
+
100
+ if (
101
+ activity.type == "invoke"
102
+ or activity.delivery_mode == DeliveryModes.expect_replies
103
+ ):
104
+ # Invoke and ExpectReplies cannot be performed async, the response must be written before the calling thread is released.
105
+ return JSONResponse(
106
+ content=invoke_response.body, status_code=invoke_response.status
107
+ )
108
+
109
+ return Response(status_code=202)
110
+ except PermissionError:
111
+ raise HTTPException(status_code=401, detail="Unauthorized")
112
+ else:
113
+ raise HTTPException(status_code=405, detail="Method Not Allowed")
@@ -7,6 +7,7 @@ from microsoft_agents.hosting.core import (
7
7
  JwtTokenValidator,
8
8
  )
9
9
 
10
+
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-hosting-fastapi
3
- Version: 0.7.0
3
+ Version: 0.7.0.dev0
4
4
  Summary: Integration library for Microsoft Agents with FastAPI
5
5
  Author: Microsoft Corporation
6
6
  License-Expression: MIT
@@ -10,7 +10,7 @@ Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: microsoft-agents-hosting-core==0.7.0
13
+ Requires-Dist: microsoft-agents-hosting-core==0.7.0.dev0
14
14
  Requires-Dist: fastapi>=0.104.0
15
15
  Dynamic: license-file
16
16
  Dynamic: requires-dist
@@ -19,33 +19,6 @@ Dynamic: requires-dist
19
19
 
20
20
  This library provides FastAPI integration for Microsoft Agents, enabling you to build conversational agents using the FastAPI web framework.
21
21
 
22
- ## Release Notes
23
- <table style="width:100%">
24
- <tr>
25
- <th style="width:20%">Version</th>
26
- <th style="width:20%">Date</th>
27
- <th style="width:60%">Release Notes</th>
28
- </tr>
29
- <tr>
30
- <td>0.6.1</td>
31
- <td>2025-12-01</td>
32
- <td>
33
- <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v061">
34
- 0.6.1 Release Notes
35
- </a>
36
- </td>
37
- </tr>
38
- <tr>
39
- <td>0.6.0</td>
40
- <td>2025-11-18</td>
41
- <td>
42
- <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v060">
43
- 0.6.0 Release Notes
44
- </a>
45
- </td>
46
- </tr>
47
- </table>
48
-
49
22
  ## Features
50
23
 
51
24
  - FastAPI integration for Microsoft Agents
@@ -1,16 +1,16 @@
1
- microsoft_agents/hosting/fastapi/__init__.py,sha256=SJbcJL-CZSyTw_nGkeMljzopavgtvhfxH-O5Vjq3nd8,688
1
+ microsoft_agents/hosting/fastapi/__init__.py,sha256=9IuCCWHY4FEPGAzv9uEyob0irIbMqAomjjiYKs95NPA,592
2
2
  microsoft_agents/hosting/fastapi/_start_agent_process.py,sha256=u4aTpiPYzhcOPD1nJjVD1stRFGmsysAE3QS8vOV_37c,946
3
3
  microsoft_agents/hosting/fastapi/agent_http_adapter.py,sha256=40fi8bPXzXMGMAdeK1NDT34RSbKP--jLzBvRJ159-BQ,427
4
- microsoft_agents/hosting/fastapi/channel_service_route_table.py,sha256=3BnYYFgTAXyMhne3St6iJxtY1-3NKXGr2h5Yr71m-Mg,5546
5
- microsoft_agents/hosting/fastapi/cloud_adapter.py,sha256=qZAbl11yENlocQ_NB7sEnuuLr8k3roIvYnyiRxrAI3Q,3010
6
- microsoft_agents/hosting/fastapi/jwt_authorization_middleware.py,sha256=6gGL4IYVOTHUQP42a_BbmpnY5_U5-S9XFRt7iWYug_Y,2599
4
+ microsoft_agents/hosting/fastapi/channel_service_route_table.py,sha256=NHt5958_SpcT1O6tTSPuTtZX0R196IAcDUr5gWuTYZ4,7113
5
+ microsoft_agents/hosting/fastapi/cloud_adapter.py,sha256=wg9wFjRvEvM1scD8lqBASXhcoGlsssB8cxfKMROp-Co,4028
6
+ microsoft_agents/hosting/fastapi/jwt_authorization_middleware.py,sha256=YMLqoXrUcC5klaLiKT4uP_dF7Tz1KbsVJjL2PvWgkLQ,2600
7
7
  microsoft_agents/hosting/fastapi/app/__init__.py,sha256=TioskqZet16twXOsI3X2snyLzmuyeKNtN2dySD1Xw7s,253
8
8
  microsoft_agents/hosting/fastapi/app/streaming/__init__.py,sha256=G_VGmQ0m6TkHZsHjRV5HitaCOt2EBEjENIoBYabJMqM,292
9
9
  microsoft_agents/hosting/fastapi/app/streaming/citation.py,sha256=ZGaMUOWxxoMplwRrkFsjnK7Z12V6rT5odE7qZCu-mP8,498
10
10
  microsoft_agents/hosting/fastapi/app/streaming/citation_util.py,sha256=c95c3Y3genmFc0vSXppPaD1-ShFohAV1UABZnyJS_BQ,2478
11
11
  microsoft_agents/hosting/fastapi/app/streaming/streaming_response.py,sha256=c5jytVvOIV3f1zKWxpFFxvCS6nR_8mi7bhv5pYusIGw,13325
12
- microsoft_agents_hosting_fastapi-0.7.0.dist-info/licenses/LICENSE,sha256=oDrK6gJRdwYynx5l4UtyDa2nX_D1WWkvionBYrCebek,1073
13
- microsoft_agents_hosting_fastapi-0.7.0.dist-info/METADATA,sha256=ZYG_tZsddfBNtaBaFw2SSCfkBBcS8ohO1-ehRdMeL5E,2130
14
- microsoft_agents_hosting_fastapi-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- microsoft_agents_hosting_fastapi-0.7.0.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
16
- microsoft_agents_hosting_fastapi-0.7.0.dist-info/RECORD,,
12
+ microsoft_agents_hosting_fastapi-0.7.0.dev0.dist-info/licenses/LICENSE,sha256=oDrK6gJRdwYynx5l4UtyDa2nX_D1WWkvionBYrCebek,1073
13
+ microsoft_agents_hosting_fastapi-0.7.0.dev0.dist-info/METADATA,sha256=_vpvIr9V7X1XwKTp27MybUG1PTM7rBOjY47xexKxHbY,1431
14
+ microsoft_agents_hosting_fastapi-0.7.0.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ microsoft_agents_hosting_fastapi-0.7.0.dev0.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
16
+ microsoft_agents_hosting_fastapi-0.7.0.dev0.dist-info/RECORD,,