@elevasis/core 0.51.0 → 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,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
|
-
|
|
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,
|