@canonmsg/backend-contracts 1.8.0 → 1.9.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/dist/bridgeLocal.d.ts +150 -8
- package/dist/bridgeLocal.js +71 -0
- package/dist/cjs/bridgeLocal.js +72 -1
- package/dist/cjs/restEndpoints.js +27 -10
- package/dist/cjs/sseEvents.js +4 -21
- package/dist/restEndpoints.d.ts +51 -16
- package/dist/restEndpoints.js +26 -9
- package/dist/schemas/bridge-publish-streaming-event-params.schema.json +138 -0
- package/dist/schemas/bridge-stream-clear-event.schema.json +15 -0
- package/dist/schemas/bridge-stream-event.schema.json +112 -0
- package/dist/schemas/bridge-stream-final-event.schema.json +19 -0
- package/dist/schemas/bridge-stream-text-event.schema.json +31 -0
- package/dist/schemas/bridge-stream-thinking-event.schema.json +15 -0
- package/dist/schemas/bridge-stream-tool-event.schema.json +36 -0
- package/dist/schemas/conversations-response.schema.json +58 -18
- package/dist/schemas/rest-conversation-last-message.schema.json +77 -0
- package/dist/schemas/rest-conversation.schema.json +58 -18
- package/dist/schemas/sse-event-envelope.schema.json +0 -2
- package/dist/schemas/sse-event-type.schema.json +0 -2
- package/dist/sseEvents.d.ts +1 -21
- package/dist/sseEvents.js +3 -20
- package/package.json +1 -1
- package/dist/schemas/sse-runtime-updated-payload.schema.json +0 -78
- package/dist/schemas/sse-turn-updated-payload.schema.json +0 -33
package/dist/bridgeLocal.d.ts
CHANGED
|
@@ -175,6 +175,111 @@ export interface BridgeLocalMethodContract {
|
|
|
175
175
|
params: z.ZodType;
|
|
176
176
|
result: z.ZodType;
|
|
177
177
|
}
|
|
178
|
+
/** A single tool-trail item forwarded raw; the bridge folds it into `blocks`. */
|
|
179
|
+
export declare const BridgeStreamToolEventSchema: z.ZodObject<{
|
|
180
|
+
kind: z.ZodLiteral<"tool">;
|
|
181
|
+
id: z.ZodString;
|
|
182
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
183
|
+
completed: "completed";
|
|
184
|
+
running: "running";
|
|
185
|
+
failed: "failed";
|
|
186
|
+
}>>;
|
|
187
|
+
title: z.ZodOptional<z.ZodString>;
|
|
188
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
189
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
190
|
+
}, z.core.$strip>;
|
|
191
|
+
/**
|
|
192
|
+
* A visible-text segment. `mode:'append'` concatenates onto the segment's
|
|
193
|
+
* running text; `mode:'replace'` overwrites it (the adapter already holds the
|
|
194
|
+
* full segment). The bridge rebuilds the bubble text from its text segments in
|
|
195
|
+
* sequence order, exactly as the core turn-output-controller does today.
|
|
196
|
+
*/
|
|
197
|
+
export declare const BridgeStreamTextEventSchema: z.ZodObject<{
|
|
198
|
+
kind: z.ZodLiteral<"text">;
|
|
199
|
+
id: z.ZodString;
|
|
200
|
+
mode: z.ZodEnum<{
|
|
201
|
+
replace: "replace";
|
|
202
|
+
append: "append";
|
|
203
|
+
}>;
|
|
204
|
+
text: z.ZodString;
|
|
205
|
+
}, z.core.$strip>;
|
|
206
|
+
/** A thinking tick (onset or keepalive) — no content; keeps the bubble warm. */
|
|
207
|
+
export declare const BridgeStreamThinkingEventSchema: z.ZodObject<{
|
|
208
|
+
kind: z.ZodLiteral<"thinking">;
|
|
209
|
+
}, z.core.$strip>;
|
|
210
|
+
/**
|
|
211
|
+
* Final compose: the assembled reply text is written one last time (status
|
|
212
|
+
* settles to `streaming`) and the node is LEFT in place for the final-message
|
|
213
|
+
* handoff. Clearing is a separate `clear` event / `clearStreaming`.
|
|
214
|
+
*/
|
|
215
|
+
export declare const BridgeStreamFinalEventSchema: z.ZodObject<{
|
|
216
|
+
kind: z.ZodLiteral<"final">;
|
|
217
|
+
text: z.ZodString;
|
|
218
|
+
}, z.core.$strip>;
|
|
219
|
+
/** Clear the streaming node (turn ended / interrupted). */
|
|
220
|
+
export declare const BridgeStreamClearEventSchema: z.ZodObject<{
|
|
221
|
+
kind: z.ZodLiteral<"clear">;
|
|
222
|
+
}, z.core.$strip>;
|
|
223
|
+
export declare const BridgeStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
224
|
+
kind: z.ZodLiteral<"text">;
|
|
225
|
+
id: z.ZodString;
|
|
226
|
+
mode: z.ZodEnum<{
|
|
227
|
+
replace: "replace";
|
|
228
|
+
append: "append";
|
|
229
|
+
}>;
|
|
230
|
+
text: z.ZodString;
|
|
231
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
232
|
+
kind: z.ZodLiteral<"tool">;
|
|
233
|
+
id: z.ZodString;
|
|
234
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
235
|
+
completed: "completed";
|
|
236
|
+
running: "running";
|
|
237
|
+
failed: "failed";
|
|
238
|
+
}>>;
|
|
239
|
+
title: z.ZodOptional<z.ZodString>;
|
|
240
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
241
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
242
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
243
|
+
kind: z.ZodLiteral<"thinking">;
|
|
244
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
245
|
+
kind: z.ZodLiteral<"final">;
|
|
246
|
+
text: z.ZodString;
|
|
247
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
248
|
+
kind: z.ZodLiteral<"clear">;
|
|
249
|
+
}, z.core.$strip>], "kind">;
|
|
250
|
+
export type BridgeStreamEvent = z.infer<typeof BridgeStreamEventSchema>;
|
|
251
|
+
export declare const BridgePublishStreamingEventParamsSchema: z.ZodObject<{
|
|
252
|
+
conversationId: z.ZodString;
|
|
253
|
+
turnId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
254
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
255
|
+
event: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
256
|
+
kind: z.ZodLiteral<"text">;
|
|
257
|
+
id: z.ZodString;
|
|
258
|
+
mode: z.ZodEnum<{
|
|
259
|
+
replace: "replace";
|
|
260
|
+
append: "append";
|
|
261
|
+
}>;
|
|
262
|
+
text: z.ZodString;
|
|
263
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
264
|
+
kind: z.ZodLiteral<"tool">;
|
|
265
|
+
id: z.ZodString;
|
|
266
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
267
|
+
completed: "completed";
|
|
268
|
+
running: "running";
|
|
269
|
+
failed: "failed";
|
|
270
|
+
}>>;
|
|
271
|
+
title: z.ZodOptional<z.ZodString>;
|
|
272
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
273
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
274
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
275
|
+
kind: z.ZodLiteral<"thinking">;
|
|
276
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
277
|
+
kind: z.ZodLiteral<"final">;
|
|
278
|
+
text: z.ZodString;
|
|
279
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
280
|
+
kind: z.ZodLiteral<"clear">;
|
|
281
|
+
}, z.core.$strip>], "kind">;
|
|
282
|
+
}, z.core.$strip>;
|
|
178
283
|
export declare const BRIDGE_LOCAL_METHODS: {
|
|
179
284
|
readonly hello: {
|
|
180
285
|
readonly params: z.ZodObject<{
|
|
@@ -519,24 +624,24 @@ export declare const BRIDGE_LOCAL_METHODS: {
|
|
|
519
624
|
behavior: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
520
625
|
hasUnread: z.ZodOptional<z.ZodBoolean>;
|
|
521
626
|
lastMessage: z.ZodNullable<z.ZodObject<{
|
|
522
|
-
text: z.ZodString
|
|
627
|
+
text: z.ZodNullable<z.ZodString>;
|
|
523
628
|
messageId: z.ZodOptional<z.ZodString>;
|
|
524
|
-
senderId: z.ZodString
|
|
525
|
-
senderType: z.ZodEnum<{
|
|
629
|
+
senderId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
630
|
+
senderType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
526
631
|
human: "human";
|
|
527
632
|
ai_agent: "ai_agent";
|
|
528
|
-
}
|
|
529
|
-
contentType: z.ZodOptional<z.ZodEnum<{
|
|
633
|
+
}>>>;
|
|
634
|
+
contentType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
530
635
|
file: "file";
|
|
531
636
|
image: "image";
|
|
532
637
|
audio: "audio";
|
|
533
638
|
video: "video";
|
|
534
639
|
text: "text";
|
|
535
640
|
contact_card: "contact_card";
|
|
536
|
-
}
|
|
537
|
-
timestamp: z.ZodString
|
|
641
|
+
}>>>;
|
|
642
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
538
643
|
}, z.core.$loose>>;
|
|
539
|
-
createdAt: z.ZodOptional<z.ZodString
|
|
644
|
+
createdAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
540
645
|
}, z.core.$loose>>;
|
|
541
646
|
}, z.core.$strip>;
|
|
542
647
|
};
|
|
@@ -867,6 +972,43 @@ export declare const BRIDGE_LOCAL_METHODS: {
|
|
|
867
972
|
ok: z.ZodLiteral<true>;
|
|
868
973
|
}, z.core.$strip>;
|
|
869
974
|
};
|
|
975
|
+
readonly publishStreamingEvent: {
|
|
976
|
+
readonly params: z.ZodObject<{
|
|
977
|
+
conversationId: z.ZodString;
|
|
978
|
+
turnId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
979
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
980
|
+
event: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
981
|
+
kind: z.ZodLiteral<"text">;
|
|
982
|
+
id: z.ZodString;
|
|
983
|
+
mode: z.ZodEnum<{
|
|
984
|
+
replace: "replace";
|
|
985
|
+
append: "append";
|
|
986
|
+
}>;
|
|
987
|
+
text: z.ZodString;
|
|
988
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
989
|
+
kind: z.ZodLiteral<"tool">;
|
|
990
|
+
id: z.ZodString;
|
|
991
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
992
|
+
completed: "completed";
|
|
993
|
+
running: "running";
|
|
994
|
+
failed: "failed";
|
|
995
|
+
}>>;
|
|
996
|
+
title: z.ZodOptional<z.ZodString>;
|
|
997
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
998
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
999
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1000
|
+
kind: z.ZodLiteral<"thinking">;
|
|
1001
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1002
|
+
kind: z.ZodLiteral<"final">;
|
|
1003
|
+
text: z.ZodString;
|
|
1004
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1005
|
+
kind: z.ZodLiteral<"clear">;
|
|
1006
|
+
}, z.core.$strip>], "kind">;
|
|
1007
|
+
}, z.core.$strip>;
|
|
1008
|
+
readonly result: z.ZodObject<{
|
|
1009
|
+
ok: z.ZodLiteral<true>;
|
|
1010
|
+
}, z.core.$strip>;
|
|
1011
|
+
};
|
|
870
1012
|
readonly publishRuntimeDescriptor: {
|
|
871
1013
|
readonly params: z.ZodObject<{
|
|
872
1014
|
descriptor: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
package/dist/bridgeLocal.js
CHANGED
|
@@ -153,6 +153,71 @@ const messageScoped = z.object({ conversationId: z.string(), messageId: z.string
|
|
|
153
153
|
*/
|
|
154
154
|
const ServerTimestampSentinelSchema = z.object({ '.sv': z.literal('timestamp') });
|
|
155
155
|
const EpochOrServerTimestampSchema = z.union([z.number(), ServerTimestampSentinelSchema]);
|
|
156
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
157
|
+
// W2-A4 REGION START — bridge-owned streaming assembly (raw event surface).
|
|
158
|
+
// ADDITIVE: `publishStreamingEvent` lets a channel adapter forward RAW turn
|
|
159
|
+
// events (a text segment begin/append/replace, a tool-trail item, a thinking
|
|
160
|
+
// tick, a final compose, or a clear). The BRIDGE owns snapshot assembly, the
|
|
161
|
+
// single write cadence, thinking keepalive, final compose, and node clear —
|
|
162
|
+
// so adapters stop reimplementing turn-output-controller + a keepalive loop
|
|
163
|
+
// (T1: they also stop swallowing the write failure). The pre-existing
|
|
164
|
+
// snapshot `publishStreaming` (below) is UNTOUCHED — coding hosts still call
|
|
165
|
+
// it via agent-host/runtime-writers.
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
167
|
+
/** A single tool-trail item forwarded raw; the bridge folds it into `blocks`. */
|
|
168
|
+
export const BridgeStreamToolEventSchema = z.object({
|
|
169
|
+
kind: z.literal('tool'),
|
|
170
|
+
/** Stable block id (adapter-scoped, e.g. `tool:{turnId}`). */
|
|
171
|
+
id: z.string(),
|
|
172
|
+
status: z.enum(['running', 'completed', 'failed']).optional(),
|
|
173
|
+
title: z.string().optional(),
|
|
174
|
+
summary: z.string().optional(),
|
|
175
|
+
detail: z.string().optional(),
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* A visible-text segment. `mode:'append'` concatenates onto the segment's
|
|
179
|
+
* running text; `mode:'replace'` overwrites it (the adapter already holds the
|
|
180
|
+
* full segment). The bridge rebuilds the bubble text from its text segments in
|
|
181
|
+
* sequence order, exactly as the core turn-output-controller does today.
|
|
182
|
+
*/
|
|
183
|
+
export const BridgeStreamTextEventSchema = z.object({
|
|
184
|
+
kind: z.literal('text'),
|
|
185
|
+
/** Segment id (adapter-scoped, e.g. `text:{turnId}:{n}`). */
|
|
186
|
+
id: z.string(),
|
|
187
|
+
mode: z.enum(['append', 'replace']),
|
|
188
|
+
text: z.string(),
|
|
189
|
+
});
|
|
190
|
+
/** A thinking tick (onset or keepalive) — no content; keeps the bubble warm. */
|
|
191
|
+
export const BridgeStreamThinkingEventSchema = z.object({
|
|
192
|
+
kind: z.literal('thinking'),
|
|
193
|
+
});
|
|
194
|
+
/**
|
|
195
|
+
* Final compose: the assembled reply text is written one last time (status
|
|
196
|
+
* settles to `streaming`) and the node is LEFT in place for the final-message
|
|
197
|
+
* handoff. Clearing is a separate `clear` event / `clearStreaming`.
|
|
198
|
+
*/
|
|
199
|
+
export const BridgeStreamFinalEventSchema = z.object({
|
|
200
|
+
kind: z.literal('final'),
|
|
201
|
+
text: z.string(),
|
|
202
|
+
});
|
|
203
|
+
/** Clear the streaming node (turn ended / interrupted). */
|
|
204
|
+
export const BridgeStreamClearEventSchema = z.object({
|
|
205
|
+
kind: z.literal('clear'),
|
|
206
|
+
});
|
|
207
|
+
export const BridgeStreamEventSchema = z.discriminatedUnion('kind', [
|
|
208
|
+
BridgeStreamTextEventSchema,
|
|
209
|
+
BridgeStreamToolEventSchema,
|
|
210
|
+
BridgeStreamThinkingEventSchema,
|
|
211
|
+
BridgeStreamFinalEventSchema,
|
|
212
|
+
BridgeStreamClearEventSchema,
|
|
213
|
+
]);
|
|
214
|
+
export const BridgePublishStreamingEventParamsSchema = conversationScoped.extend({
|
|
215
|
+
/** New turnId resets the bridge's accumulation for this conversation. */
|
|
216
|
+
turnId: z.string().nullable().optional(),
|
|
217
|
+
messageId: z.string().optional(),
|
|
218
|
+
event: BridgeStreamEventSchema,
|
|
219
|
+
});
|
|
220
|
+
// W2-A4 REGION END — bridge-owned streaming assembly.
|
|
156
221
|
export const BRIDGE_LOCAL_METHODS = {
|
|
157
222
|
// §A lifecycle
|
|
158
223
|
hello: { params: BridgeHelloParamsSchema, result: BridgeHelloResultSchema },
|
|
@@ -305,6 +370,12 @@ export const BRIDGE_LOCAL_METHODS = {
|
|
|
305
370
|
result: OkResultSchema,
|
|
306
371
|
},
|
|
307
372
|
clearStreaming: { params: conversationScoped, result: OkResultSchema },
|
|
373
|
+
// W2-A4 additive: raw streaming events (bridge owns assembly/cadence/
|
|
374
|
+
// keepalive/final/clear). Coexists with the snapshot publishStreaming above.
|
|
375
|
+
publishStreamingEvent: {
|
|
376
|
+
params: BridgePublishStreamingEventParamsSchema,
|
|
377
|
+
result: OkResultSchema,
|
|
378
|
+
},
|
|
308
379
|
// §B6 runtime presentation (adapter owns content, bridge publishes)
|
|
309
380
|
publishRuntimeDescriptor: {
|
|
310
381
|
params: z.object({
|
package/dist/cjs/bridgeLocal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BridgeRpcResponseSchema = exports.BridgeRpcErrorSchema = exports.BridgeRpcNotificationSchema = exports.BridgeRpcRequestSchema = exports.BridgeRpcIdSchema = exports.BRIDGE_LOCAL_NOTIFICATION_FAMILIES = exports.BRIDGE_LOCAL_NOTIFICATION_NAMES = exports.BRIDGE_LOCAL_NOTIFICATIONS = exports.BRIDGE_LOCAL_METHOD_NAMES = exports.BRIDGE_LOCAL_METHODS = exports.BridgeGetAttachmentResultSchema = exports.BridgeGetAttachmentParamsSchema = exports.BridgeUploadMediaParamsSchema = exports.BridgeHitlStateSchema = exports.BridgeStatusResultSchema = exports.BridgeConnectionStateNotificationSchema = exports.BridgeReadyNotificationSchema = exports.BridgeUpstreamStateSchema = exports.BridgeHelloResultSchema = exports.BRIDGE_EVENT_CURSOR_SHAPE = exports.BridgeResyncSchema = exports.BridgeResyncReasonSchema = exports.BridgeHelloParamsSchema = exports.BridgeOkResultSchema = exports.BRIDGE_LOCAL_PROTOCOL_VERSION = void 0;
|
|
3
|
+
exports.BridgeRpcResponseSchema = exports.BridgeRpcErrorSchema = exports.BridgeRpcNotificationSchema = exports.BridgeRpcRequestSchema = exports.BridgeRpcIdSchema = exports.BRIDGE_LOCAL_NOTIFICATION_FAMILIES = exports.BRIDGE_LOCAL_NOTIFICATION_NAMES = exports.BRIDGE_LOCAL_NOTIFICATIONS = exports.BRIDGE_LOCAL_METHOD_NAMES = exports.BRIDGE_LOCAL_METHODS = exports.BridgePublishStreamingEventParamsSchema = exports.BridgeStreamEventSchema = exports.BridgeStreamClearEventSchema = exports.BridgeStreamFinalEventSchema = exports.BridgeStreamThinkingEventSchema = exports.BridgeStreamTextEventSchema = exports.BridgeStreamToolEventSchema = exports.BridgeGetAttachmentResultSchema = exports.BridgeGetAttachmentParamsSchema = exports.BridgeUploadMediaParamsSchema = exports.BridgeHitlStateSchema = exports.BridgeStatusResultSchema = exports.BridgeConnectionStateNotificationSchema = exports.BridgeReadyNotificationSchema = exports.BridgeUpstreamStateSchema = exports.BridgeHelloResultSchema = exports.BRIDGE_EVENT_CURSOR_SHAPE = exports.BridgeResyncSchema = exports.BridgeResyncReasonSchema = exports.BridgeHelloParamsSchema = exports.BridgeOkResultSchema = exports.BRIDGE_LOCAL_PROTOCOL_VERSION = void 0;
|
|
4
4
|
// Bridge ⇄ runtime-adapter LOCAL JSON-RPC contract v1 (types + schemas ONLY —
|
|
5
5
|
// no runtime, no transport; Phase 4 builds the bridge against these).
|
|
6
6
|
//
|
|
@@ -156,6 +156,71 @@ const messageScoped = zod_1.z.object({ conversationId: zod_1.z.string(), message
|
|
|
156
156
|
*/
|
|
157
157
|
const ServerTimestampSentinelSchema = zod_1.z.object({ '.sv': zod_1.z.literal('timestamp') });
|
|
158
158
|
const EpochOrServerTimestampSchema = zod_1.z.union([zod_1.z.number(), ServerTimestampSentinelSchema]);
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
160
|
+
// W2-A4 REGION START — bridge-owned streaming assembly (raw event surface).
|
|
161
|
+
// ADDITIVE: `publishStreamingEvent` lets a channel adapter forward RAW turn
|
|
162
|
+
// events (a text segment begin/append/replace, a tool-trail item, a thinking
|
|
163
|
+
// tick, a final compose, or a clear). The BRIDGE owns snapshot assembly, the
|
|
164
|
+
// single write cadence, thinking keepalive, final compose, and node clear —
|
|
165
|
+
// so adapters stop reimplementing turn-output-controller + a keepalive loop
|
|
166
|
+
// (T1: they also stop swallowing the write failure). The pre-existing
|
|
167
|
+
// snapshot `publishStreaming` (below) is UNTOUCHED — coding hosts still call
|
|
168
|
+
// it via agent-host/runtime-writers.
|
|
169
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
170
|
+
/** A single tool-trail item forwarded raw; the bridge folds it into `blocks`. */
|
|
171
|
+
exports.BridgeStreamToolEventSchema = zod_1.z.object({
|
|
172
|
+
kind: zod_1.z.literal('tool'),
|
|
173
|
+
/** Stable block id (adapter-scoped, e.g. `tool:{turnId}`). */
|
|
174
|
+
id: zod_1.z.string(),
|
|
175
|
+
status: zod_1.z.enum(['running', 'completed', 'failed']).optional(),
|
|
176
|
+
title: zod_1.z.string().optional(),
|
|
177
|
+
summary: zod_1.z.string().optional(),
|
|
178
|
+
detail: zod_1.z.string().optional(),
|
|
179
|
+
});
|
|
180
|
+
/**
|
|
181
|
+
* A visible-text segment. `mode:'append'` concatenates onto the segment's
|
|
182
|
+
* running text; `mode:'replace'` overwrites it (the adapter already holds the
|
|
183
|
+
* full segment). The bridge rebuilds the bubble text from its text segments in
|
|
184
|
+
* sequence order, exactly as the core turn-output-controller does today.
|
|
185
|
+
*/
|
|
186
|
+
exports.BridgeStreamTextEventSchema = zod_1.z.object({
|
|
187
|
+
kind: zod_1.z.literal('text'),
|
|
188
|
+
/** Segment id (adapter-scoped, e.g. `text:{turnId}:{n}`). */
|
|
189
|
+
id: zod_1.z.string(),
|
|
190
|
+
mode: zod_1.z.enum(['append', 'replace']),
|
|
191
|
+
text: zod_1.z.string(),
|
|
192
|
+
});
|
|
193
|
+
/** A thinking tick (onset or keepalive) — no content; keeps the bubble warm. */
|
|
194
|
+
exports.BridgeStreamThinkingEventSchema = zod_1.z.object({
|
|
195
|
+
kind: zod_1.z.literal('thinking'),
|
|
196
|
+
});
|
|
197
|
+
/**
|
|
198
|
+
* Final compose: the assembled reply text is written one last time (status
|
|
199
|
+
* settles to `streaming`) and the node is LEFT in place for the final-message
|
|
200
|
+
* handoff. Clearing is a separate `clear` event / `clearStreaming`.
|
|
201
|
+
*/
|
|
202
|
+
exports.BridgeStreamFinalEventSchema = zod_1.z.object({
|
|
203
|
+
kind: zod_1.z.literal('final'),
|
|
204
|
+
text: zod_1.z.string(),
|
|
205
|
+
});
|
|
206
|
+
/** Clear the streaming node (turn ended / interrupted). */
|
|
207
|
+
exports.BridgeStreamClearEventSchema = zod_1.z.object({
|
|
208
|
+
kind: zod_1.z.literal('clear'),
|
|
209
|
+
});
|
|
210
|
+
exports.BridgeStreamEventSchema = zod_1.z.discriminatedUnion('kind', [
|
|
211
|
+
exports.BridgeStreamTextEventSchema,
|
|
212
|
+
exports.BridgeStreamToolEventSchema,
|
|
213
|
+
exports.BridgeStreamThinkingEventSchema,
|
|
214
|
+
exports.BridgeStreamFinalEventSchema,
|
|
215
|
+
exports.BridgeStreamClearEventSchema,
|
|
216
|
+
]);
|
|
217
|
+
exports.BridgePublishStreamingEventParamsSchema = conversationScoped.extend({
|
|
218
|
+
/** New turnId resets the bridge's accumulation for this conversation. */
|
|
219
|
+
turnId: zod_1.z.string().nullable().optional(),
|
|
220
|
+
messageId: zod_1.z.string().optional(),
|
|
221
|
+
event: exports.BridgeStreamEventSchema,
|
|
222
|
+
});
|
|
223
|
+
// W2-A4 REGION END — bridge-owned streaming assembly.
|
|
159
224
|
exports.BRIDGE_LOCAL_METHODS = {
|
|
160
225
|
// §A lifecycle
|
|
161
226
|
hello: { params: exports.BridgeHelloParamsSchema, result: exports.BridgeHelloResultSchema },
|
|
@@ -308,6 +373,12 @@ exports.BRIDGE_LOCAL_METHODS = {
|
|
|
308
373
|
result: OkResultSchema,
|
|
309
374
|
},
|
|
310
375
|
clearStreaming: { params: conversationScoped, result: OkResultSchema },
|
|
376
|
+
// W2-A4 additive: raw streaming events (bridge owns assembly/cadence/
|
|
377
|
+
// keepalive/final/clear). Coexists with the snapshot publishStreaming above.
|
|
378
|
+
publishStreamingEvent: {
|
|
379
|
+
params: exports.BridgePublishStreamingEventParamsSchema,
|
|
380
|
+
result: OkResultSchema,
|
|
381
|
+
},
|
|
311
382
|
// §B6 runtime presentation (adapter owns content, bridge publishes)
|
|
312
383
|
publishRuntimeDescriptor: {
|
|
313
384
|
params: zod_1.z.object({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UploadMediaResponseSchema = exports.UploadMediaRequestSchema = exports.SetStreamingRequestSchema = exports.SetTypingRequestSchema = exports.ResolveAdmissionResponseSchema = exports.ResolveAdmissionRequestSchema = exports.ContactsResponseSchema = exports.CreateContactRequestResponseSchema = exports.CreateContactRequestRequestSchema = exports.CreateConversationResponseSchema = exports.CreateConversationRequestSchema = exports.MessagesPageResponseSchema = exports.GetMessagesQuerySchema = exports.ConversationsResponseSchema = exports.UpdateDispositionRequestSchema = exports.MarkReadResponseSchema = exports.DeleteMessageResponseSchema = exports.ForwardMessageResponseSchema = exports.ForwardMessageRequestSchema = exports.ReactResponseSchema = exports.ReactRequestSchema = exports.SendMessageResponseSchema = exports.SendMessageRequestSchema = exports.RestSessionSelectionSchema = exports.RestContactSchema = exports.RestConversationSchema = exports.RestMessageSchema = exports.RestOkResponseSchema = void 0;
|
|
3
|
+
exports.UploadMediaResponseSchema = exports.UploadMediaRequestSchema = exports.SetStreamingRequestSchema = exports.SetTypingRequestSchema = exports.ResolveAdmissionResponseSchema = exports.ResolveAdmissionRequestSchema = exports.ContactsResponseSchema = exports.CreateContactRequestResponseSchema = exports.CreateContactRequestRequestSchema = exports.CreateConversationResponseSchema = exports.CreateConversationRequestSchema = exports.MessagesPageResponseSchema = exports.GetMessagesQuerySchema = exports.ConversationsResponseSchema = exports.UpdateDispositionRequestSchema = exports.MarkReadResponseSchema = exports.DeleteMessageResponseSchema = exports.ForwardMessageResponseSchema = exports.ForwardMessageRequestSchema = exports.ReactResponseSchema = exports.ReactRequestSchema = exports.SendMessageResponseSchema = exports.SendMessageRequestSchema = exports.RestSessionSelectionSchema = exports.RestContactSchema = exports.RestConversationSchema = exports.RestConversationLastMessageSchema = exports.RestMessageSchema = exports.RestOkResponseSchema = void 0;
|
|
4
4
|
// Wire contract v1 — request/response schemas for the agent-facing "Canon op"
|
|
5
5
|
// REST surface the bridge proxies (bridge-local-api.md §B).
|
|
6
6
|
//
|
|
@@ -20,6 +20,30 @@ exports.RestOkResponseSchema = zod_1.z.looseObject({
|
|
|
20
20
|
exports.RestMessageSchema = message_js_1.SerializedMessageSchema.extend({
|
|
21
21
|
deleted: zod_1.z.boolean().optional(),
|
|
22
22
|
});
|
|
23
|
+
/**
|
|
24
|
+
* STRICT WRITES, TOLERANT READS: this is a READ schema over live production
|
|
25
|
+
* data, so it must describe what Firestore actually holds — not what today's
|
|
26
|
+
* writers produce. The GET /conversations serializer
|
|
27
|
+
* (functions/src/api/getConversations.ts) passes `lastMessage` fields through
|
|
28
|
+
* from the conversation doc, and legacy rows (pre-refactor writes,
|
|
29
|
+
* deleted-message repair) hold explicit nulls:
|
|
30
|
+
* - `text` is serialized `?? null` and `timestamp` as
|
|
31
|
+
* `?.toDate?.()?.toISOString() ?? null` — both emit null by construction.
|
|
32
|
+
* - `senderId` / `senderType` / `contentType` are raw pass-throughs; live
|
|
33
|
+
* rows exist with all three null (the getConversations "ghost fix" comment
|
|
34
|
+
* acknowledges null senderId).
|
|
35
|
+
* - conversation `createdAt` is also serialized `?? null`.
|
|
36
|
+
* Writers (onMessageCreated / deletedMessageRepair) stay strict — they always
|
|
37
|
+
* produce the full shape; only the read contract admits the legacy nulls.
|
|
38
|
+
*/
|
|
39
|
+
exports.RestConversationLastMessageSchema = zod_1.z.looseObject({
|
|
40
|
+
text: zod_1.z.string().nullable(),
|
|
41
|
+
messageId: zod_1.z.string().optional(),
|
|
42
|
+
senderId: zod_1.z.string().nullable().optional(),
|
|
43
|
+
senderType: zod_1.z.enum(['human', 'ai_agent']).nullable().optional(),
|
|
44
|
+
contentType: message_js_1.SerializedContentTypeSchema.nullable().optional(),
|
|
45
|
+
timestamp: zod_1.z.string().nullable(),
|
|
46
|
+
});
|
|
23
47
|
exports.RestConversationSchema = zod_1.z.looseObject({
|
|
24
48
|
id: zod_1.z.string(),
|
|
25
49
|
type: zod_1.z.enum(['direct', 'group']),
|
|
@@ -30,15 +54,8 @@ exports.RestConversationSchema = zod_1.z.looseObject({
|
|
|
30
54
|
/** Resolved agent behavior policy — opaque pass-through. */
|
|
31
55
|
behavior: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
32
56
|
hasUnread: zod_1.z.boolean().optional(),
|
|
33
|
-
lastMessage:
|
|
34
|
-
|
|
35
|
-
messageId: zod_1.z.string().optional(),
|
|
36
|
-
senderId: zod_1.z.string(),
|
|
37
|
-
senderType: zod_1.z.enum(['human', 'ai_agent']),
|
|
38
|
-
contentType: message_js_1.SerializedContentTypeSchema.optional(),
|
|
39
|
-
timestamp: zod_1.z.string(),
|
|
40
|
-
}).nullable(),
|
|
41
|
-
createdAt: zod_1.z.string().optional(),
|
|
57
|
+
lastMessage: exports.RestConversationLastMessageSchema.nullable(),
|
|
58
|
+
createdAt: zod_1.z.string().nullable().optional(),
|
|
42
59
|
});
|
|
43
60
|
exports.RestContactSchema = zod_1.z.object({
|
|
44
61
|
id: zod_1.z.string(),
|
package/dist/cjs/sseEvents.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SseEventEnvelopeSchema = exports.SSE_EVENT_FAMILY_BY_TYPE = exports.SseEventFamilySchema = exports.SSE_EVENT_FAMILIES = exports.SSE_EVENT_PAYLOAD_SCHEMAS = exports.SseEventTypeSchema = exports.SSE_EVENT_TYPES = exports.SseVoiceSessionEventPayloadSchema = exports.SseVoiceSessionSchema = exports.SseRuntimeControlPayloadSchema = exports.SseRuntimeControlKindSchema = exports.
|
|
3
|
+
exports.SseEventEnvelopeSchema = exports.SSE_EVENT_FAMILY_BY_TYPE = exports.SseEventFamilySchema = exports.SSE_EVENT_FAMILIES = exports.SSE_EVENT_PAYLOAD_SCHEMAS = exports.SseEventTypeSchema = exports.SSE_EVENT_TYPES = exports.SseVoiceSessionEventPayloadSchema = exports.SseVoiceSessionSchema = exports.SseRuntimeControlPayloadSchema = exports.SseRuntimeControlKindSchema = exports.SsePresencePayloadSchema = exports.SseTypingPayloadSchema = exports.SseContactRemovedPayloadSchema = exports.SseContactPayloadSchema = exports.SseContactRequestPayloadSchema = exports.SseConversationUpdatedPayloadSchema = exports.SseMembershipChangeSchema = exports.SseMessageDeletedPayloadSchema = exports.SseMessageUpdatedPayloadSchema = exports.SseMessageCreatedPayloadSchema = exports.SseProvenanceSchema = exports.SseTurnDispatchSchema = exports.SseSelfContextSchema = exports.SseErrorPayloadSchema = exports.SseReplayExpiredPayloadSchema = exports.SseHeartbeatPayloadSchema = exports.SseAgentContextPayloadSchema = exports.SseConnectedPayloadSchema = exports.SseAccessPolicySchema = void 0;
|
|
4
4
|
// Wire contract v1 — SSE event envelope + per-family payload schemas.
|
|
5
5
|
//
|
|
6
6
|
// Grounded in what stream-service ACTUALLY emits (stream-service/src/stream.ts
|
|
@@ -142,20 +142,9 @@ exports.SsePresencePayloadSchema = zod_1.z.object({
|
|
|
142
142
|
lastSeen: zod_1.z.string().optional(),
|
|
143
143
|
});
|
|
144
144
|
// ── Runtime / turn family ──────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
agentId: zod_1.z.string(),
|
|
149
|
-
runtime: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullable(),
|
|
150
|
-
runtimeInfo: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullable(),
|
|
151
|
-
sessionState: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullable(),
|
|
152
|
-
sessionConfig: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullable(),
|
|
153
|
-
});
|
|
154
|
-
exports.SseTurnUpdatedPayloadSchema = zod_1.z.object({
|
|
155
|
-
conversationId: zod_1.z.string(),
|
|
156
|
-
agentId: zod_1.z.string(),
|
|
157
|
-
turn: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullable(),
|
|
158
|
-
});
|
|
145
|
+
// `runtime.updated` / `turn.updated` were retired 2026-07 (zero consumers;
|
|
146
|
+
// the bridge reads RTDB directly). `runtime.control` remains — it is the
|
|
147
|
+
// planned event-push control transport (housecleaning plan §6 W2-A2).
|
|
159
148
|
exports.SseRuntimeControlKindSchema = zod_1.z.enum([
|
|
160
149
|
'session',
|
|
161
150
|
'signal',
|
|
@@ -219,8 +208,6 @@ exports.SSE_EVENT_TYPES = [
|
|
|
219
208
|
'contact.removed',
|
|
220
209
|
'typing',
|
|
221
210
|
'presence',
|
|
222
|
-
'runtime.updated',
|
|
223
|
-
'turn.updated',
|
|
224
211
|
'runtime.control',
|
|
225
212
|
'voice.session.started',
|
|
226
213
|
'voice.session.ended',
|
|
@@ -244,8 +231,6 @@ exports.SSE_EVENT_PAYLOAD_SCHEMAS = {
|
|
|
244
231
|
'contact.removed': exports.SseContactRemovedPayloadSchema,
|
|
245
232
|
'typing': exports.SseTypingPayloadSchema,
|
|
246
233
|
'presence': exports.SsePresencePayloadSchema,
|
|
247
|
-
'runtime.updated': exports.SseRuntimeUpdatedPayloadSchema,
|
|
248
|
-
'turn.updated': exports.SseTurnUpdatedPayloadSchema,
|
|
249
234
|
'runtime.control': exports.SseRuntimeControlPayloadSchema,
|
|
250
235
|
'voice.session.started': exports.SseVoiceSessionEventPayloadSchema,
|
|
251
236
|
'voice.session.ended': exports.SseVoiceSessionEventPayloadSchema,
|
|
@@ -279,8 +264,6 @@ exports.SSE_EVENT_FAMILY_BY_TYPE = {
|
|
|
279
264
|
'contact.removed': 'contacts',
|
|
280
265
|
'typing': 'typing_presence',
|
|
281
266
|
'presence': 'typing_presence',
|
|
282
|
-
'runtime.updated': 'runtime_turn',
|
|
283
|
-
'turn.updated': 'runtime_turn',
|
|
284
267
|
'runtime.control': 'runtime_turn',
|
|
285
268
|
'voice.session.started': 'voice',
|
|
286
269
|
'voice.session.ended': 'voice',
|
package/dist/restEndpoints.d.ts
CHANGED
|
@@ -78,6 +78,41 @@ export declare const RestMessageSchema: z.ZodObject<{
|
|
|
78
78
|
deleted: z.ZodOptional<z.ZodBoolean>;
|
|
79
79
|
}, z.core.$strip>;
|
|
80
80
|
export type RestMessage = z.infer<typeof RestMessageSchema>;
|
|
81
|
+
/**
|
|
82
|
+
* STRICT WRITES, TOLERANT READS: this is a READ schema over live production
|
|
83
|
+
* data, so it must describe what Firestore actually holds — not what today's
|
|
84
|
+
* writers produce. The GET /conversations serializer
|
|
85
|
+
* (functions/src/api/getConversations.ts) passes `lastMessage` fields through
|
|
86
|
+
* from the conversation doc, and legacy rows (pre-refactor writes,
|
|
87
|
+
* deleted-message repair) hold explicit nulls:
|
|
88
|
+
* - `text` is serialized `?? null` and `timestamp` as
|
|
89
|
+
* `?.toDate?.()?.toISOString() ?? null` — both emit null by construction.
|
|
90
|
+
* - `senderId` / `senderType` / `contentType` are raw pass-throughs; live
|
|
91
|
+
* rows exist with all three null (the getConversations "ghost fix" comment
|
|
92
|
+
* acknowledges null senderId).
|
|
93
|
+
* - conversation `createdAt` is also serialized `?? null`.
|
|
94
|
+
* Writers (onMessageCreated / deletedMessageRepair) stay strict — they always
|
|
95
|
+
* produce the full shape; only the read contract admits the legacy nulls.
|
|
96
|
+
*/
|
|
97
|
+
export declare const RestConversationLastMessageSchema: z.ZodObject<{
|
|
98
|
+
text: z.ZodNullable<z.ZodString>;
|
|
99
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
100
|
+
senderId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
101
|
+
senderType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
102
|
+
human: "human";
|
|
103
|
+
ai_agent: "ai_agent";
|
|
104
|
+
}>>>;
|
|
105
|
+
contentType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
106
|
+
file: "file";
|
|
107
|
+
image: "image";
|
|
108
|
+
audio: "audio";
|
|
109
|
+
video: "video";
|
|
110
|
+
text: "text";
|
|
111
|
+
contact_card: "contact_card";
|
|
112
|
+
}>>>;
|
|
113
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
114
|
+
}, z.core.$loose>;
|
|
115
|
+
export type RestConversationLastMessage = z.infer<typeof RestConversationLastMessageSchema>;
|
|
81
116
|
export declare const RestConversationSchema: z.ZodObject<{
|
|
82
117
|
id: z.ZodString;
|
|
83
118
|
type: z.ZodEnum<{
|
|
@@ -91,24 +126,24 @@ export declare const RestConversationSchema: z.ZodObject<{
|
|
|
91
126
|
behavior: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
92
127
|
hasUnread: z.ZodOptional<z.ZodBoolean>;
|
|
93
128
|
lastMessage: z.ZodNullable<z.ZodObject<{
|
|
94
|
-
text: z.ZodString
|
|
129
|
+
text: z.ZodNullable<z.ZodString>;
|
|
95
130
|
messageId: z.ZodOptional<z.ZodString>;
|
|
96
|
-
senderId: z.ZodString
|
|
97
|
-
senderType: z.ZodEnum<{
|
|
131
|
+
senderId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
132
|
+
senderType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
98
133
|
human: "human";
|
|
99
134
|
ai_agent: "ai_agent";
|
|
100
|
-
}
|
|
101
|
-
contentType: z.ZodOptional<z.ZodEnum<{
|
|
135
|
+
}>>>;
|
|
136
|
+
contentType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
102
137
|
file: "file";
|
|
103
138
|
image: "image";
|
|
104
139
|
audio: "audio";
|
|
105
140
|
video: "video";
|
|
106
141
|
text: "text";
|
|
107
142
|
contact_card: "contact_card";
|
|
108
|
-
}
|
|
109
|
-
timestamp: z.ZodString
|
|
143
|
+
}>>>;
|
|
144
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
110
145
|
}, z.core.$loose>>;
|
|
111
|
-
createdAt: z.ZodOptional<z.ZodString
|
|
146
|
+
createdAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
112
147
|
}, z.core.$loose>;
|
|
113
148
|
export type RestConversation = z.infer<typeof RestConversationSchema>;
|
|
114
149
|
export declare const RestContactSchema: z.ZodObject<{
|
|
@@ -230,24 +265,24 @@ export declare const ConversationsResponseSchema: z.ZodObject<{
|
|
|
230
265
|
behavior: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
231
266
|
hasUnread: z.ZodOptional<z.ZodBoolean>;
|
|
232
267
|
lastMessage: z.ZodNullable<z.ZodObject<{
|
|
233
|
-
text: z.ZodString
|
|
268
|
+
text: z.ZodNullable<z.ZodString>;
|
|
234
269
|
messageId: z.ZodOptional<z.ZodString>;
|
|
235
|
-
senderId: z.ZodString
|
|
236
|
-
senderType: z.ZodEnum<{
|
|
270
|
+
senderId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
271
|
+
senderType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
237
272
|
human: "human";
|
|
238
273
|
ai_agent: "ai_agent";
|
|
239
|
-
}
|
|
240
|
-
contentType: z.ZodOptional<z.ZodEnum<{
|
|
274
|
+
}>>>;
|
|
275
|
+
contentType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
241
276
|
file: "file";
|
|
242
277
|
image: "image";
|
|
243
278
|
audio: "audio";
|
|
244
279
|
video: "video";
|
|
245
280
|
text: "text";
|
|
246
281
|
contact_card: "contact_card";
|
|
247
|
-
}
|
|
248
|
-
timestamp: z.ZodString
|
|
282
|
+
}>>>;
|
|
283
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
249
284
|
}, z.core.$loose>>;
|
|
250
|
-
createdAt: z.ZodOptional<z.ZodString
|
|
285
|
+
createdAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
251
286
|
}, z.core.$loose>>;
|
|
252
287
|
}, z.core.$strip>;
|
|
253
288
|
export type ConversationsResponse = z.infer<typeof ConversationsResponseSchema>;
|
package/dist/restEndpoints.js
CHANGED
|
@@ -17,6 +17,30 @@ export const RestOkResponseSchema = z.looseObject({
|
|
|
17
17
|
export const RestMessageSchema = SerializedMessageSchema.extend({
|
|
18
18
|
deleted: z.boolean().optional(),
|
|
19
19
|
});
|
|
20
|
+
/**
|
|
21
|
+
* STRICT WRITES, TOLERANT READS: this is a READ schema over live production
|
|
22
|
+
* data, so it must describe what Firestore actually holds — not what today's
|
|
23
|
+
* writers produce. The GET /conversations serializer
|
|
24
|
+
* (functions/src/api/getConversations.ts) passes `lastMessage` fields through
|
|
25
|
+
* from the conversation doc, and legacy rows (pre-refactor writes,
|
|
26
|
+
* deleted-message repair) hold explicit nulls:
|
|
27
|
+
* - `text` is serialized `?? null` and `timestamp` as
|
|
28
|
+
* `?.toDate?.()?.toISOString() ?? null` — both emit null by construction.
|
|
29
|
+
* - `senderId` / `senderType` / `contentType` are raw pass-throughs; live
|
|
30
|
+
* rows exist with all three null (the getConversations "ghost fix" comment
|
|
31
|
+
* acknowledges null senderId).
|
|
32
|
+
* - conversation `createdAt` is also serialized `?? null`.
|
|
33
|
+
* Writers (onMessageCreated / deletedMessageRepair) stay strict — they always
|
|
34
|
+
* produce the full shape; only the read contract admits the legacy nulls.
|
|
35
|
+
*/
|
|
36
|
+
export const RestConversationLastMessageSchema = z.looseObject({
|
|
37
|
+
text: z.string().nullable(),
|
|
38
|
+
messageId: z.string().optional(),
|
|
39
|
+
senderId: z.string().nullable().optional(),
|
|
40
|
+
senderType: z.enum(['human', 'ai_agent']).nullable().optional(),
|
|
41
|
+
contentType: SerializedContentTypeSchema.nullable().optional(),
|
|
42
|
+
timestamp: z.string().nullable(),
|
|
43
|
+
});
|
|
20
44
|
export const RestConversationSchema = z.looseObject({
|
|
21
45
|
id: z.string(),
|
|
22
46
|
type: z.enum(['direct', 'group']),
|
|
@@ -27,15 +51,8 @@ export const RestConversationSchema = z.looseObject({
|
|
|
27
51
|
/** Resolved agent behavior policy — opaque pass-through. */
|
|
28
52
|
behavior: z.record(z.string(), z.unknown()).optional(),
|
|
29
53
|
hasUnread: z.boolean().optional(),
|
|
30
|
-
lastMessage:
|
|
31
|
-
|
|
32
|
-
messageId: z.string().optional(),
|
|
33
|
-
senderId: z.string(),
|
|
34
|
-
senderType: z.enum(['human', 'ai_agent']),
|
|
35
|
-
contentType: SerializedContentTypeSchema.optional(),
|
|
36
|
-
timestamp: z.string(),
|
|
37
|
-
}).nullable(),
|
|
38
|
-
createdAt: z.string().optional(),
|
|
54
|
+
lastMessage: RestConversationLastMessageSchema.nullable(),
|
|
55
|
+
createdAt: z.string().nullable().optional(),
|
|
39
56
|
});
|
|
40
57
|
export const RestContactSchema = z.object({
|
|
41
58
|
id: z.string(),
|