Activity Protocol Schema v5 Translation pseudocode (v3 → v5)
Open
@gurubhg is already working on this.
Since Mar 17, 2026.
Discussion
Specs
- Dominant language
- TypeSpec
- Stars
- 1.1k
- Forks
- 340
- Avg merge
- 3d 13h
- Merged PRs (30d)
- 12
Description
Activity Protocol v5 — Translation Pseudocode (§17.4)
Parent issue: #472 — Activity Protocol Schema v5 Proposal
Section: §17.4 Translation pseudocode (v3 → v5)
This sub-issue contains the complete translation pseudocode for converting v3 activities to v5 format.
17.4 Translation pseudocode (v3 → v5)
def v3_to_v5(v3_activity):
v5 = {
"v": "5",
"type": map_type(v3_activity["type"]),
"id": v3_activity.get("id", generate_id()),
"timestamp": v3_activity.get("timestamp", now_utc()),
"channelId": v3_activity.get("channelId"),
"conversation": { "id": v3_activity["conversation"]["id"] },
"from": { "id": v3_activity["from"]["id"],
"name": v3_activity["from"].get("name"),
"role": v3_activity["from"].get("role") },
}
# Map recipient → to[]
if "recipient" in v3_activity:
v5["to"] = [{ "id": v3_activity["recipient"]["id"],
"name": v3_activity["recipient"].get("name") }]
# Map replyToId → relatesTo.inReplyTo
if "replyToId" in v3_activity:
v5.setdefault("relatesTo", {})["inReplyTo"] = {
"activityId": v3_activity["replyToId"]
}
# Map message content → payload
if v3_activity["type"] == "message":
v5["payload"] = build_payload(v3_activity)
# Map messageUpdate → message with replaces
if v3_activity["type"] == "messageUpdate":
v5.setdefault("relatesTo", {})["replaces"] = {
"activityId": v3_activity["id"]
}
v5["payload"] = build_payload(v3_activity)
# Map messageDelete → event with tombstone
if v3_activity["type"] == "messageDelete":
v5["name"] = "message.delete"
v5.setdefault("relatesTo", {})["tombstones"] = {
"activityId": v3_activity["id"]
}
# Map conversationUpdate → event with payload
if v3_activity["type"] == "conversationUpdate":
v5["name"] = "conversation.update"
v5["payload"] = {
"contentType": "application/vnd.microsoft.activity.conversation.update+json",
"content": {
"membersAdded": v3_activity.get("membersAdded", []),
"membersRemoved": v3_activity.get("membersRemoved", []),
"topicName": v3_activity.get("topicName")
}
}
# Map typing → event
if v3_activity["type"] == "typing":
v5["name"] = "typing"
# No payload needed for typing
# Map messageReaction → event with reactsTo
if v3_activity["type"] == "messageReaction":
v5["name"] = "message.reaction"
if "replyToId" in v3_activity:
v5.setdefault("relatesTo", {})["reactsTo"] = {
"activityId": v3_activity["replyToId"]
}
if "reactionsAdded" in v3_activity or "reactionsRemoved" in v3_activity:
v5["payload"] = {
"contentType": "application/vnd.microsoft.activity.reaction+json",
"content": {
"reactionsAdded": v3_activity.get("reactionsAdded", []),
"reactionsRemoved": v3_activity.get("reactionsRemoved", [])
}
}
# Map invoke → command
if v3_activity["type"] == "invoke":
v5["name"] = v3_activity.get("name", "")
if "value" in v3_activity:
v5["payload"] = {
"contentType": v3_activity.get("valueType", "application/json"),
"content": v3_activity["value"]
}
# Map endOfConversation → event
if v3_activity["type"] == "endOfConversation":
v5["name"] = "conversation.end"
if "code" in v3_activity:
v5["payload"] = {
"contentType": "application/json",
"content": { "code": v3_activity["code"] }
}
# Map event → event (direct mapping)
if v3_activity["type"] == "event":
v5["name"] = v3_activity["name"]
if "value" in v3_activity:
v5["payload"] = {
"contentType": v3_activity.get("valueType", "application/json"),
"content": v3_activity["value"]
}
# Map contactRelationUpdate → event
if v3_activity["type"] == "contactRelationUpdate":
v5["name"] = "contact.update"
v5["payload"] = {
"contentType": "application/vnd.microsoft.activity.contact.update+json",
"content": { "action": v3_activity.get("action") }
}
# Map installationUpdate → event
if v3_activity["type"] == "installationUpdate":
v5["name"] = "installation.update"
v5["payload"] = {
"contentType": "application/vnd.microsoft.activity.installation.update+json",
"content": { "action": v3_activity.get("action") }
}
# Map handoff → event
if v3_activity["type"] == "handoff":
v5["name"] = "handoff"
if "value" in v3_activity:
v5["payload"] = {
"contentType": "application/json",
"content": v3_activity["value"]
}
# Map locale → locale entity
if "locale" in v3_activity:
v5.setdefault("entities", []).append({
"type": "locale",
"language": v3_activity["locale"]
})
# Map suggestedActions → payload item
if "suggestedActions" in v3_activity and v3_activity["suggestedActions"]:
payload = v5.get("payload", {})
items = payload.get("items", [])
items.append({
"kind": "json",
"contentType": "application/vnd.microsoft.activity.suggestedActions+json",
"content": v3_activity["suggestedActions"]
})
payload["items"] = items
if "contentType" not in payload:
payload["contentType"] = "multipart/mixed"
v5["payload"] = payload
# Map callerId → security entity
if "callerId" in v3_activity:
v5.setdefault("entities", []).append({
"type": "security",
"caller": { "id": v3_activity["callerId"] }
})
return v5
def map_type(v3_type):
mapping = {
"message": "message",
"event": "event",
"invoke": "command",
"command": "command",
"commandResult": "commandResult",
"trace": "trace",
"conversationUpdate": "event", # name = "conversation.update"
"contactRelationUpdate": "event", # name = "contact.update"
"installationUpdate": "event", # name = "installation.update"
"endOfConversation": "event", # name = "conversation.end"
"typing": "event", # name = "typing"
"messageReaction": "event", # name = "message.reaction"
"messageDelete": "event", # relatesTo.tombstones
"messageUpdate": "message", # relatesTo.replaces
"suggestion": "message",
"handoff": "event", # name = "handoff"
}
return mapping.get(v3_type, v3_type)
def build_payload(v3_activity):
"""Build a v5 payload from a v3 message-type activity."""
payload = {}
# Primary text content
if "text" in v3_activity:
text_format = v3_activity.get("textFormat", "plain")
if text_format == "markdown":
payload["contentType"] = "text/markdown"
elif text_format == "xml":
payload["contentType"] = "text/xml"
else:
payload["contentType"] = "text/plain"
payload["content"] = v3_activity["text"]
# Attachments → items
if "attachments" in v3_activity and v3_activity["attachments"]:
items = []
for att in v3_activity["attachments"]:
item = {
"contentType": att.get("contentType", "application/octet-stream"),
}
# Determine kind from contentType
ct = item["contentType"].lower()
if ct.startswith("image/"):
item["kind"] = "image"
elif ct.startswith("audio/"):
item["kind"] = "audio"
elif ct.startswith("video/"):
item["kind"] = "video"
elif ct.startswith("application/vnd.microsoft.card."):
item["kind"] = "json"
elif ct.startswith("application/"):
item["kind"] = "json"
else:
item["kind"] = "file"
if "content" in att:
item["content"] = att["content"]
if "contentUrl" in att:
item["contentUrl"] = att["contentUrl"]
if "name" in att:
item["name"] = att["name"]
if "thumbnailUrl" in att:
item["thumbnailUrl"] = att["thumbnailUrl"]
items.append(item)
payload["items"] = items
if "contentType" not in payload:
payload["contentType"] = "multipart/mixed"
# value/valueType (for invoke/command types handled elsewhere,
# but some message activities also use value)
if "value" in v3_activity and "text" not in v3_activity:
payload["contentType"] = v3_activity.get("valueType", "application/json")
payload["content"] = v3_activity["value"]
return payload if payload else {"contentType": "text/plain", "content": ""}
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.