@agent-relay/sdk 10.4.0 → 10.5.0
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.
- package/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/bin/agent-relay-broker-win32-x64.exe +0 -0
- package/dist/agent-relay.d.ts +23 -0
- package/dist/agent-relay.d.ts.map +1 -1
- package/dist/agent-relay.js +57 -5
- package/dist/agent-relay.js.map +1 -1
- package/dist/delivery/types.d.ts +5 -1
- package/dist/delivery/types.d.ts.map +1 -1
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +16 -4
- package/dist/listeners.js.map +1 -1
- package/dist/messaging/event-fanin.d.ts +54 -0
- package/dist/messaging/event-fanin.d.ts.map +1 -0
- package/dist/messaging/event-fanin.js +293 -0
- package/dist/messaging/event-fanin.js.map +1 -0
- package/dist/messaging/index.d.ts +2 -0
- package/dist/messaging/index.d.ts.map +1 -1
- package/dist/messaging/index.js +2 -0
- package/dist/messaging/index.js.map +1 -1
- package/dist/messaging/normalize.d.ts.map +1 -1
- package/dist/messaging/normalize.js +479 -509
- package/dist/messaging/normalize.js.map +1 -1
- package/dist/messaging/observer-source.d.ts +80 -0
- package/dist/messaging/observer-source.d.ts.map +1 -0
- package/dist/messaging/observer-source.js +502 -0
- package/dist/messaging/observer-source.js.map +1 -0
- package/dist/messaging/relaycast-client.d.ts +2 -1
- package/dist/messaging/relaycast-client.d.ts.map +1 -1
- package/dist/messaging/relaycast-client.js.map +1 -1
- package/dist/messaging/types.d.ts +133 -107
- package/dist/messaging/types.d.ts.map +1 -1
- package/package.json +5 -3
|
@@ -1,42 +1,72 @@
|
|
|
1
|
+
import { AgentPresenceInfoSchema, AgentSchema, AgentStatusSchema, AgentTypeSchema, ChannelMemberInfoSchema, ChannelReadStatusSchema, ChannelSchema, CreateAgentResponseSchema, CreateGroupDmResponseSchema, DeliveryMessageSchema, DeliverySchema, DeliveryStatusSchema, InboxResponseSchema, MessageInjectionModeSchema, MessageSchema, MessageWithMetaSchema, ReactionGroupSchema, ReadReceiptSchema, ReaderInfoSchema, SearchMessageResultSchema, } from '@relaycast/types';
|
|
2
|
+
import { z } from 'zod';
|
|
1
3
|
function isRecord(value) {
|
|
2
4
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
3
5
|
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// ── Wire boundary ────────────────────────────────────────────────────────────
|
|
7
|
+
//
|
|
8
|
+
// `@relaycast/sdk` camelizes wire responses at runtime, while the canonical
|
|
9
|
+
// contract in `@relaycast/types` (and injected raw clients) is snake_case.
|
|
10
|
+
// Instead of probing both spellings field by field, every payload is folded
|
|
11
|
+
// back onto the canonical snake_case wire shape once, then validated with a
|
|
12
|
+
// schema derived from `@relaycast/types` and mapped deliberately.
|
|
13
|
+
/** Keys whose values are caller-defined payloads that must pass through untouched. */
|
|
14
|
+
const PASSTHROUGH_KEYS = new Set([
|
|
15
|
+
'metadata',
|
|
16
|
+
'blocks',
|
|
17
|
+
'data',
|
|
18
|
+
'value',
|
|
19
|
+
'input',
|
|
20
|
+
'output',
|
|
21
|
+
'parameters',
|
|
22
|
+
'headers',
|
|
23
|
+
]);
|
|
24
|
+
function toSnakeKey(key) {
|
|
25
|
+
return key.replace(/[A-Z]/g, (char, index) => (index > 0 ? '_' : '') + char.toLowerCase());
|
|
26
|
+
}
|
|
27
|
+
function toWire(value) {
|
|
28
|
+
if (Array.isArray(value))
|
|
29
|
+
return value.map(toWire);
|
|
30
|
+
if (!isRecord(value))
|
|
31
|
+
return value;
|
|
32
|
+
// Null-prototype output: an untrusted `__proto__` key (own on JSON.parse'd
|
|
33
|
+
// payloads) stays an ordinary data property instead of hijacking the
|
|
34
|
+
// prototype and feeding inherited fields to downstream reads.
|
|
35
|
+
const out = Object.create(null);
|
|
36
|
+
for (const [key, val] of Object.entries(value)) {
|
|
37
|
+
const snake = toSnakeKey(key);
|
|
38
|
+
out[snake] = PASSTHROUGH_KEYS.has(snake) ? val : toWire(val);
|
|
10
39
|
}
|
|
11
|
-
return
|
|
40
|
+
return out;
|
|
12
41
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (typeof value === 'number' || typeof value === 'bigint')
|
|
18
|
-
return String(value);
|
|
19
|
-
return undefined;
|
|
42
|
+
/** Snake-case a payload's keys and validate it against a canonical-derived schema. */
|
|
43
|
+
function parseWire(schema, input) {
|
|
44
|
+
const wired = toWire(input);
|
|
45
|
+
return schema.parse(isRecord(wired) ? wired : {});
|
|
20
46
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return
|
|
47
|
+
/** Drop undefined values so optional fields stay absent instead of present-but-undefined. */
|
|
48
|
+
function compact(value) {
|
|
49
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));
|
|
24
50
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
51
|
+
/** Collapse empty/null strings to undefined (absent). */
|
|
52
|
+
function opt(value) {
|
|
53
|
+
return value ? value : undefined;
|
|
54
|
+
}
|
|
55
|
+
function str(record, key) {
|
|
56
|
+
const value = record[key];
|
|
57
|
+
return typeof value === 'string' ? value : undefined;
|
|
28
58
|
}
|
|
29
|
-
function
|
|
30
|
-
const value =
|
|
59
|
+
function num(record, key) {
|
|
60
|
+
const value = record[key];
|
|
31
61
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
32
62
|
}
|
|
33
|
-
function
|
|
34
|
-
const value =
|
|
35
|
-
return
|
|
63
|
+
function bool(record, key) {
|
|
64
|
+
const value = record[key];
|
|
65
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
36
66
|
}
|
|
37
|
-
function
|
|
38
|
-
const value =
|
|
39
|
-
return isRecord(value) ?
|
|
67
|
+
function rec(record, key) {
|
|
68
|
+
const value = record[key];
|
|
69
|
+
return isRecord(value) ? value : {};
|
|
40
70
|
}
|
|
41
71
|
function normalizeOptionalChannelName(value) {
|
|
42
72
|
if (!value)
|
|
@@ -46,384 +76,369 @@ function normalizeOptionalChannelName(value) {
|
|
|
46
76
|
export function normalizeChannelName(value) {
|
|
47
77
|
return normalizeOptionalChannelName(value) ?? value;
|
|
48
78
|
}
|
|
49
|
-
function normalizeAgentType(value) {
|
|
50
|
-
return value === 'human' || value === 'system' ? value : 'agent';
|
|
51
|
-
}
|
|
52
|
-
function normalizeAgentStatus(value) {
|
|
53
|
-
if (value === 'online' || value === 'offline' || value === 'away')
|
|
54
|
-
return value;
|
|
55
|
-
return 'unknown';
|
|
56
|
-
}
|
|
57
|
-
function normalizeOnlineStatus(value) {
|
|
58
|
-
return value === 'online' ? 'online' : 'offline';
|
|
59
|
-
}
|
|
60
|
-
function normalizeRole(value) {
|
|
61
|
-
return value === 'owner' ? 'owner' : 'member';
|
|
62
|
-
}
|
|
63
|
-
function normalizeMode(value) {
|
|
64
|
-
return value === 'wait' || value === 'steer' ? value : undefined;
|
|
65
|
-
}
|
|
66
79
|
function normalizeBlocks(value) {
|
|
67
80
|
return Array.isArray(value) ? value.filter(isRecord).map((block) => ({ ...block })) : [];
|
|
68
81
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
// ── Boundary schemas ─────────────────────────────────────────────────────────
|
|
83
|
+
//
|
|
84
|
+
// Canonical wire schemas made tolerant of partial rows (older engines and
|
|
85
|
+
// trimmed payloads omit fields); every field that is present is validated
|
|
86
|
+
// against the canonical contract, and enum fields degrade to the relay
|
|
87
|
+
// fallback instead of failing the whole payload.
|
|
88
|
+
const WireAgentChannelSchema = AgentSchema.shape.channels
|
|
89
|
+
.unwrap()
|
|
90
|
+
.element.partial()
|
|
91
|
+
.extend({ role: ChannelMemberInfoSchema.shape.role.optional().catch(undefined) });
|
|
92
|
+
const WireAgentSchema = AgentSchema.omit({ channels: true })
|
|
93
|
+
.partial()
|
|
94
|
+
.extend({
|
|
95
|
+
type: AgentTypeSchema.optional().catch(undefined),
|
|
96
|
+
status: AgentStatusSchema.optional().catch(undefined),
|
|
97
|
+
metadata: AgentSchema.shape.metadata.nullable().optional(),
|
|
98
|
+
channels: z.array(z.unknown()).optional(),
|
|
99
|
+
});
|
|
100
|
+
const WireRegistrationSchema = CreateAgentResponseSchema.partial().extend({
|
|
101
|
+
status: AgentStatusSchema.optional().catch(undefined),
|
|
102
|
+
});
|
|
103
|
+
const WirePresenceSchema = AgentPresenceInfoSchema.partial().extend({
|
|
104
|
+
status: AgentPresenceInfoSchema.shape.status.optional().catch(undefined),
|
|
105
|
+
});
|
|
106
|
+
const WireChannelMemberSchema = ChannelMemberInfoSchema.partial().extend({
|
|
107
|
+
role: ChannelMemberInfoSchema.shape.role.optional().catch(undefined),
|
|
108
|
+
});
|
|
109
|
+
const WireChannelSchema = ChannelSchema.omit({ members: true })
|
|
110
|
+
.partial()
|
|
111
|
+
.extend({
|
|
112
|
+
metadata: ChannelSchema.shape.metadata.unwrap().nullable().optional(),
|
|
113
|
+
members: z.array(z.unknown()).optional(),
|
|
114
|
+
});
|
|
115
|
+
// Accepts the union of canonical message rows: `MessageWithMeta` (channel
|
|
116
|
+
// listings), `Message` (raw rows, where the text is `body`), the core message
|
|
117
|
+
// payload carried by WebSocket events, and the nullable-sender message
|
|
118
|
+
// embedded in delivery ledger rows.
|
|
119
|
+
const WireMessageSchema = MessageWithMetaSchema.omit({
|
|
120
|
+
agent_id: true,
|
|
121
|
+
agent_name: true,
|
|
122
|
+
attachments: true,
|
|
123
|
+
blocks: true,
|
|
124
|
+
has_attachments: true,
|
|
125
|
+
injection_mode: true,
|
|
126
|
+
reactions: true,
|
|
127
|
+
})
|
|
128
|
+
.partial()
|
|
129
|
+
.extend({
|
|
130
|
+
agent_id: DeliveryMessageSchema.shape.agent_id.optional(),
|
|
131
|
+
agent_name: DeliveryMessageSchema.shape.agent_name.optional(),
|
|
132
|
+
body: MessageSchema.shape.body.optional(),
|
|
133
|
+
updated_at: MessageSchema.shape.updated_at.optional(),
|
|
134
|
+
channel_name: z.string().optional(),
|
|
135
|
+
conversation_id: z.string().optional(),
|
|
136
|
+
parent_id: z.string().optional(),
|
|
137
|
+
mode: MessageInjectionModeSchema.optional().catch(undefined),
|
|
138
|
+
injection_mode: MessageInjectionModeSchema.optional().catch(undefined),
|
|
139
|
+
metadata: MessageWithMetaSchema.shape.metadata.unwrap().nullable().optional(),
|
|
140
|
+
// Legacy alias for `metadata` on older message rows.
|
|
141
|
+
data: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
142
|
+
attachments: z.array(z.unknown()).optional(),
|
|
143
|
+
blocks: z.unknown().optional(),
|
|
144
|
+
reactions: z.array(z.unknown()).optional(),
|
|
145
|
+
});
|
|
146
|
+
const WireInboxChannelSchema = InboxResponseSchema.shape.unread_channels.element.partial();
|
|
147
|
+
const WireInboxReactionSchema = InboxResponseSchema.shape.recent_reactions.element.partial();
|
|
148
|
+
const WireInboxDmSchema = InboxResponseSchema.shape.unread_dms.element
|
|
149
|
+
.omit({ last_message: true })
|
|
150
|
+
.partial()
|
|
151
|
+
.extend({ last_message: z.unknown().optional() });
|
|
152
|
+
const WireInboxLastMessageSchema = InboxResponseSchema.shape.unread_dms.element.shape.last_message
|
|
153
|
+
.unwrap()
|
|
154
|
+
.partial();
|
|
155
|
+
const WireInboxSchema = z.object({
|
|
156
|
+
unread_channels: z.array(WireInboxChannelSchema).optional(),
|
|
157
|
+
mentions: z.array(z.unknown()).optional(),
|
|
158
|
+
unread_dms: z.array(z.unknown()).optional(),
|
|
159
|
+
recent_reactions: z.array(z.unknown()).optional(),
|
|
160
|
+
});
|
|
161
|
+
const WireReadReceiptSchema = ReadReceiptSchema.extend(ReaderInfoSchema.shape).partial();
|
|
162
|
+
const WireChannelReadStatusSchema = ChannelReadStatusSchema.partial();
|
|
163
|
+
const WireSearchResultSchema = SearchMessageResultSchema.partial();
|
|
164
|
+
// Delivery statuses reported by the legacy `api.relaycast.dev` engine
|
|
165
|
+
// (the `@relaycast/types` 3.x lifecycle names not carried into 6.x).
|
|
166
|
+
const LegacyDeliveryStatusSchema = z.enum(['accepted', 'deferred']);
|
|
167
|
+
// A delivery ledger row, optionally carrying its embedded message payload
|
|
168
|
+
// (`DeliveryItem`) — transitions return the bare row.
|
|
169
|
+
const WireDeliverySchema = DeliverySchema.partial().extend({
|
|
170
|
+
status: z.union([DeliveryStatusSchema, LegacyDeliveryStatusSchema]).optional().catch(undefined),
|
|
171
|
+
message: DeliveryMessageSchema.partial().nullable().optional(),
|
|
172
|
+
});
|
|
173
|
+
const WireGroupDmSchema = CreateGroupDmResponseSchema.omit({ dm_type: true, participants: true })
|
|
174
|
+
.partial()
|
|
175
|
+
.extend({
|
|
176
|
+
conversation_id: z.string().optional(),
|
|
177
|
+
participants: z.array(z.unknown()).optional(),
|
|
178
|
+
});
|
|
179
|
+
// ── Normalizers ──────────────────────────────────────────────────────────────
|
|
72
180
|
export function normalizeAgentChannel(input) {
|
|
73
|
-
const
|
|
74
|
-
const name = normalizeOptionalChannelName(
|
|
75
|
-
return {
|
|
76
|
-
id:
|
|
181
|
+
const channel = parseWire(WireAgentChannelSchema, input);
|
|
182
|
+
const name = normalizeOptionalChannelName(channel.name) ?? '';
|
|
183
|
+
return compact({
|
|
184
|
+
id: channel.id ?? name,
|
|
77
185
|
name,
|
|
78
|
-
role:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
: {}),
|
|
82
|
-
};
|
|
186
|
+
role: channel.role ?? 'member',
|
|
187
|
+
joinedAt: opt(channel.joined_at),
|
|
188
|
+
});
|
|
83
189
|
}
|
|
84
190
|
export function normalizeAgent(input) {
|
|
85
|
-
const
|
|
86
|
-
const id =
|
|
87
|
-
|
|
88
|
-
'';
|
|
89
|
-
const name = readString(record, 'name', 'agentName', 'agent_name') ?? id;
|
|
90
|
-
const lastSeenAt = readString(record, 'lastSeenAt', 'last_seen', 'lastSeen');
|
|
91
|
-
const createdAt = readString(record, 'createdAt', 'created_at');
|
|
92
|
-
const persona = readNullableString(record, 'persona');
|
|
93
|
-
return {
|
|
191
|
+
const agent = parseWire(WireAgentSchema, input);
|
|
192
|
+
const id = agent.id ?? agent.name ?? '';
|
|
193
|
+
return compact({
|
|
94
194
|
id,
|
|
95
|
-
name,
|
|
96
|
-
type:
|
|
97
|
-
status:
|
|
98
|
-
|
|
99
|
-
metadata:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
channels:
|
|
103
|
-
};
|
|
195
|
+
name: agent.name ?? id,
|
|
196
|
+
type: agent.type ?? 'agent',
|
|
197
|
+
status: agent.status ?? 'unknown',
|
|
198
|
+
persona: opt(agent.persona),
|
|
199
|
+
metadata: agent.metadata ?? {},
|
|
200
|
+
lastSeenAt: opt(agent.last_seen),
|
|
201
|
+
createdAt: opt(agent.created_at),
|
|
202
|
+
channels: (agent.channels ?? []).map(normalizeAgentChannel),
|
|
203
|
+
});
|
|
104
204
|
}
|
|
105
205
|
export function normalizeAgentRegistration(input) {
|
|
106
|
-
const
|
|
107
|
-
const id =
|
|
108
|
-
|
|
109
|
-
const createdAt = readString(record, 'createdAt', 'created_at');
|
|
110
|
-
return {
|
|
206
|
+
const registration = parseWire(WireRegistrationSchema, input);
|
|
207
|
+
const id = registration.id ?? registration.name ?? '';
|
|
208
|
+
return compact({
|
|
111
209
|
id,
|
|
112
|
-
name,
|
|
113
|
-
token:
|
|
114
|
-
status:
|
|
115
|
-
|
|
116
|
-
};
|
|
210
|
+
name: registration.name ?? id,
|
|
211
|
+
token: registration.token ?? '',
|
|
212
|
+
status: registration.status ?? 'unknown',
|
|
213
|
+
createdAt: opt(registration.created_at),
|
|
214
|
+
});
|
|
117
215
|
}
|
|
118
216
|
export function normalizeAgentPresence(input) {
|
|
119
|
-
const
|
|
120
|
-
const agentId =
|
|
121
|
-
const agentName = readString(record, 'agentName', 'agent_name', 'name') ?? agentId;
|
|
217
|
+
const presence = parseWire(WirePresenceSchema, input);
|
|
218
|
+
const agentId = presence.agent_id ?? '';
|
|
122
219
|
return {
|
|
123
220
|
agentId,
|
|
124
|
-
agentName,
|
|
125
|
-
status:
|
|
221
|
+
agentName: presence.agent_name ?? agentId,
|
|
222
|
+
status: presence.status === 'online' ? 'online' : 'offline',
|
|
126
223
|
};
|
|
127
224
|
}
|
|
128
225
|
export function normalizeChannelMember(input) {
|
|
129
|
-
const
|
|
130
|
-
const agentId =
|
|
131
|
-
|
|
132
|
-
return {
|
|
226
|
+
const member = parseWire(WireChannelMemberSchema, input);
|
|
227
|
+
const agentId = member.agent_id ?? '';
|
|
228
|
+
return compact({
|
|
133
229
|
agentId,
|
|
134
|
-
agentName,
|
|
135
|
-
role:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
muted: readBoolean(record, 'muted', 'isMuted', 'is_muted') ?? false,
|
|
140
|
-
};
|
|
230
|
+
agentName: member.agent_name ?? agentId,
|
|
231
|
+
role: member.role ?? 'member',
|
|
232
|
+
joinedAt: opt(member.joined_at),
|
|
233
|
+
muted: member.is_muted ?? false,
|
|
234
|
+
});
|
|
141
235
|
}
|
|
142
236
|
export function normalizeChannel(input) {
|
|
143
|
-
const
|
|
144
|
-
const name = normalizeOptionalChannelName(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const createdAt = readString(record, 'createdAt', 'created_at');
|
|
148
|
-
const memberCount = readNumber(record, 'memberCount', 'member_count');
|
|
149
|
-
return {
|
|
150
|
-
id: readString(record, 'id', 'channelId', 'channel_id') ?? name,
|
|
237
|
+
const channel = parseWire(WireChannelSchema, input);
|
|
238
|
+
const name = normalizeOptionalChannelName(channel.name) ?? '';
|
|
239
|
+
return compact({
|
|
240
|
+
id: channel.id ?? name,
|
|
151
241
|
name,
|
|
152
|
-
|
|
153
|
-
metadata:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
archived:
|
|
157
|
-
|
|
158
|
-
members:
|
|
159
|
-
};
|
|
242
|
+
topic: opt(channel.topic),
|
|
243
|
+
metadata: channel.metadata ?? {},
|
|
244
|
+
createdBy: opt(channel.created_by),
|
|
245
|
+
createdAt: opt(channel.created_at),
|
|
246
|
+
archived: channel.is_archived ?? false,
|
|
247
|
+
memberCount: channel.member_count,
|
|
248
|
+
members: (channel.members ?? []).map(normalizeChannelMember),
|
|
249
|
+
});
|
|
160
250
|
}
|
|
161
251
|
export function normalizeAttachment(input) {
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
type,
|
|
209
|
-
patch: readString(record, 'patch', 'diff') ?? '',
|
|
210
|
-
...(readString(record, 'label') ? { label: readString(record, 'label') } : {}),
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
if (type === 'artifact') {
|
|
214
|
-
return {
|
|
215
|
-
type,
|
|
216
|
-
id: readString(record, 'id', 'artifactId', 'artifact_id') ?? '',
|
|
217
|
-
...(readString(record, 'url') ? { url: readString(record, 'url') } : {}),
|
|
218
|
-
...(readString(record, 'label') ? { label: readString(record, 'label') } : {}),
|
|
219
|
-
};
|
|
252
|
+
const wired = toWire(input);
|
|
253
|
+
const record = isRecord(wired) ? wired : {};
|
|
254
|
+
const type = str(record, 'type');
|
|
255
|
+
const label = opt(str(record, 'label'));
|
|
256
|
+
switch (type) {
|
|
257
|
+
case 'text':
|
|
258
|
+
return compact({ type, text: str(record, 'text') ?? str(record, 'content') ?? '', label });
|
|
259
|
+
case 'image':
|
|
260
|
+
return compact({
|
|
261
|
+
type,
|
|
262
|
+
url: opt(str(record, 'url')),
|
|
263
|
+
data: opt(str(record, 'data')),
|
|
264
|
+
mimeType: opt(str(record, 'mime_type')),
|
|
265
|
+
alt: opt(str(record, 'alt')),
|
|
266
|
+
label,
|
|
267
|
+
});
|
|
268
|
+
case 'link':
|
|
269
|
+
return compact({ type, url: str(record, 'url') ?? '', title: opt(str(record, 'title')), label });
|
|
270
|
+
case 'file':
|
|
271
|
+
return compact({
|
|
272
|
+
type,
|
|
273
|
+
path: str(record, 'path') ?? str(record, 'filename') ?? '',
|
|
274
|
+
line: num(record, 'line'),
|
|
275
|
+
label,
|
|
276
|
+
});
|
|
277
|
+
case 'json':
|
|
278
|
+
return compact({ type, value: record.value, label });
|
|
279
|
+
case 'diff':
|
|
280
|
+
return compact({ type, patch: str(record, 'patch') ?? str(record, 'diff') ?? '', label });
|
|
281
|
+
case 'artifact':
|
|
282
|
+
return compact({
|
|
283
|
+
type,
|
|
284
|
+
id: str(record, 'id') ?? str(record, 'artifact_id') ?? '',
|
|
285
|
+
url: opt(str(record, 'url')),
|
|
286
|
+
label,
|
|
287
|
+
});
|
|
288
|
+
default: {
|
|
289
|
+
// Canonical stored file attachment (`FileAttachment`).
|
|
290
|
+
const filename = str(record, 'filename');
|
|
291
|
+
return compact({
|
|
292
|
+
id: str(record, 'id') ?? str(record, 'file_id') ?? filename ?? '',
|
|
293
|
+
filename: opt(filename),
|
|
294
|
+
contentType: opt(str(record, 'content_type')),
|
|
295
|
+
sizeBytes: num(record, 'size_bytes'),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
220
298
|
}
|
|
221
|
-
const filename = readString(record, 'filename', 'name');
|
|
222
|
-
const contentType = readString(record, 'contentType', 'content_type');
|
|
223
|
-
const sizeBytes = readNumber(record, 'sizeBytes', 'size_bytes');
|
|
224
|
-
return {
|
|
225
|
-
id: readString(record, 'id', 'fileId', 'file_id') ?? filename ?? '',
|
|
226
|
-
...(filename ? { filename } : {}),
|
|
227
|
-
...(contentType ? { contentType } : {}),
|
|
228
|
-
...(sizeBytes !== undefined ? { sizeBytes } : {}),
|
|
229
|
-
};
|
|
230
299
|
}
|
|
231
300
|
export function normalizeReaction(input) {
|
|
232
|
-
const
|
|
301
|
+
const reaction = parseWire(ReactionGroupSchema.partial(), input);
|
|
233
302
|
return {
|
|
234
|
-
emoji:
|
|
235
|
-
count:
|
|
236
|
-
agents:
|
|
303
|
+
emoji: reaction.emoji ?? '',
|
|
304
|
+
count: reaction.count ?? 0,
|
|
305
|
+
agents: reaction.agents ?? [],
|
|
237
306
|
};
|
|
238
307
|
}
|
|
239
308
|
export function normalizeMessage(input, context = {}) {
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
normalizeOptionalChannelName(readString(record, 'channelName', 'channel_name', 'channel'));
|
|
246
|
-
const channelId = context.channelId ?? readString(record, 'channelId', 'channel_id');
|
|
247
|
-
const conversationId = context.conversationId ?? readString(record, 'conversationId', 'conversation_id');
|
|
248
|
-
const parentId = context.parentId ?? readString(record, 'parentId', 'parent_id');
|
|
249
|
-
const threadId = context.threadId ?? readNullableString(record, 'threadId', 'thread_id');
|
|
250
|
-
const createdAt = readString(record, 'createdAt', 'created_at') ?? context.createdAt;
|
|
251
|
-
const updatedAt = readNullableString(record, 'updatedAt', 'updated_at');
|
|
252
|
-
const metadata = readRecord(record, 'metadata') ?? readRecord(record, 'data');
|
|
253
|
-
const replyCount = readNumber(record, 'replyCount', 'reply_count');
|
|
254
|
-
const readByCount = readNumber(record, 'readByCount', 'read_by_count');
|
|
309
|
+
const message = parseWire(WireMessageSchema, input);
|
|
310
|
+
const channelName = context.channelName ?? normalizeOptionalChannelName(message.channel_name);
|
|
311
|
+
const channelId = context.channelId ?? message.channel_id;
|
|
312
|
+
const conversationId = context.conversationId ?? message.conversation_id;
|
|
313
|
+
const parentId = context.parentId ?? message.parent_id;
|
|
255
314
|
const kind = context.kind ??
|
|
256
315
|
(parentId ? 'thread_reply' : conversationId ? 'dm' : channelId || channelName ? 'channel' : 'unknown');
|
|
257
|
-
const id =
|
|
258
|
-
return {
|
|
316
|
+
const id = message.id ?? '';
|
|
317
|
+
return compact({
|
|
259
318
|
id,
|
|
260
319
|
messageId: id,
|
|
261
320
|
kind,
|
|
262
|
-
text:
|
|
263
|
-
from: {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
channel: {
|
|
282
|
-
...(channelId ? { id: channelId } : {}),
|
|
283
|
-
...(channelName ? { name: channelName } : {}),
|
|
284
|
-
},
|
|
285
|
-
}
|
|
286
|
-
: {}),
|
|
287
|
-
...(conversationId ? { conversationId } : {}),
|
|
288
|
-
...(threadId ? { threadId } : {}),
|
|
289
|
-
...(parentId ? { parentId } : {}),
|
|
290
|
-
...(normalizeMode(readString(record, 'mode', 'injectionMode', 'injection_mode'))
|
|
291
|
-
? {
|
|
292
|
-
mode: normalizeMode(readString(record, 'mode', 'injectionMode', 'injection_mode')),
|
|
293
|
-
}
|
|
294
|
-
: {}),
|
|
295
|
-
...(createdAt ? { createdAt } : {}),
|
|
296
|
-
...(updatedAt ? { updatedAt } : {}),
|
|
297
|
-
...(metadata ? { metadata } : {}),
|
|
298
|
-
blocks: normalizeBlocks(readValue(record, 'blocks')),
|
|
299
|
-
attachments: readArray(record, 'attachments').map(normalizeAttachment),
|
|
300
|
-
...(replyCount !== undefined ? { replyCount } : {}),
|
|
301
|
-
reactions: readArray(record, 'reactions').map(normalizeReaction),
|
|
302
|
-
...(readByCount !== undefined ? { readByCount } : {}),
|
|
303
|
-
mentions: normalizeStringArray(readValue(record, 'mentions')),
|
|
304
|
-
};
|
|
321
|
+
text: message.text ?? message.body ?? '',
|
|
322
|
+
from: compact({ id: opt(message.agent_id), name: opt(message.agent_name) }),
|
|
323
|
+
channel: channelId || channelName
|
|
324
|
+
? compact({ id: opt(channelId), name: opt(channelName) })
|
|
325
|
+
: undefined,
|
|
326
|
+
conversationId: opt(conversationId),
|
|
327
|
+
threadId: opt(context.threadId ?? message.thread_id),
|
|
328
|
+
parentId: opt(parentId),
|
|
329
|
+
mode: message.mode ?? message.injection_mode,
|
|
330
|
+
createdAt: opt(message.created_at ?? context.createdAt),
|
|
331
|
+
updatedAt: opt(message.updated_at),
|
|
332
|
+
metadata: message.metadata ?? message.data ?? undefined,
|
|
333
|
+
blocks: normalizeBlocks(message.blocks),
|
|
334
|
+
attachments: (message.attachments ?? []).map(normalizeAttachment),
|
|
335
|
+
replyCount: message.reply_count,
|
|
336
|
+
reactions: (message.reactions ?? []).map(normalizeReaction),
|
|
337
|
+
readByCount: message.read_by_count,
|
|
338
|
+
mentions: message.mentions ?? [],
|
|
339
|
+
});
|
|
305
340
|
}
|
|
306
341
|
export function normalizeThread(input) {
|
|
307
342
|
const record = isRecord(input) ? input : {};
|
|
308
|
-
const parent = normalizeMessage(
|
|
309
|
-
const replies =
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
343
|
+
const parent = normalizeMessage(record.parent, { kind: 'channel' });
|
|
344
|
+
const replies = Array.isArray(record.replies) ? record.replies : [];
|
|
345
|
+
return {
|
|
346
|
+
parent,
|
|
347
|
+
replies: replies.map((reply) => normalizeMessage(reply, {
|
|
348
|
+
kind: 'thread_reply',
|
|
349
|
+
channelId: parent.channel?.id,
|
|
350
|
+
channelName: parent.channel?.name,
|
|
351
|
+
threadId: parent.threadId ?? parent.id,
|
|
352
|
+
parentId: parent.id,
|
|
353
|
+
})),
|
|
354
|
+
};
|
|
317
355
|
}
|
|
318
356
|
function normalizeInboxLastMessage(input) {
|
|
319
|
-
|
|
320
|
-
if (!record)
|
|
357
|
+
if (!isRecord(input))
|
|
321
358
|
return undefined;
|
|
322
|
-
const
|
|
323
|
-
return {
|
|
324
|
-
id:
|
|
325
|
-
text:
|
|
326
|
-
|
|
327
|
-
};
|
|
359
|
+
const last = parseWire(WireInboxLastMessageSchema, input);
|
|
360
|
+
return compact({
|
|
361
|
+
id: last.id ?? '',
|
|
362
|
+
text: last.text ?? '',
|
|
363
|
+
createdAt: opt(last.created_at),
|
|
364
|
+
});
|
|
328
365
|
}
|
|
329
366
|
function normalizeInboxDirect(input) {
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
};
|
|
367
|
+
const dm = parseWire(WireInboxDmSchema, input);
|
|
368
|
+
return compact({
|
|
369
|
+
conversationId: dm.conversation_id ?? '',
|
|
370
|
+
from: dm.from ?? '',
|
|
371
|
+
unreadCount: dm.unread_count ?? 0,
|
|
372
|
+
lastMessage: normalizeInboxLastMessage(dm.last_message),
|
|
373
|
+
});
|
|
338
374
|
}
|
|
339
375
|
function normalizeInboxReaction(input) {
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
};
|
|
376
|
+
const reaction = parseWire(WireInboxReactionSchema, input);
|
|
377
|
+
return compact({
|
|
378
|
+
messageId: reaction.message_id ?? '',
|
|
379
|
+
channelName: normalizeOptionalChannelName(reaction.channel_name) ?? '',
|
|
380
|
+
emoji: reaction.emoji ?? '',
|
|
381
|
+
agentName: reaction.agent_name ?? '',
|
|
382
|
+
createdAt: opt(reaction.created_at),
|
|
383
|
+
});
|
|
349
384
|
}
|
|
350
385
|
export function normalizeInbox(input) {
|
|
351
|
-
const
|
|
386
|
+
const inbox = parseWire(WireInboxSchema, input);
|
|
352
387
|
return {
|
|
353
|
-
unreadChannels:
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}),
|
|
361
|
-
mentions: readArray(record, 'mentions').map((item) => {
|
|
362
|
-
const itemRecord = isRecord(item) ? item : {};
|
|
363
|
-
return normalizeMessage(itemRecord, {
|
|
364
|
-
kind: 'channel',
|
|
365
|
-
channelName: normalizeOptionalChannelName(readString(itemRecord, 'channelName', 'channel_name', 'channel')),
|
|
366
|
-
});
|
|
367
|
-
}),
|
|
368
|
-
unreadDms: readArray(record, 'unreadDms', 'unread_dms').map(normalizeInboxDirect),
|
|
369
|
-
recentReactions: readArray(record, 'recentReactions', 'recent_reactions').map(normalizeInboxReaction),
|
|
388
|
+
unreadChannels: (inbox.unread_channels ?? []).map((channel) => ({
|
|
389
|
+
channelName: normalizeOptionalChannelName(channel.channel_name) ?? '',
|
|
390
|
+
unreadCount: channel.unread_count ?? 0,
|
|
391
|
+
})),
|
|
392
|
+
mentions: (inbox.mentions ?? []).map((mention) => normalizeMessage(mention, { kind: 'channel' })),
|
|
393
|
+
unreadDms: (inbox.unread_dms ?? []).map(normalizeInboxDirect),
|
|
394
|
+
recentReactions: (inbox.recent_reactions ?? []).map(normalizeInboxReaction),
|
|
370
395
|
};
|
|
371
396
|
}
|
|
372
397
|
export function normalizeReadReceipt(input) {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
...(readString(record, 'agentName', 'agent_name')
|
|
381
|
-
? { agentName: readString(record, 'agentName', 'agent_name') }
|
|
382
|
-
: {}),
|
|
383
|
-
...(readAt ? { readAt } : {}),
|
|
384
|
-
};
|
|
398
|
+
const receipt = parseWire(WireReadReceiptSchema, input);
|
|
399
|
+
return compact({
|
|
400
|
+
messageId: receipt.message_id ?? '',
|
|
401
|
+
agentId: opt(receipt.agent_id),
|
|
402
|
+
agentName: opt(receipt.agent_name),
|
|
403
|
+
readAt: opt(receipt.read_at),
|
|
404
|
+
});
|
|
385
405
|
}
|
|
386
406
|
export function normalizeChannelReadStatus(input) {
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
//
|
|
397
|
-
// `
|
|
407
|
+
const status = parseWire(WireChannelReadStatusSchema, input);
|
|
408
|
+
return compact({
|
|
409
|
+
agentName: status.agent_name ?? '',
|
|
410
|
+
lastReadId: opt(status.last_read_id),
|
|
411
|
+
lastReadAt: opt(status.last_read_at),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
// Canonical durable delivery statuses mapped onto relay inbox states:
|
|
415
|
+
// the terminal `acked` surfaces as `read` and `dead_lettered` as `failed`;
|
|
416
|
+
// a deferred 6.x row stays `queued` with a future `available_at`. Typed
|
|
417
|
+
// against `DeliveryStatus` so a new canonical status fails to compile here
|
|
418
|
+
// until it is mapped. Legacy `api.relaycast.dev` statuses map alongside.
|
|
398
419
|
const INBOX_STATE_BY_DELIVERY_STATUS = {
|
|
399
|
-
|
|
420
|
+
queued: 'queued',
|
|
400
421
|
delivered: 'delivered',
|
|
401
|
-
|
|
422
|
+
acked: 'read',
|
|
402
423
|
failed: 'failed',
|
|
424
|
+
dead_lettered: 'failed',
|
|
425
|
+
// Legacy 3.x lifecycle names.
|
|
426
|
+
accepted: 'queued',
|
|
427
|
+
deferred: 'deferred',
|
|
403
428
|
};
|
|
404
429
|
export function normalizeInboxItemState(value) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (reason)
|
|
418
|
-
metadata.reason = reason;
|
|
419
|
-
if (priority)
|
|
420
|
-
metadata.priority = priority;
|
|
421
|
-
if (retryable !== undefined)
|
|
422
|
-
metadata.retryable = retryable;
|
|
423
|
-
if (error)
|
|
424
|
-
metadata.error = error;
|
|
425
|
-
if (deadline)
|
|
426
|
-
metadata.deadline = deadline;
|
|
430
|
+
const status = z.union([DeliveryStatusSchema, LegacyDeliveryStatusSchema]).safeParse(value);
|
|
431
|
+
return status.success ? INBOX_STATE_BY_DELIVERY_STATUS[status.data] : 'queued';
|
|
432
|
+
}
|
|
433
|
+
function normalizeDeliveryMetadata(delivery) {
|
|
434
|
+
const metadata = compact({
|
|
435
|
+
mode: opt(delivery.mode),
|
|
436
|
+
reason: opt(delivery.reason),
|
|
437
|
+
priority: opt(delivery.priority),
|
|
438
|
+
retryable: delivery.retryable ?? undefined,
|
|
439
|
+
error: opt(delivery.error),
|
|
440
|
+
deadline: opt(delivery.deadline),
|
|
441
|
+
});
|
|
427
442
|
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
428
443
|
}
|
|
429
444
|
/**
|
|
@@ -431,107 +446,92 @@ function normalizeDeliveryMetadata(record) {
|
|
|
431
446
|
* its embedded message payload) into a relay `InboxItem`.
|
|
432
447
|
*/
|
|
433
448
|
export function normalizeInboxItem(input, context = {}) {
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
id: readString(record, 'id', 'deliveryId', 'delivery_id') ?? '',
|
|
443
|
-
recipient: {
|
|
444
|
-
name: context.recipientName ?? agentId ?? '',
|
|
445
|
-
...(agentId ? { id: agentId } : {}),
|
|
446
|
-
},
|
|
447
|
-
state: normalizeInboxItemState(readString(record, 'status')),
|
|
449
|
+
const delivery = parseWire(WireDeliverySchema, input);
|
|
450
|
+
return compact({
|
|
451
|
+
id: delivery.id ?? '',
|
|
452
|
+
recipient: compact({
|
|
453
|
+
name: context.recipientName ?? delivery.agent_id ?? '',
|
|
454
|
+
id: opt(delivery.agent_id),
|
|
455
|
+
}),
|
|
456
|
+
state: delivery.status ? INBOX_STATE_BY_DELIVERY_STATUS[delivery.status] : 'queued',
|
|
448
457
|
// The relaycast ledger does not expose attempt counts.
|
|
449
458
|
attempts: 0,
|
|
450
|
-
|
|
451
|
-
message: normalizeMessage(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
...(metadata ? { metadata } : {}),
|
|
456
|
-
};
|
|
459
|
+
availableAt: opt(delivery.available_at),
|
|
460
|
+
message: normalizeMessage(delivery.message ??
|
|
461
|
+
compact({ id: opt(delivery.message_id), channel_id: opt(delivery.channel_id) })),
|
|
462
|
+
metadata: normalizeDeliveryMetadata(delivery),
|
|
463
|
+
});
|
|
457
464
|
}
|
|
458
465
|
/**
|
|
459
466
|
* Normalize the delivery row returned by a relaycast ack/fail/defer transition
|
|
460
467
|
* into the relay delivery result contract.
|
|
461
468
|
*/
|
|
462
469
|
export function normalizeDeliveryTransition(action, input) {
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
return {
|
|
470
|
+
const delivery = parseWire(WireDeliverySchema, input);
|
|
471
|
+
return compact({
|
|
466
472
|
supported: true,
|
|
467
473
|
action,
|
|
468
|
-
deliveryId:
|
|
469
|
-
messageId:
|
|
470
|
-
state:
|
|
471
|
-
|
|
472
|
-
};
|
|
474
|
+
deliveryId: delivery.id ?? '',
|
|
475
|
+
messageId: delivery.message_id ?? '',
|
|
476
|
+
state: delivery.status ? INBOX_STATE_BY_DELIVERY_STATUS[delivery.status] : 'queued',
|
|
477
|
+
deferUntil: action === 'defer' ? opt(delivery.available_at) : undefined,
|
|
478
|
+
});
|
|
473
479
|
}
|
|
474
480
|
export function normalizeSearchResult(input) {
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
};
|
|
481
|
+
const result = parseWire(WireSearchResultSchema, input);
|
|
482
|
+
return compact({
|
|
483
|
+
id: result.id ?? '',
|
|
484
|
+
channelName: normalizeOptionalChannelName(result.channel_name) ?? '',
|
|
485
|
+
agentName: result.agent_name ?? '',
|
|
486
|
+
text: result.text ?? '',
|
|
487
|
+
createdAt: opt(result.created_at),
|
|
488
|
+
relevanceScore: result.relevance_score ?? 0,
|
|
489
|
+
});
|
|
485
490
|
}
|
|
486
491
|
export function normalizeGroupDirectConversation(input) {
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const participantRecord = isRecord(participant) ? participant : undefined;
|
|
499
|
-
return typeof participant === 'string'
|
|
500
|
-
? participant
|
|
501
|
-
: readString(participantRecord, 'agentName', 'agent_name', 'agentId', 'agent_id');
|
|
502
|
-
})
|
|
492
|
+
const conversation = parseWire(WireGroupDmSchema, input);
|
|
493
|
+
return compact({
|
|
494
|
+
id: conversation.id ?? conversation.conversation_id ?? '',
|
|
495
|
+
channelId: opt(conversation.channel_id),
|
|
496
|
+
name: opt(conversation.name),
|
|
497
|
+
participants: (conversation.participants ?? [])
|
|
498
|
+
.map((participant) => typeof participant === 'string'
|
|
499
|
+
? participant
|
|
500
|
+
: isRecord(participant)
|
|
501
|
+
? (str(participant, 'agent_name') ?? str(participant, 'agent_id'))
|
|
502
|
+
: undefined)
|
|
503
503
|
.filter((participant) => typeof participant === 'string'),
|
|
504
|
-
|
|
505
|
-
};
|
|
506
|
-
}
|
|
504
|
+
createdAt: opt(conversation.created_at),
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
const MEMBERSHIP_EVENT_TYPES = {
|
|
508
|
+
'member.joined': 'memberJoined',
|
|
509
|
+
'member.left': 'memberLeft',
|
|
510
|
+
'member.channel_muted': 'channelMuted',
|
|
511
|
+
'member.channel_unmuted': 'channelUnmuted',
|
|
512
|
+
};
|
|
507
513
|
export function normalizeMessagingEvent(input) {
|
|
508
|
-
const
|
|
509
|
-
const
|
|
514
|
+
const wired = toWire(input);
|
|
515
|
+
const record = isRecord(wired) ? wired : {};
|
|
516
|
+
const sourceType = str(record, 'type');
|
|
510
517
|
switch (sourceType) {
|
|
511
|
-
case 'message.created':
|
|
512
|
-
const channel = normalizeOptionalChannelName(readString(record, 'channel')) ?? '';
|
|
513
|
-
return {
|
|
514
|
-
type: 'messageCreated',
|
|
515
|
-
channel,
|
|
516
|
-
message: normalizeMessage(readValue(record, 'message'), { kind: 'channel', channelName: channel }),
|
|
517
|
-
};
|
|
518
|
-
}
|
|
518
|
+
case 'message.created':
|
|
519
519
|
case 'message.updated': {
|
|
520
|
-
const channel = normalizeOptionalChannelName(
|
|
520
|
+
const channel = normalizeOptionalChannelName(str(record, 'channel')) ?? '';
|
|
521
521
|
return {
|
|
522
|
-
type: 'messageUpdated',
|
|
522
|
+
type: sourceType === 'message.created' ? 'messageCreated' : 'messageUpdated',
|
|
523
523
|
channel,
|
|
524
|
-
message: normalizeMessage(
|
|
524
|
+
message: normalizeMessage(record.message, { kind: 'channel', channelName: channel }),
|
|
525
525
|
};
|
|
526
526
|
}
|
|
527
527
|
case 'thread.reply': {
|
|
528
|
-
const channel = normalizeOptionalChannelName(
|
|
529
|
-
const parentId =
|
|
528
|
+
const channel = normalizeOptionalChannelName(str(record, 'channel')) ?? '';
|
|
529
|
+
const parentId = str(record, 'parent_id') ?? '';
|
|
530
530
|
return {
|
|
531
531
|
type: 'threadReply',
|
|
532
532
|
channel,
|
|
533
533
|
parentId,
|
|
534
|
-
message: normalizeMessage(
|
|
534
|
+
message: normalizeMessage(record.message, {
|
|
535
535
|
kind: 'thread_reply',
|
|
536
536
|
channelName: channel,
|
|
537
537
|
parentId,
|
|
@@ -539,130 +539,100 @@ export function normalizeMessagingEvent(input) {
|
|
|
539
539
|
}),
|
|
540
540
|
};
|
|
541
541
|
}
|
|
542
|
-
case 'dm.received':
|
|
543
|
-
const conversationId = readString(record, 'conversationId', 'conversation_id') ?? '';
|
|
544
|
-
return {
|
|
545
|
-
type: 'dmReceived',
|
|
546
|
-
conversationId,
|
|
547
|
-
message: normalizeMessage(readValue(record, 'message'), { kind: 'dm', conversationId }),
|
|
548
|
-
};
|
|
549
|
-
}
|
|
542
|
+
case 'dm.received':
|
|
550
543
|
case 'group_dm.received': {
|
|
551
|
-
const conversationId =
|
|
544
|
+
const conversationId = str(record, 'conversation_id') ?? '';
|
|
545
|
+
const kind = sourceType === 'dm.received' ? 'dm' : 'group_dm';
|
|
552
546
|
return {
|
|
553
|
-
type: 'groupDmReceived',
|
|
547
|
+
type: kind === 'dm' ? 'dmReceived' : 'groupDmReceived',
|
|
554
548
|
conversationId,
|
|
555
|
-
message: normalizeMessage(
|
|
549
|
+
message: normalizeMessage(record.message, { kind, conversationId }),
|
|
556
550
|
};
|
|
557
551
|
}
|
|
552
|
+
// Legacy presence events emitted by older engines; current engines emit
|
|
553
|
+
// `agent.status.*` (surfaced as `unknown` events).
|
|
558
554
|
case 'agent.online':
|
|
559
|
-
return {
|
|
560
|
-
type: 'agentOnline',
|
|
561
|
-
agent: {
|
|
562
|
-
name: readString(isRecord(readValue(record, 'agent'))
|
|
563
|
-
? readValue(record, 'agent')
|
|
564
|
-
: undefined, 'name') ?? '',
|
|
565
|
-
},
|
|
566
|
-
};
|
|
567
555
|
case 'agent.offline':
|
|
568
556
|
return {
|
|
569
|
-
type: 'agentOffline',
|
|
570
|
-
agent: {
|
|
571
|
-
name: readString(isRecord(readValue(record, 'agent'))
|
|
572
|
-
? readValue(record, 'agent')
|
|
573
|
-
: undefined, 'name') ?? '',
|
|
574
|
-
},
|
|
557
|
+
type: sourceType === 'agent.online' ? 'agentOnline' : 'agentOffline',
|
|
558
|
+
agent: { name: str(rec(record, 'agent'), 'name') ?? '' },
|
|
575
559
|
};
|
|
576
560
|
case 'agent.spawn_requested': {
|
|
577
|
-
const agent =
|
|
561
|
+
const agent = rec(record, 'agent');
|
|
578
562
|
return {
|
|
579
563
|
type: 'agentSpawnRequested',
|
|
580
|
-
agent: {
|
|
581
|
-
name:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
alreadyExisted: readBoolean(agent, 'alreadyExisted', 'already_existed') ?? false,
|
|
589
|
-
},
|
|
564
|
+
agent: compact({
|
|
565
|
+
name: str(agent, 'name') ?? '',
|
|
566
|
+
cli: opt(str(agent, 'cli')),
|
|
567
|
+
task: opt(str(agent, 'task')),
|
|
568
|
+
channel: normalizeOptionalChannelName(str(agent, 'channel')),
|
|
569
|
+
model: opt(str(agent, 'model')),
|
|
570
|
+
alreadyExisted: bool(agent, 'already_existed') ?? false,
|
|
571
|
+
}),
|
|
590
572
|
};
|
|
591
573
|
}
|
|
592
|
-
case 'agent.release_requested':
|
|
593
|
-
|
|
594
|
-
const reason = readNullableString(record, 'reason');
|
|
595
|
-
return {
|
|
574
|
+
case 'agent.release_requested':
|
|
575
|
+
return compact({
|
|
596
576
|
type: 'agentReleaseRequested',
|
|
597
|
-
agent: { name:
|
|
598
|
-
|
|
599
|
-
deleted:
|
|
600
|
-
};
|
|
601
|
-
}
|
|
577
|
+
agent: { name: str(rec(record, 'agent'), 'name') ?? '' },
|
|
578
|
+
reason: opt(str(record, 'reason')),
|
|
579
|
+
deleted: bool(record, 'deleted') ?? false,
|
|
580
|
+
});
|
|
602
581
|
case 'channel.created':
|
|
603
582
|
case 'channel.updated': {
|
|
604
|
-
const channel =
|
|
605
|
-
? readValue(record, 'channel')
|
|
606
|
-
: {};
|
|
607
|
-
const topic = readNullableString(channel, 'topic');
|
|
583
|
+
const channel = rec(record, 'channel');
|
|
608
584
|
return {
|
|
609
585
|
type: sourceType === 'channel.created' ? 'channelCreated' : 'channelUpdated',
|
|
610
|
-
channel: {
|
|
611
|
-
name: normalizeOptionalChannelName(
|
|
612
|
-
|
|
613
|
-
},
|
|
586
|
+
channel: compact({
|
|
587
|
+
name: normalizeOptionalChannelName(str(channel, 'name')) ?? '',
|
|
588
|
+
topic: opt(str(channel, 'topic')),
|
|
589
|
+
}),
|
|
614
590
|
};
|
|
615
591
|
}
|
|
616
|
-
case 'channel.archived':
|
|
617
|
-
const channel = isRecord(readValue(record, 'channel'))
|
|
618
|
-
? readValue(record, 'channel')
|
|
619
|
-
: {};
|
|
592
|
+
case 'channel.archived':
|
|
620
593
|
return {
|
|
621
594
|
type: 'channelArchived',
|
|
622
|
-
channel: { name: normalizeOptionalChannelName(
|
|
595
|
+
channel: { name: normalizeOptionalChannelName(str(rec(record, 'channel'), 'name')) ?? '' },
|
|
623
596
|
};
|
|
624
|
-
}
|
|
625
597
|
case 'member.joined':
|
|
626
598
|
case 'member.left':
|
|
627
599
|
case 'member.channel_muted':
|
|
628
|
-
case 'member.channel_unmuted':
|
|
629
|
-
const type = sourceType === 'member.joined'
|
|
630
|
-
? 'memberJoined'
|
|
631
|
-
: sourceType === 'member.left'
|
|
632
|
-
? 'memberLeft'
|
|
633
|
-
: sourceType === 'member.channel_muted'
|
|
634
|
-
? 'channelMuted'
|
|
635
|
-
: 'channelUnmuted';
|
|
600
|
+
case 'member.channel_unmuted':
|
|
636
601
|
return {
|
|
637
|
-
type,
|
|
638
|
-
channel: normalizeOptionalChannelName(
|
|
639
|
-
agentName:
|
|
602
|
+
type: MEMBERSHIP_EVENT_TYPES[sourceType],
|
|
603
|
+
channel: normalizeOptionalChannelName(str(record, 'channel')) ?? '',
|
|
604
|
+
agentName: str(record, 'agent_name') ?? '',
|
|
640
605
|
};
|
|
641
|
-
}
|
|
642
606
|
case 'message.read':
|
|
643
|
-
return {
|
|
607
|
+
return compact({
|
|
644
608
|
type: 'messageRead',
|
|
645
|
-
messageId:
|
|
646
|
-
agentName:
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
609
|
+
messageId: str(record, 'message_id') ?? '',
|
|
610
|
+
agentName: str(record, 'agent_name') ?? '',
|
|
611
|
+
readAt: opt(str(record, 'read_at')),
|
|
612
|
+
});
|
|
613
|
+
// Canonical reaction event plus the legacy split pair. The engine's raw
|
|
614
|
+
// workspace-stream frames and the durable event log (observer mode) carry
|
|
615
|
+
// reactions as a single `message.reacted` type with an `action` field;
|
|
616
|
+
// higher-level clients split it into `reaction.added`/`reaction.removed`
|
|
617
|
+
// before we see it. All three land here.
|
|
618
|
+
case 'message.reacted':
|
|
651
619
|
case 'reaction.added':
|
|
652
620
|
case 'reaction.removed':
|
|
653
621
|
return {
|
|
654
|
-
type: sourceType === 'reaction.
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
622
|
+
type: sourceType === 'reaction.removed' || str(record, 'action') === 'removed'
|
|
623
|
+
? 'reactionRemoved'
|
|
624
|
+
: 'reactionAdded',
|
|
625
|
+
messageId: str(record, 'message_id') ?? '',
|
|
626
|
+
emoji: str(record, 'emoji') ?? '',
|
|
627
|
+
agentName: str(record, 'agent_name') ?? '',
|
|
658
628
|
};
|
|
659
629
|
case 'action.invoked':
|
|
660
630
|
return {
|
|
661
631
|
type: 'actionInvoked',
|
|
662
|
-
invocationId:
|
|
663
|
-
actionName:
|
|
664
|
-
callerName:
|
|
665
|
-
handlerAgentId:
|
|
632
|
+
invocationId: str(record, 'invocation_id') ?? '',
|
|
633
|
+
actionName: str(record, 'action_name') ?? '',
|
|
634
|
+
callerName: str(record, 'caller_name') ?? '',
|
|
635
|
+
handlerAgentId: str(record, 'handler_agent_id') ?? '',
|
|
666
636
|
};
|
|
667
637
|
case 'open':
|
|
668
638
|
return { type: 'connected' };
|
|
@@ -671,9 +641,9 @@ export function normalizeMessagingEvent(input) {
|
|
|
671
641
|
case 'error':
|
|
672
642
|
return { type: 'error' };
|
|
673
643
|
case 'reconnecting':
|
|
674
|
-
return { type: 'reconnecting', attempt:
|
|
644
|
+
return { type: 'reconnecting', attempt: num(record, 'attempt') ?? 0 };
|
|
675
645
|
case 'permanently_disconnected':
|
|
676
|
-
return { type: 'permanentlyDisconnected', attempt:
|
|
646
|
+
return { type: 'permanentlyDisconnected', attempt: num(record, 'attempt') ?? 0 };
|
|
677
647
|
default:
|
|
678
648
|
return { type: 'unknown', ...(sourceType ? { sourceType } : {}), raw: input };
|
|
679
649
|
}
|