@elevasis/core 0.51.1 → 0.52.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/core",
3
- "version": "0.51.1",
3
+ "version": "0.52.0",
4
4
  "license": "MIT",
5
5
  "description": "Minimal shared constants across Elevasis monorepo",
6
6
  "sideEffects": false,
@@ -1,9 +1,14 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import {
3
+ CaptureFieldSchema,
3
4
  PublicAgentChatAuthorizeSchema,
4
5
  PublicAgentChatBrandingSchema,
5
6
  PublicAgentChatCreateSessionSchema,
6
- PublicAgentChatSlugParamSchema
7
+ PublicAgentChatGrantSchema,
8
+ PublicAgentChatInboundMessageSchema,
9
+ PublicAgentChatSlugParamSchema,
10
+ WebSocketSessionEndSchema,
11
+ WebSocketSessionEndedEventSchema
7
12
  } from '../api-schemas'
8
13
 
9
14
  describe('PublicAgentChatBrandingSchema', () => {
@@ -106,4 +111,153 @@ describe('PublicAgentChatCreateSessionSchema', () => {
106
111
  it('rejects unknown keys (.strict)', () => {
107
112
  expect(PublicAgentChatCreateSessionSchema.safeParse({ rogue: true }).success).toBe(false)
108
113
  })
114
+
115
+ it('accepts arbitrary metadata (z.record — not narrowed)', () => {
116
+ const result = PublicAgentChatCreateSessionSchema.safeParse({
117
+ capabilityToken: 'a'.repeat(32),
118
+ metadata: { name: 'Alice', company: 'Acme', nested: { foo: 1 } }
119
+ })
120
+ expect(result.success).toBe(true)
121
+ })
122
+ })
123
+
124
+ describe('CaptureFieldSchema', () => {
125
+ it('accepts a minimal field with only key and label', () => {
126
+ expect(CaptureFieldSchema.safeParse({ key: 'name', label: 'Your Name' }).success).toBe(true)
127
+ })
128
+
129
+ it('accepts all optional fields populated', () => {
130
+ const result = CaptureFieldSchema.safeParse({
131
+ key: 'email',
132
+ label: 'Email Address',
133
+ type: 'email',
134
+ required: true
135
+ })
136
+ expect(result.success).toBe(true)
137
+ })
138
+
139
+ it('accepts type tel', () => {
140
+ expect(CaptureFieldSchema.safeParse({ key: 'phone', label: 'Phone', type: 'tel' }).success).toBe(true)
141
+ })
142
+
143
+ it('accepts type text', () => {
144
+ expect(CaptureFieldSchema.safeParse({ key: 'company', label: 'Company', type: 'text' }).success).toBe(true)
145
+ })
146
+
147
+ it('rejects an unknown type value', () => {
148
+ expect(CaptureFieldSchema.safeParse({ key: 'x', label: 'X', type: 'number' }).success).toBe(false)
149
+ })
150
+
151
+ it('rejects a missing key', () => {
152
+ expect(CaptureFieldSchema.safeParse({ label: 'Name' }).success).toBe(false)
153
+ })
154
+
155
+ it('rejects a missing label', () => {
156
+ expect(CaptureFieldSchema.safeParse({ key: 'name' }).success).toBe(false)
157
+ })
158
+
159
+ it('rejects unknown keys (.strict)', () => {
160
+ expect(CaptureFieldSchema.safeParse({ key: 'x', label: 'X', placeholder: 'hint' }).success).toBe(false)
161
+ })
162
+ })
163
+
164
+ describe('WebSocketSessionEndSchema', () => {
165
+ it('accepts { type: "session:end" }', () => {
166
+ expect(WebSocketSessionEndSchema.safeParse({ type: 'session:end' }).success).toBe(true)
167
+ })
168
+
169
+ it('rejects wrong type discriminant', () => {
170
+ expect(WebSocketSessionEndSchema.safeParse({ type: 'session:turn' }).success).toBe(false)
171
+ })
172
+
173
+ it('rejects unknown extra keys (.strict)', () => {
174
+ expect(WebSocketSessionEndSchema.safeParse({ type: 'session:end', extra: true }).success).toBe(false)
175
+ })
176
+ })
177
+
178
+ describe('PublicAgentChatInboundMessageSchema (discriminated union)', () => {
179
+ it('routes session:turn correctly', () => {
180
+ const result = PublicAgentChatInboundMessageSchema.safeParse({
181
+ type: 'session:turn',
182
+ input: 'Hello!'
183
+ })
184
+ expect(result.success).toBe(true)
185
+ if (result.success) expect(result.data.type).toBe('session:turn')
186
+ })
187
+
188
+ it('routes session:end correctly', () => {
189
+ const result = PublicAgentChatInboundMessageSchema.safeParse({ type: 'session:end' })
190
+ expect(result.success).toBe(true)
191
+ if (result.success) expect(result.data.type).toBe('session:end')
192
+ })
193
+
194
+ it('rejects an unknown message type', () => {
195
+ expect(PublicAgentChatInboundMessageSchema.safeParse({ type: 'session:delete' }).success).toBe(false)
196
+ })
197
+ })
198
+
199
+ describe('WebSocketSessionEndedEventSchema', () => {
200
+ it('accepts a valid session:ended event', () => {
201
+ const result = WebSocketSessionEndedEventSchema.safeParse({
202
+ type: 'session:ended',
203
+ sessionId: 'abc-123'
204
+ })
205
+ expect(result.success).toBe(true)
206
+ })
207
+
208
+ it('rejects missing sessionId', () => {
209
+ expect(WebSocketSessionEndedEventSchema.safeParse({ type: 'session:ended' }).success).toBe(false)
210
+ })
211
+
212
+ it('rejects wrong type discriminant', () => {
213
+ expect(WebSocketSessionEndedEventSchema.safeParse({ type: 'turn:complete', sessionId: 'x' }).success).toBe(false)
214
+ })
215
+ })
216
+
217
+ describe('PublicAgentChatGrantSchema', () => {
218
+ it('accepts a minimal grant (captureFields absent — treated as empty)', () => {
219
+ const result = PublicAgentChatGrantSchema.safeParse({
220
+ slug: 'voice-agent',
221
+ resourceId: 'resource-1',
222
+ mode: 'open',
223
+ requiresCode: false
224
+ })
225
+ expect(result.success).toBe(true)
226
+ })
227
+
228
+ it('accepts captureFields as an empty array (DB default)', () => {
229
+ const result = PublicAgentChatGrantSchema.safeParse({
230
+ slug: 'voice-agent',
231
+ resourceId: 'resource-1',
232
+ mode: 'open',
233
+ requiresCode: false,
234
+ captureFields: []
235
+ })
236
+ expect(result.success).toBe(true)
237
+ })
238
+
239
+ it('accepts captureFields with valid field definitions', () => {
240
+ const result = PublicAgentChatGrantSchema.safeParse({
241
+ slug: 'voice-agent',
242
+ resourceId: 'resource-1',
243
+ mode: 'open',
244
+ requiresCode: false,
245
+ captureFields: [
246
+ { key: 'name', label: 'Your Name', type: 'text', required: true },
247
+ { key: 'email', label: 'Email', type: 'email' }
248
+ ]
249
+ })
250
+ expect(result.success).toBe(true)
251
+ })
252
+
253
+ it('rejects captureFields containing an invalid field (unknown type)', () => {
254
+ const result = PublicAgentChatGrantSchema.safeParse({
255
+ slug: 'voice-agent',
256
+ resourceId: 'resource-1',
257
+ mode: 'open',
258
+ requiresCode: false,
259
+ captureFields: [{ key: 'age', label: 'Age', type: 'number' }]
260
+ })
261
+ expect(result.success).toBe(false)
262
+ })
109
263
  })
@@ -1,5 +1,84 @@
1
1
  import { z } from 'zod'
2
2
  import { UuidSchema } from '../../platform/utils/validation'
3
+ import { WebSocketSessionTurnSchema } from '../sessions/api-schemas'
4
+
5
+ // ============================================================================
6
+ // Capture Fields
7
+ // ============================================================================
8
+
9
+ /**
10
+ * A single intake field definition stored in agent_access_grants.capture_fields.
11
+ *
12
+ * - key: machine identifier (e.g. "name", "company")
13
+ * - label: display label shown to the visitor
14
+ * - type: input type hint; defaults to "text" when absent
15
+ * - required: whether the visitor must fill this field; defaults to false
16
+ */
17
+ export const CaptureFieldSchema = z
18
+ .object({
19
+ key: z.string().min(1).max(100),
20
+ label: z.string().min(1).max(200),
21
+ type: z.enum(['text', 'email', 'tel']).optional(),
22
+ required: z.boolean().optional()
23
+ })
24
+ .strict()
25
+
26
+ export type CaptureField = z.infer<typeof CaptureFieldSchema>
27
+
28
+ // ============================================================================
29
+ // WebSocket Inbound Messages (public agent-chat)
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Inbound WebSocket message: end the current session.
34
+ *
35
+ * Client sends: { type: "session:end" }
36
+ *
37
+ * The server validates the capability token, calls session.end(), and emits
38
+ * a "session:ended" outbound event confirming the transition.
39
+ */
40
+ export const WebSocketSessionEndSchema = z
41
+ .object({
42
+ type: z.literal('session:end')
43
+ })
44
+ .strict()
45
+
46
+ export type WebSocketSessionEndMessage = z.infer<typeof WebSocketSessionEndSchema>
47
+
48
+ /**
49
+ * Discriminated union of all inbound WebSocket messages for the public
50
+ * agent-chat endpoint. Add new inbound message schemas here.
51
+ *
52
+ * Note: WebSocketSessionTurnSchema is defined in the sessions domain and
53
+ * composed here so consumers have a single import point for the full union.
54
+ */
55
+ export const PublicAgentChatInboundMessageSchema = z.discriminatedUnion('type', [
56
+ WebSocketSessionTurnSchema,
57
+ WebSocketSessionEndSchema
58
+ ])
59
+
60
+ export type PublicAgentChatInboundMessage = z.infer<typeof PublicAgentChatInboundMessageSchema>
61
+
62
+ // ============================================================================
63
+ // Outbound event: session:ended
64
+ // ============================================================================
65
+
66
+ /**
67
+ * Outbound WebSocket event emitted by the server after a session:end request
68
+ * is processed and the session is successfully marked ended.
69
+ */
70
+ export const WebSocketSessionEndedEventSchema = z
71
+ .object({
72
+ type: z.literal('session:ended'),
73
+ sessionId: z.string()
74
+ })
75
+ .strict()
76
+
77
+ export type WebSocketSessionEndedEvent = z.infer<typeof WebSocketSessionEndedEventSchema>
78
+
79
+ // ============================================================================
80
+ // Path / Query Parameter Schemas
81
+ // ============================================================================
3
82
 
4
83
  export const PublicAgentChatSlugParamSchema = z
5
84
  .object({
@@ -54,6 +133,31 @@ export const PublicAgentChatBrandingSchema = z
54
133
  })
55
134
  .strict()
56
135
 
136
+ // ============================================================================
137
+ // Grant response schema
138
+ // ============================================================================
139
+
140
+ /**
141
+ * The public-facing grant shape returned by /authorize and /metadata endpoints.
142
+ *
143
+ * captureFields defaults to [] on the DB (NOT NULL); the schema field is
144
+ * .optional() for forward-compat, but callers should treat absence as [].
145
+ */
146
+ export const PublicAgentChatGrantSchema = z.object({
147
+ slug: z.string(),
148
+ resourceId: z.string(),
149
+ mode: z.string(),
150
+ requiresCode: z.boolean(),
151
+ branding: PublicAgentChatBrandingSchema.optional(),
152
+ captureFields: z.array(CaptureFieldSchema).optional()
153
+ })
154
+
155
+ export type PublicAgentChatGrant = z.infer<typeof PublicAgentChatGrantSchema>
156
+
157
+ // ============================================================================
158
+ // TypeScript Type Exports
159
+ // ============================================================================
160
+
57
161
  export type PublicAgentChatSlugParams = z.infer<typeof PublicAgentChatSlugParamSchema>
58
162
  export type PublicAgentChatSessionParams = z.infer<typeof PublicAgentChatSessionParamSchema>
59
163
  export type PublicAgentChatAuthorizeInput = z.infer<typeof PublicAgentChatAuthorizeSchema>
@@ -1,10 +1,20 @@
1
1
  export {
2
+ CaptureFieldSchema,
3
+ type CaptureField,
4
+ WebSocketSessionEndSchema,
5
+ type WebSocketSessionEndMessage,
6
+ PublicAgentChatInboundMessageSchema,
7
+ type PublicAgentChatInboundMessage,
8
+ WebSocketSessionEndedEventSchema,
9
+ type WebSocketSessionEndedEvent,
2
10
  PublicAgentChatSlugParamSchema,
3
11
  PublicAgentChatSessionParamSchema,
4
12
  PublicAgentChatAuthorizeSchema,
5
13
  PublicAgentChatCreateSessionSchema,
6
14
  PublicAgentChatCapabilityQuerySchema,
7
15
  PublicAgentChatBrandingSchema,
16
+ PublicAgentChatGrantSchema,
17
+ type PublicAgentChatGrant,
8
18
  type PublicAgentChatSlugParams,
9
19
  type PublicAgentChatSessionParams,
10
20
  type PublicAgentChatAuthorizeInput,
@@ -1,3 +1,3 @@
1
1
  export const VERSION = {
2
- CURRENT: '1.13.14'
2
+ CURRENT: '1.13.15'
3
3
  }