@canonmsg/backend-contracts 1.8.0 → 1.8.1
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 +8 -8
- package/dist/cjs/hitl.js +0 -171
- package/dist/cjs/restEndpoints.js +27 -10
- package/dist/restEndpoints.d.ts +51 -16
- package/dist/restEndpoints.js +26 -9
- 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/package.json +1 -1
package/dist/bridgeLocal.d.ts
CHANGED
|
@@ -519,24 +519,24 @@ export declare const BRIDGE_LOCAL_METHODS: {
|
|
|
519
519
|
behavior: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
520
520
|
hasUnread: z.ZodOptional<z.ZodBoolean>;
|
|
521
521
|
lastMessage: z.ZodNullable<z.ZodObject<{
|
|
522
|
-
text: z.ZodString
|
|
522
|
+
text: z.ZodNullable<z.ZodString>;
|
|
523
523
|
messageId: z.ZodOptional<z.ZodString>;
|
|
524
|
-
senderId: z.ZodString
|
|
525
|
-
senderType: z.ZodEnum<{
|
|
524
|
+
senderId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
525
|
+
senderType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
526
526
|
human: "human";
|
|
527
527
|
ai_agent: "ai_agent";
|
|
528
|
-
}
|
|
529
|
-
contentType: z.ZodOptional<z.ZodEnum<{
|
|
528
|
+
}>>>;
|
|
529
|
+
contentType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
530
530
|
file: "file";
|
|
531
531
|
image: "image";
|
|
532
532
|
audio: "audio";
|
|
533
533
|
video: "video";
|
|
534
534
|
text: "text";
|
|
535
535
|
contact_card: "contact_card";
|
|
536
|
-
}
|
|
537
|
-
timestamp: z.ZodString
|
|
536
|
+
}>>>;
|
|
537
|
+
timestamp: z.ZodNullable<z.ZodString>;
|
|
538
538
|
}, z.core.$loose>>;
|
|
539
|
-
createdAt: z.ZodOptional<z.ZodString
|
|
539
|
+
createdAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
540
540
|
}, z.core.$loose>>;
|
|
541
541
|
}, z.core.$strip>;
|
|
542
542
|
};
|
package/dist/cjs/hitl.js
CHANGED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HitlPendingSchema = exports.HitlResolutionSchema = exports.HitlRequestAcceptedSchema = exports.HitlCardOutcomeRecordSchema = exports.HitlCardReplyRecordSchema = exports.HitlCardRequestRecordSchema = exports.HitlInputOutcomeRecordSchema = exports.HitlInputReplyRecordSchema = exports.HitlInputRequestRecordSchema = exports.HitlInputAnswersSchema = exports.HitlInputQuestionSchema = exports.HitlInputKindSchema = exports.HitlApprovalOutcomeRecordSchema = exports.HitlApprovalReplyRecordSchema = exports.HitlApprovalRequestRecordSchema = exports.HitlSessionRuleSchema = exports.HitlApprovalDetailSchema = exports.HitlApprovalCategorySchema = exports.HitlApprovalRiskSchema = exports.HitlResolutionStatusSchema = exports.HitlRequestKindSchema = void 0;
|
|
4
|
-
// Wire contract v1 — HITL (human-in-the-loop) request / resolution records.
|
|
5
|
-
//
|
|
6
|
-
// HITL is durable state, not a held request (bridge-local-api.md): the pending
|
|
7
|
-
// request is a persisted record (message metadata + RTDB node with expiresAt),
|
|
8
|
-
// and the resolution arrives later. These schemas ground the three record
|
|
9
|
-
// families as core's hosts publish/consume them today:
|
|
10
|
-
// approval — approval_request / approval_reply / approval_outcome
|
|
11
|
-
// input — runtime_input_request / runtime_input_reply / runtime_input_outcome
|
|
12
|
-
// card — runtime_card_request / runtime_card_reply / runtime_card_outcome
|
|
13
|
-
// plus the unified bridge-level resolution record (§C6 onHitlResolved).
|
|
14
|
-
const zod_1 = require("zod");
|
|
15
|
-
const runtimeCardFields_js_1 = require("./runtimeCardFields.js");
|
|
16
|
-
exports.HitlRequestKindSchema = zod_1.z.enum(['approval', 'input', 'card']);
|
|
17
|
-
exports.HitlResolutionStatusSchema = zod_1.z.enum(['submitted', 'cancelled', 'timeout']);
|
|
18
|
-
// ── Approval records ───────────────────────────────────────────────────
|
|
19
|
-
exports.HitlApprovalRiskSchema = zod_1.z.enum(['low', 'normal', 'high', 'destructive']);
|
|
20
|
-
exports.HitlApprovalCategorySchema = zod_1.z.enum([
|
|
21
|
-
'command',
|
|
22
|
-
'file',
|
|
23
|
-
'network',
|
|
24
|
-
'browser',
|
|
25
|
-
'mcp',
|
|
26
|
-
'plugin',
|
|
27
|
-
'canon',
|
|
28
|
-
'tool',
|
|
29
|
-
]);
|
|
30
|
-
exports.HitlApprovalDetailSchema = zod_1.z.object({
|
|
31
|
-
label: zod_1.z.string(),
|
|
32
|
-
value: zod_1.z.string(),
|
|
33
|
-
monospace: zod_1.z.boolean().optional(),
|
|
34
|
-
});
|
|
35
|
-
exports.HitlSessionRuleSchema = zod_1.z.object({
|
|
36
|
-
type: zod_1.z.enum(['approve-all', 'approve-tool', 'deny-tool']),
|
|
37
|
-
toolPattern: zod_1.z.string().optional(),
|
|
38
|
-
expiresAt: zod_1.z.string().nullable().optional(),
|
|
39
|
-
});
|
|
40
|
-
exports.HitlApprovalRequestRecordSchema = zod_1.z.looseObject({
|
|
41
|
-
type: zod_1.z.literal('approval_request'),
|
|
42
|
-
approvalId: zod_1.z.string(),
|
|
43
|
-
responseUserId: zod_1.z.string().optional(),
|
|
44
|
-
toolName: zod_1.z.string(),
|
|
45
|
-
/** Pre-computed, redacted summary — raw tool input is never stored. */
|
|
46
|
-
toolSummary: zod_1.z.string(),
|
|
47
|
-
category: exports.HitlApprovalCategorySchema.optional(),
|
|
48
|
-
risk: exports.HitlApprovalRiskSchema.optional(),
|
|
49
|
-
riskLevel: zod_1.z.enum(['normal', 'destructive']).optional(),
|
|
50
|
-
runtimeId: zod_1.z.string().optional(),
|
|
51
|
-
turnId: zod_1.z.string().optional(),
|
|
52
|
-
/** Native runtime correlation handles — opaque pass-through. */
|
|
53
|
-
native: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
54
|
-
details: zod_1.z.array(exports.HitlApprovalDetailSchema).optional(),
|
|
55
|
-
/** File-change preview (CanonUnifiedDiff) — opaque pass-through. */
|
|
56
|
-
diff: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
57
|
-
expiresAt: zod_1.z.string(),
|
|
58
|
-
});
|
|
59
|
-
exports.HitlApprovalReplyRecordSchema = zod_1.z.looseObject({
|
|
60
|
-
type: zod_1.z.literal('approval_reply'),
|
|
61
|
-
approvalId: zod_1.z.string(),
|
|
62
|
-
decision: zod_1.z.enum(['allow', 'deny']),
|
|
63
|
-
sessionRule: exports.HitlSessionRuleSchema.optional(),
|
|
64
|
-
});
|
|
65
|
-
exports.HitlApprovalOutcomeRecordSchema = zod_1.z.looseObject({
|
|
66
|
-
type: zod_1.z.literal('approval_outcome'),
|
|
67
|
-
approvalId: zod_1.z.string().optional(),
|
|
68
|
-
decision: zod_1.z.enum(['allow', 'deny']).optional(),
|
|
69
|
-
reason: zod_1.z.enum(['replied', 'timeout', 'session-rule']).optional(),
|
|
70
|
-
});
|
|
71
|
-
// ── Input records ──────────────────────────────────────────────────────
|
|
72
|
-
exports.HitlInputKindSchema = zod_1.z.enum(['clarify', 'sudo', 'secret']);
|
|
73
|
-
exports.HitlInputQuestionSchema = zod_1.z.object({
|
|
74
|
-
id: zod_1.z.string(),
|
|
75
|
-
question: zod_1.z.string(),
|
|
76
|
-
header: zod_1.z.string().optional(),
|
|
77
|
-
choices: zod_1.z.array(runtimeCardFields_js_1.RuntimeCardFieldChoiceSchema).optional(),
|
|
78
|
-
allowOther: zod_1.z.boolean().optional(),
|
|
79
|
-
isSecret: zod_1.z.boolean().optional(),
|
|
80
|
-
multiSelect: zod_1.z.boolean().optional(),
|
|
81
|
-
});
|
|
82
|
-
/** questionId → selected/typed answers. */
|
|
83
|
-
exports.HitlInputAnswersSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.object({ answers: zod_1.z.array(zod_1.z.string()) }));
|
|
84
|
-
exports.HitlInputRequestRecordSchema = zod_1.z.looseObject({
|
|
85
|
-
type: zod_1.z.literal('runtime_input_request'),
|
|
86
|
-
inputId: zod_1.z.string(),
|
|
87
|
-
responseUserId: zod_1.z.string().optional(),
|
|
88
|
-
kind: exports.HitlInputKindSchema,
|
|
89
|
-
prompt: zod_1.z.string(),
|
|
90
|
-
title: zod_1.z.string().optional(),
|
|
91
|
-
choices: zod_1.z.array(runtimeCardFields_js_1.RuntimeCardFieldChoiceSchema).optional(),
|
|
92
|
-
questions: zod_1.z.array(exports.HitlInputQuestionSchema).optional(),
|
|
93
|
-
secretName: zod_1.z.string().optional(),
|
|
94
|
-
native: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
95
|
-
expiresAt: zod_1.z.string(),
|
|
96
|
-
sensitive: zod_1.z.boolean().optional(),
|
|
97
|
-
});
|
|
98
|
-
exports.HitlInputReplyRecordSchema = zod_1.z.looseObject({
|
|
99
|
-
type: zod_1.z.literal('runtime_input_reply'),
|
|
100
|
-
inputId: zod_1.z.string(),
|
|
101
|
-
kind: exports.HitlInputKindSchema.optional(),
|
|
102
|
-
status: zod_1.z.enum(['submitted', 'cancelled']),
|
|
103
|
-
answerSummary: zod_1.z.string().optional(),
|
|
104
|
-
answersSummary: zod_1.z.string().optional(),
|
|
105
|
-
sensitive: zod_1.z.boolean().optional(),
|
|
106
|
-
});
|
|
107
|
-
exports.HitlInputOutcomeRecordSchema = zod_1.z.looseObject({
|
|
108
|
-
type: zod_1.z.literal('runtime_input_outcome'),
|
|
109
|
-
inputId: zod_1.z.string(),
|
|
110
|
-
kind: exports.HitlInputKindSchema.optional(),
|
|
111
|
-
status: exports.HitlResolutionStatusSchema,
|
|
112
|
-
reason: zod_1.z.enum(['submitted', 'cancelled', 'timeout', 'expired', 'interrupted']).optional(),
|
|
113
|
-
});
|
|
114
|
-
// ── Card records ───────────────────────────────────────────────────────
|
|
115
|
-
exports.HitlCardRequestRecordSchema = zod_1.z.looseObject({
|
|
116
|
-
type: zod_1.z.literal('runtime_card_request'),
|
|
117
|
-
cardId: zod_1.z.string(),
|
|
118
|
-
cardKind: zod_1.z.string(),
|
|
119
|
-
schemaVersion: zod_1.z.literal('canon.card.v1'),
|
|
120
|
-
fallbackText: zod_1.z.string(),
|
|
121
|
-
responseUserId: zod_1.z.string().optional(),
|
|
122
|
-
expiresAt: zod_1.z.string(),
|
|
123
|
-
runtimeId: zod_1.z.string().optional(),
|
|
124
|
-
turnId: zod_1.z.string().optional(),
|
|
125
|
-
native: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
126
|
-
/** True when the card carries an actions block (agent waits for a response). */
|
|
127
|
-
interactive: zod_1.z.boolean().optional(),
|
|
128
|
-
/** RuntimeCardPreview — opaque pass-through. */
|
|
129
|
-
cardPreview: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
130
|
-
turnSemantics: zod_1.z.literal('control'),
|
|
131
|
-
replyBehavior: zod_1.z.literal('suppress_auto_reply'),
|
|
132
|
-
});
|
|
133
|
-
exports.HitlCardReplyRecordSchema = zod_1.z.looseObject({
|
|
134
|
-
type: zod_1.z.literal('runtime_card_reply'),
|
|
135
|
-
cardId: zod_1.z.string(),
|
|
136
|
-
status: zod_1.z.enum(['submitted', 'cancelled']),
|
|
137
|
-
actionId: zod_1.z.string().optional(),
|
|
138
|
-
answerSummary: zod_1.z.string().optional(),
|
|
139
|
-
sensitive: zod_1.z.boolean().optional(),
|
|
140
|
-
turnSemantics: zod_1.z.literal('control'),
|
|
141
|
-
replyBehavior: zod_1.z.literal('suppress_auto_reply'),
|
|
142
|
-
});
|
|
143
|
-
exports.HitlCardOutcomeRecordSchema = zod_1.z.looseObject({
|
|
144
|
-
type: zod_1.z.literal('runtime_card_outcome'),
|
|
145
|
-
cardId: zod_1.z.string(),
|
|
146
|
-
status: exports.HitlResolutionStatusSchema,
|
|
147
|
-
reason: zod_1.z.enum(['submitted', 'cancelled', 'timeout', 'expired', 'interrupted']).optional(),
|
|
148
|
-
turnSemantics: zod_1.z.literal('control'),
|
|
149
|
-
replyBehavior: zod_1.z.literal('suppress_auto_reply'),
|
|
150
|
-
});
|
|
151
|
-
// ── Unified bridge-level records (bridge-local-api §B7/§C6) ────────────
|
|
152
|
-
/** Immediate result of requestApproval/requestInput/requestCard. */
|
|
153
|
-
exports.HitlRequestAcceptedSchema = zod_1.z.object({
|
|
154
|
-
requestId: zod_1.z.string(),
|
|
155
|
-
expiresAt: zod_1.z.string(),
|
|
156
|
-
});
|
|
157
|
-
/** `onHitlResolved` — matches an earlier requestId. */
|
|
158
|
-
exports.HitlResolutionSchema = zod_1.z.object({
|
|
159
|
-
requestId: zod_1.z.string(),
|
|
160
|
-
kind: exports.HitlRequestKindSchema.optional(),
|
|
161
|
-
status: exports.HitlResolutionStatusSchema,
|
|
162
|
-
/** Kind-specific answer payload (decision / answers / card action values). */
|
|
163
|
-
answer: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
164
|
-
});
|
|
165
|
-
/** `onHitlPending` — replayed on reconnect for still-open requests. */
|
|
166
|
-
exports.HitlPendingSchema = zod_1.z.object({
|
|
167
|
-
requestId: zod_1.z.string(),
|
|
168
|
-
kind: exports.HitlRequestKindSchema,
|
|
169
|
-
conversationId: zod_1.z.string(),
|
|
170
|
-
expiresAt: zod_1.z.string(),
|
|
171
|
-
});
|
|
@@ -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/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(),
|
|
@@ -63,40 +63,73 @@
|
|
|
63
63
|
"type": "object",
|
|
64
64
|
"properties": {
|
|
65
65
|
"text": {
|
|
66
|
-
"
|
|
66
|
+
"anyOf": [
|
|
67
|
+
{
|
|
68
|
+
"type": "string"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"type": "null"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
67
74
|
},
|
|
68
75
|
"messageId": {
|
|
69
76
|
"type": "string"
|
|
70
77
|
},
|
|
71
78
|
"senderId": {
|
|
72
|
-
"
|
|
79
|
+
"anyOf": [
|
|
80
|
+
{
|
|
81
|
+
"type": "string"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"type": "null"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
73
87
|
},
|
|
74
88
|
"senderType": {
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
"anyOf": [
|
|
90
|
+
{
|
|
91
|
+
"type": "string",
|
|
92
|
+
"enum": [
|
|
93
|
+
"human",
|
|
94
|
+
"ai_agent"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "null"
|
|
99
|
+
}
|
|
79
100
|
]
|
|
80
101
|
},
|
|
81
102
|
"contentType": {
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
"anyOf": [
|
|
104
|
+
{
|
|
105
|
+
"type": "string",
|
|
106
|
+
"enum": [
|
|
107
|
+
"text",
|
|
108
|
+
"image",
|
|
109
|
+
"audio",
|
|
110
|
+
"video",
|
|
111
|
+
"file",
|
|
112
|
+
"contact_card"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "null"
|
|
117
|
+
}
|
|
90
118
|
]
|
|
91
119
|
},
|
|
92
120
|
"timestamp": {
|
|
93
|
-
"
|
|
121
|
+
"anyOf": [
|
|
122
|
+
{
|
|
123
|
+
"type": "string"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"type": "null"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
94
129
|
}
|
|
95
130
|
},
|
|
96
131
|
"required": [
|
|
97
132
|
"text",
|
|
98
|
-
"senderId",
|
|
99
|
-
"senderType",
|
|
100
133
|
"timestamp"
|
|
101
134
|
],
|
|
102
135
|
"additionalProperties": {}
|
|
@@ -107,7 +140,14 @@
|
|
|
107
140
|
]
|
|
108
141
|
},
|
|
109
142
|
"createdAt": {
|
|
110
|
-
"
|
|
143
|
+
"anyOf": [
|
|
144
|
+
{
|
|
145
|
+
"type": "string"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"type": "null"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
111
151
|
}
|
|
112
152
|
},
|
|
113
153
|
"required": [
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "canon:RestConversationLastMessageSchema",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"text": {
|
|
7
|
+
"anyOf": [
|
|
8
|
+
{
|
|
9
|
+
"type": "string"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "null"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"messageId": {
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"senderId": {
|
|
20
|
+
"anyOf": [
|
|
21
|
+
{
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "null"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"senderType": {
|
|
30
|
+
"anyOf": [
|
|
31
|
+
{
|
|
32
|
+
"type": "string",
|
|
33
|
+
"enum": [
|
|
34
|
+
"human",
|
|
35
|
+
"ai_agent"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "null"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"contentType": {
|
|
44
|
+
"anyOf": [
|
|
45
|
+
{
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": [
|
|
48
|
+
"text",
|
|
49
|
+
"image",
|
|
50
|
+
"audio",
|
|
51
|
+
"video",
|
|
52
|
+
"file",
|
|
53
|
+
"contact_card"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "null"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"timestamp": {
|
|
62
|
+
"anyOf": [
|
|
63
|
+
{
|
|
64
|
+
"type": "string"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "null"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"required": [
|
|
73
|
+
"text",
|
|
74
|
+
"timestamp"
|
|
75
|
+
],
|
|
76
|
+
"additionalProperties": {}
|
|
77
|
+
}
|
|
@@ -58,40 +58,73 @@
|
|
|
58
58
|
"type": "object",
|
|
59
59
|
"properties": {
|
|
60
60
|
"text": {
|
|
61
|
-
"
|
|
61
|
+
"anyOf": [
|
|
62
|
+
{
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "null"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
62
69
|
},
|
|
63
70
|
"messageId": {
|
|
64
71
|
"type": "string"
|
|
65
72
|
},
|
|
66
73
|
"senderId": {
|
|
67
|
-
"
|
|
74
|
+
"anyOf": [
|
|
75
|
+
{
|
|
76
|
+
"type": "string"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "null"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
68
82
|
},
|
|
69
83
|
"senderType": {
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
"anyOf": [
|
|
85
|
+
{
|
|
86
|
+
"type": "string",
|
|
87
|
+
"enum": [
|
|
88
|
+
"human",
|
|
89
|
+
"ai_agent"
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"type": "null"
|
|
94
|
+
}
|
|
74
95
|
]
|
|
75
96
|
},
|
|
76
97
|
"contentType": {
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
98
|
+
"anyOf": [
|
|
99
|
+
{
|
|
100
|
+
"type": "string",
|
|
101
|
+
"enum": [
|
|
102
|
+
"text",
|
|
103
|
+
"image",
|
|
104
|
+
"audio",
|
|
105
|
+
"video",
|
|
106
|
+
"file",
|
|
107
|
+
"contact_card"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"type": "null"
|
|
112
|
+
}
|
|
85
113
|
]
|
|
86
114
|
},
|
|
87
115
|
"timestamp": {
|
|
88
|
-
"
|
|
116
|
+
"anyOf": [
|
|
117
|
+
{
|
|
118
|
+
"type": "string"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"type": "null"
|
|
122
|
+
}
|
|
123
|
+
]
|
|
89
124
|
}
|
|
90
125
|
},
|
|
91
126
|
"required": [
|
|
92
127
|
"text",
|
|
93
|
-
"senderId",
|
|
94
|
-
"senderType",
|
|
95
128
|
"timestamp"
|
|
96
129
|
],
|
|
97
130
|
"additionalProperties": {}
|
|
@@ -102,7 +135,14 @@
|
|
|
102
135
|
]
|
|
103
136
|
},
|
|
104
137
|
"createdAt": {
|
|
105
|
-
"
|
|
138
|
+
"anyOf": [
|
|
139
|
+
{
|
|
140
|
+
"type": "string"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"type": "null"
|
|
144
|
+
}
|
|
145
|
+
]
|
|
106
146
|
}
|
|
107
147
|
},
|
|
108
148
|
"required": [
|