@csntgao/uni-base 0.1.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.
Files changed (90) hide show
  1. package/.prettierignore +4 -0
  2. package/.prettierrc.json +6 -0
  3. package/AGENTS.md +519 -0
  4. package/CLAUDE.md +512 -0
  5. package/README.md +734 -0
  6. package/docs//346/225/260/345/255/227/345/221/230/345/267/245/345/256/214/346/225/264/346/212/275/345/261/211/346/216/245/345/205/245/350/257/264/346/230/216.md +110 -0
  7. package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/215/225/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +79 -0
  8. package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/244/232/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +93 -0
  9. package/docs//350/264/246/346/210/267/344/270/216/347/231/273/345/275/225/347/273/204/344/273/266/345/212/250/346/200/201/346/216/245/345/205/245/350/257/264/346/230/216.md +201 -0
  10. package/eslint.config.js +30 -0
  11. package/examples/vanilla/index.html +33 -0
  12. package/examples/vanilla/main.js +247 -0
  13. package/examples/vanilla/package.json +15 -0
  14. package/examples/vanilla/public/runtime-config.js +10 -0
  15. package/examples/vanilla/style.css +99 -0
  16. package/package.json +31 -0
  17. package/packages/core/package.json +30 -0
  18. package/packages/core/src/client.ts +1132 -0
  19. package/packages/core/src/customer-support-client.ts +825 -0
  20. package/packages/core/src/customer-support-types.ts +123 -0
  21. package/packages/core/src/index.ts +79 -0
  22. package/packages/core/src/notification-center-client.ts +462 -0
  23. package/packages/core/src/notification-center-types.ts +154 -0
  24. package/packages/core/src/protocol.ts +237 -0
  25. package/packages/core/src/types.ts +170 -0
  26. package/packages/core/tests/client.test.ts +760 -0
  27. package/packages/core/tests/customer-support-client.test.ts +576 -0
  28. package/packages/core/tests/fake-websocket.ts +55 -0
  29. package/packages/core/tests/notification-center-client.test.ts +467 -0
  30. package/packages/core/tests/protocol.test.ts +124 -0
  31. package/packages/core/tsconfig.json +7 -0
  32. package/packages/core/tsup.config.ts +11 -0
  33. package/packages/web-components/package.json +35 -0
  34. package/packages/web-components/scripts/verify-runtime-build.mjs +111 -0
  35. package/packages/web-components/src/account-context.ts +1540 -0
  36. package/packages/web-components/src/application-switcher-transport.ts +243 -0
  37. package/packages/web-components/src/application-switcher.ts +1103 -0
  38. package/packages/web-components/src/chat.ts +1754 -0
  39. package/packages/web-components/src/customer-support-chat.ts +862 -0
  40. package/packages/web-components/src/customer-support-transport.ts +269 -0
  41. package/packages/web-components/src/index.ts +256 -0
  42. package/packages/web-components/src/notification-center.ts +1066 -0
  43. package/packages/web-components/src/notification-popover.ts +615 -0
  44. package/packages/web-components/src/organization-multi-employee-selector.ts +1564 -0
  45. package/packages/web-components/src/organization-onboarding-transport.ts +54 -0
  46. package/packages/web-components/src/organization-onboarding.ts +1216 -0
  47. package/packages/web-components/src/organization-selector.ts +534 -0
  48. package/packages/web-components/src/organization-single-employee-selector.ts +114 -0
  49. package/packages/web-components/src/prototype-icons.ts +242 -0
  50. package/packages/web-components/src/safe-image-url.ts +29 -0
  51. package/packages/web-components/src/safe-markdown.ts +192 -0
  52. package/packages/web-components/src/uniplat-base-account-context-transport.ts +625 -0
  53. package/packages/web-components/src/uniplat-base-application-switcher-transport.ts +418 -0
  54. package/packages/web-components/src/uniplat-base-application-ticket.ts +254 -0
  55. package/packages/web-components/src/uniplat-base-identity-ticket.ts +202 -0
  56. package/packages/web-components/src/uniplat-base-notification-center-transport.ts +378 -0
  57. package/packages/web-components/src/uniplat-base-organization-multi-employee-selector-transport.ts +394 -0
  58. package/packages/web-components/src/uniplat-base-organization-onboarding-transport.ts +183 -0
  59. package/packages/web-components/src/uniplat-base-organization-single-employee-selector-transport.ts +14 -0
  60. package/packages/web-components/src/user-login-transport.ts +563 -0
  61. package/packages/web-components/src/user-login.ts +2027 -0
  62. package/packages/web-components/src/user-menu.ts +880 -0
  63. package/packages/web-components/tests/account-context.test.ts +798 -0
  64. package/packages/web-components/tests/application-switcher.test.ts +562 -0
  65. package/packages/web-components/tests/chat.test.ts +1130 -0
  66. package/packages/web-components/tests/customer-support-chat.test.ts +428 -0
  67. package/packages/web-components/tests/customer-support-transport.test.ts +79 -0
  68. package/packages/web-components/tests/notification-center-transport.test.ts +271 -0
  69. package/packages/web-components/tests/notification-center.test.ts +429 -0
  70. package/packages/web-components/tests/notification-popover.test.ts +283 -0
  71. package/packages/web-components/tests/organization-multi-employee-selector-transport.test.ts +332 -0
  72. package/packages/web-components/tests/organization-multi-employee-selector.test.ts +225 -0
  73. package/packages/web-components/tests/organization-onboarding.test.ts +260 -0
  74. package/packages/web-components/tests/organization-selector.test.ts +127 -0
  75. package/packages/web-components/tests/organization-single-employee-selector.test.ts +145 -0
  76. package/packages/web-components/tests/uniplat-base-account-context-transport.test.ts +496 -0
  77. package/packages/web-components/tests/uniplat-base-application-switcher-transport.test.ts +598 -0
  78. package/packages/web-components/tests/uniplat-base-application-ticket.test.ts +233 -0
  79. package/packages/web-components/tests/uniplat-base-identity-ticket.test.ts +226 -0
  80. package/packages/web-components/tests/uniplat-base-organization-onboarding-transport.test.ts +227 -0
  81. package/packages/web-components/tests/user-login-transport.test.ts +443 -0
  82. package/packages/web-components/tests/user-login.test.ts +626 -0
  83. package/packages/web-components/tests/user-menu.test.ts +271 -0
  84. package/packages/web-components/tsconfig.json +8 -0
  85. package/packages/web-components/tsup.config.ts +12 -0
  86. package/packages/web-components/tsup.runtime.config.ts +37 -0
  87. package/pnpm-workspace.yaml +6 -0
  88. package/tsconfig.base.json +21 -0
  89. package/vitest.config.ts +20 -0
  90. package//344/272/244/346/216/245/346/226/207/346/241/243.md +254 -0
@@ -0,0 +1,154 @@
1
+ export type NotificationReadStatus = 'unread' | 'read'
2
+ export type NotificationContentType = 'text' | 'markdown'
3
+
4
+ export interface NotificationSource {
5
+ type: string
6
+ code: string
7
+ id: string
8
+ name: string
9
+ avatarUrl: string
10
+ }
11
+
12
+ export interface NotificationResource {
13
+ type: string
14
+ id: string
15
+ title: string
16
+ aeCode: string
17
+ }
18
+
19
+ export interface NotificationAttachment {
20
+ fileId: string
21
+ versionId: string
22
+ name: string
23
+ sizeBytes: number
24
+ mimeType: string
25
+ }
26
+
27
+ export interface NotificationDto {
28
+ id: string
29
+ type: string
30
+ // 后端 notification_type_name 投影;缺省为空串,展示时回退到 type。
31
+ typeName: string
32
+ title: string
33
+ summary: string
34
+ content: string
35
+ contentType: NotificationContentType
36
+ priority: string
37
+ source: NotificationSource | null
38
+ resources: readonly NotificationResource[]
39
+ attachments: readonly NotificationAttachment[]
40
+ readStatus: NotificationReadStatus
41
+ readAt: string | null
42
+ archivedAt: string | null
43
+ createdAt: string
44
+ expiresAt: string | null
45
+ }
46
+
47
+ /** 分类目录条目:稳定 code 加后端字典给出的展示名(可能为空)。 */
48
+ export interface NotificationTypeSummary {
49
+ type: string
50
+ name: string
51
+ }
52
+
53
+ export interface NotificationInboxQuery {
54
+ pageSize: number
55
+ beforeId?: string | null
56
+ // 请求分类目录;后端按收件人可见范围统计,忽略类型筛选与分页游标。
57
+ includeTypeFacets?: boolean
58
+ notificationTypes?: readonly string[]
59
+ readStatus?: 'all' | 'unread' | 'read'
60
+ includeArchived?: boolean
61
+ // Empty means the current principal's notifications across all applications.
62
+ applicationName: string
63
+ }
64
+
65
+ export interface NotificationInboxResult {
66
+ items: readonly NotificationDto[]
67
+ hasMore: boolean
68
+ nextId: string | null
69
+ // 仅在 includeTypeFacets 请求且后端支持时出现;缺省时客户端回退为累积已加载分类。
70
+ types?: readonly NotificationTypeSummary[]
71
+ }
72
+
73
+ export interface NotificationMarkReadResult {
74
+ unreadCount: number
75
+ readAt: string | null
76
+ }
77
+
78
+ export interface NotificationMarkAllReadResult {
79
+ markedCount: number
80
+ unreadCount: number
81
+ }
82
+
83
+ export type NotificationRealtimeChange =
84
+ | { kind: 'created'; notification: NotificationDto; unreadCount?: number }
85
+ | {
86
+ kind: 'read'
87
+ notificationIds: readonly string[]
88
+ unreadCount?: number
89
+ readAt?: string | null
90
+ }
91
+ | { kind: 'archived'; notificationIds: readonly string[]; unreadCount?: number }
92
+
93
+ export interface NotificationCenterTransport {
94
+ listInbox(query: NotificationInboxQuery): Promise<NotificationInboxResult>
95
+ getUnreadCount(params: { applicationName: string }): Promise<{ unreadCount: number }>
96
+ markRead(params: {
97
+ notificationId: string
98
+ applicationName: string
99
+ }): Promise<NotificationMarkReadResult>
100
+ markAllRead(params: {
101
+ beforeId?: string | null
102
+ notificationTypes?: readonly string[]
103
+ applicationName: string
104
+ }): Promise<NotificationMarkAllReadResult>
105
+ downloadAttachment(params: { fileId: string; name?: string }): Promise<Blob>
106
+ archive?(params: {
107
+ notificationId: string
108
+ applicationName: string
109
+ }): Promise<{ unreadCount: number }>
110
+ // Optional: map a raw realtime notification event handed in by the host to a
111
+ // stable DTO change so the inbox can update in place. When absent — or when the
112
+ // event cannot be mapped — the client falls back to a full HTTP refresh.
113
+ parseRealtimeEvent?(event: unknown): NotificationRealtimeChange | null
114
+ }
115
+
116
+ export interface NotificationCenterClientOptions {
117
+ transport: NotificationCenterTransport
118
+ // Empty means the current principal's notifications across all applications.
119
+ applicationName: string
120
+ pageSize?: number
121
+ // Optional inbox projection used by compact consumers such as the unread
122
+ // notification popover. The full notification center defaults to `all`.
123
+ readStatus?: 'all' | 'unread' | 'read'
124
+ // Optional category codes pinned to the front of the tab order when they exist
125
+ // in the data. Defaults to empty: order then follows the backend catalog, and
126
+ // the SDK privileges no code of its own.
127
+ preferredTypeOrder?: readonly string[]
128
+ }
129
+
130
+ export type NotificationCenterErrorCode =
131
+ | 'INVALID_CONFIGURATION'
132
+ | 'INBOX_LOAD_FAILED'
133
+ | 'MARK_READ_FAILED'
134
+ | 'DOWNLOAD_FAILED'
135
+ | 'CLIENT_DESTROYED'
136
+
137
+ export interface NotificationCenterErrorDetail {
138
+ code: NotificationCenterErrorCode
139
+ message: string
140
+ recoverable: boolean
141
+ }
142
+
143
+ export interface NotificationCenterClientEventMap {
144
+ ready: Record<string, never>
145
+ inbox: NotificationInboxResult
146
+ 'types-change': { types: readonly NotificationTypeSummary[] }
147
+ 'unread-count-change': { unreadCount: number }
148
+ 'notification-read': { notificationId: string }
149
+ error: NotificationCenterErrorDetail
150
+ }
151
+
152
+ export type NotificationCenterClientListener<K extends keyof NotificationCenterClientEventMap> = (
153
+ detail: NotificationCenterClientEventMap[K],
154
+ ) => void
@@ -0,0 +1,237 @@
1
+ import type { AiEmployeeMessage, AiEmployeeMessageRole } from './types'
2
+
3
+ type JsonRecord = Record<string, unknown>
4
+
5
+ export interface AuthenticateFrame {
6
+ type: 'authenticate'
7
+ protocol_version: 1
8
+ access_token: string
9
+ requested_channels: ['ai_employee']
10
+ cursors: Record<string, never>
11
+ }
12
+
13
+ export interface SessionSubscriptionFrame {
14
+ action: 'subscribe' | 'unsubscribe'
15
+ channel: 'ai_employee'
16
+ resource_type: 'session'
17
+ resource_id: string
18
+ replay_history?: boolean
19
+ }
20
+
21
+ export interface PingFrame {
22
+ action: 'ping'
23
+ timestamp: string
24
+ }
25
+
26
+ export interface ParsedSessionMessage {
27
+ messages: AiEmployeeMessage[]
28
+ fallbackResult?: AiEmployeeMessage
29
+ processing?: boolean
30
+ }
31
+
32
+ export type ParsedAiEmployeeEvent =
33
+ | { kind: 'session-message'; sessionId: string; result: ParsedSessionMessage }
34
+ | { kind: 'session-title'; sessionId: string; title: string }
35
+ | { kind: 'ignored' }
36
+
37
+ export function createAuthenticateFrame(accessToken: string): AuthenticateFrame {
38
+ return {
39
+ type: 'authenticate',
40
+ protocol_version: 1,
41
+ access_token: accessToken,
42
+ requested_channels: ['ai_employee'],
43
+ cursors: {},
44
+ }
45
+ }
46
+
47
+ export function createSubscriptionFrame(
48
+ sessionId: string,
49
+ action: 'subscribe' | 'unsubscribe' = 'subscribe',
50
+ replayHistory = false,
51
+ ): SessionSubscriptionFrame {
52
+ const frame: SessionSubscriptionFrame = {
53
+ action,
54
+ channel: 'ai_employee',
55
+ resource_type: 'session',
56
+ resource_id: sessionId,
57
+ }
58
+ if (action === 'subscribe') frame.replay_history = replayHistory
59
+ return frame
60
+ }
61
+
62
+ export function createPingFrame(now = new Date()): PingFrame {
63
+ return { action: 'ping', timestamp: now.toISOString() }
64
+ }
65
+
66
+ export function parseJsonRecord(raw: string): JsonRecord | null {
67
+ try {
68
+ return asRecord(JSON.parse(raw))
69
+ } catch {
70
+ return null
71
+ }
72
+ }
73
+
74
+ export function parseAiEmployeeEvent(raw: unknown): ParsedAiEmployeeEvent {
75
+ const event = asRecord(raw)
76
+ const type = stringValue(event.type)
77
+ const sessionId = stringValue(event.session_id).trim()
78
+
79
+ if (type === 'session_title_updated') {
80
+ const title = stringValue(asRecord(event.payload).title).trim()
81
+ return sessionId && title ? { kind: 'session-title', sessionId, title } : { kind: 'ignored' }
82
+ }
83
+
84
+ if (type !== 'session_message' || !sessionId) return { kind: 'ignored' }
85
+ return {
86
+ kind: 'session-message',
87
+ sessionId,
88
+ result: parseSessionPayload(asRecord(event.payload), sessionId),
89
+ }
90
+ }
91
+
92
+ function parseSessionPayload(payload: JsonRecord, sessionId: string): ParsedSessionMessage {
93
+ const messages: AiEmployeeMessage[] = []
94
+ const patches = Array.isArray(payload.JsonPatch) ? payload.JsonPatch : []
95
+
96
+ for (const rawPatch of patches) {
97
+ const patch = asRecord(rawPatch)
98
+ const value = asRecord(patch.value)
99
+ const parsed = messageFromEntry(value, sessionId, patch)
100
+ if (parsed) messages.push(parsed)
101
+ }
102
+
103
+ const result = stringValue(payload.result).trim()
104
+ const fallbackResult = result
105
+ ? createMessage(
106
+ sessionId,
107
+ 'assistant',
108
+ result,
109
+ `result:${sessionId}:${hashText(result)}`,
110
+ payload,
111
+ )
112
+ : undefined
113
+
114
+ if (payload.error) {
115
+ messages.push(
116
+ createMessage(
117
+ sessionId,
118
+ 'error',
119
+ 'AI 回复出现异常,请稍后重试。',
120
+ `error:${sessionId}:${hashText(stableString(payload))}`,
121
+ payload,
122
+ ),
123
+ )
124
+ }
125
+
126
+ let processing: boolean | undefined
127
+ if (payload.Ready === true) processing = true
128
+ if (payload.finished === true || payload.stopped === true) processing = false
129
+ return { messages, fallbackResult, processing }
130
+ }
131
+
132
+ function messageFromEntry(
133
+ entry: JsonRecord,
134
+ sessionId: string,
135
+ patch: JsonRecord,
136
+ ): AiEmployeeMessage | null {
137
+ const entryTypeValue = entry.entry_type
138
+ const entryType =
139
+ typeof entryTypeValue === 'string' ? entryTypeValue : stringValue(asRecord(entryTypeValue).type)
140
+
141
+ let role: AiEmployeeMessageRole
142
+ switch (entryType) {
143
+ case 'assistant_message':
144
+ case 'result':
145
+ role = 'assistant'
146
+ break
147
+ case 'user_message':
148
+ role = 'user'
149
+ break
150
+ case 'system_message':
151
+ case 'system_push':
152
+ role = 'system'
153
+ break
154
+ case 'error':
155
+ case 'error_message':
156
+ role = 'error'
157
+ break
158
+ default:
159
+ return null
160
+ }
161
+
162
+ const content = role === 'error' ? 'AI 回复出现异常,请稍后重试。' : extractText(entry.content)
163
+ if (!content) return null
164
+
165
+ const messageId = stringValue(entry.message_id)
166
+ const webSocketMessageId = stringValue(entry.ws_message_id)
167
+ const explicitId =
168
+ role === 'user'
169
+ ? messageId || webSocketMessageId || stringValue(entry.id)
170
+ : webSocketMessageId || messageId || stringValue(entry.id)
171
+ const fingerprint = stableString({ entry, path: patch.path, op: patch.op })
172
+ const id = explicitId || `realtime:${sessionId}:${hashText(fingerprint)}`
173
+ return createMessage(sessionId, role, content, id, entry)
174
+ }
175
+
176
+ function createMessage(
177
+ sessionId: string,
178
+ role: AiEmployeeMessageRole,
179
+ content: string,
180
+ id: string,
181
+ source: JsonRecord,
182
+ ): AiEmployeeMessage {
183
+ const createdAt =
184
+ stringValue(source.timestamp) ||
185
+ stringValue(source.create_time) ||
186
+ stringValue(source.created_at) ||
187
+ new Date().toISOString()
188
+ return { id, sessionId, role, content, createdAt }
189
+ }
190
+
191
+ function extractText(raw: unknown): string {
192
+ if (typeof raw !== 'string') return ''
193
+ const value = raw.trim()
194
+ if (!value) return ''
195
+
196
+ try {
197
+ const parsed: unknown = JSON.parse(value)
198
+ if (typeof parsed === 'string') return parsed.trim()
199
+ const record = asRecord(parsed)
200
+ for (const key of ['text', 'result', 'message', 'content']) {
201
+ const candidate = stringValue(record[key]).trim()
202
+ if (candidate) return candidate
203
+ }
204
+ } catch {
205
+ // Plain text and the simple user-message XML format are handled below.
206
+ }
207
+
208
+ const messageMatch = value.match(/<message>([\s\S]*?)<\/message>/i)
209
+ return (messageMatch?.[1] ?? value).trim()
210
+ }
211
+
212
+ function asRecord(value: unknown): JsonRecord {
213
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
214
+ ? (value as JsonRecord)
215
+ : {}
216
+ }
217
+
218
+ function stringValue(value: unknown): string {
219
+ return typeof value === 'string' ? value : ''
220
+ }
221
+
222
+ function stableString(value: unknown): string {
223
+ try {
224
+ return JSON.stringify(value) ?? ''
225
+ } catch {
226
+ return ''
227
+ }
228
+ }
229
+
230
+ export function hashText(value: string): string {
231
+ let hash = 2_166_136_261
232
+ for (let index = 0; index < value.length; index += 1) {
233
+ hash ^= value.charCodeAt(index)
234
+ hash = Math.imul(hash, 16_777_619)
235
+ }
236
+ return (hash >>> 0).toString(36)
237
+ }
@@ -0,0 +1,170 @@
1
+ export type AiEmployeeMessageRole = 'user' | 'assistant' | 'system' | 'error'
2
+
3
+ export interface AiEmployeeMessage {
4
+ id: string
5
+ sessionId: string
6
+ role: AiEmployeeMessageRole
7
+ content: string
8
+ createdAt: string
9
+ }
10
+
11
+ export interface AiEmployeeSessionSummary {
12
+ sessionId: string
13
+ title: string
14
+ latestMessage: string
15
+ latestMessageId?: string
16
+ updatedAt: string
17
+ unreadCount: number
18
+ }
19
+
20
+ export interface AiEmployeeAttachmentReference {
21
+ transactionId: string
22
+ fspath: string
23
+ }
24
+
25
+ export interface AiEmployeeFirstMessageOptions {
26
+ attachments?: readonly AiEmployeeAttachmentReference[]
27
+ trustedContext?: Readonly<Record<string, unknown>> | null
28
+ }
29
+
30
+ export interface AiEmployeeFirstMessageRequest {
31
+ requestId: string
32
+ aeCode: string
33
+ agentCode: string
34
+ content: string
35
+ attachments: readonly AiEmployeeAttachmentReference[]
36
+ trustedContext: Readonly<Record<string, unknown>> | null
37
+ }
38
+
39
+ export interface AiEmployeeFirstMessageResult {
40
+ sessionId: string
41
+ messageId: string
42
+ executionId: string
43
+ status: 'running' | 'queued'
44
+ startedAt: string
45
+ aeCode: string
46
+ agentCode: string
47
+ conversationType: string
48
+ title?: string
49
+ createdAt?: string
50
+ }
51
+
52
+ export interface AiEmployeeFirstMessageRequestState {
53
+ fingerprint: string
54
+ requestId: string
55
+ }
56
+
57
+ export interface AiEmployeeFirstMessageRequestStore {
58
+ load(
59
+ scope: string,
60
+ ): AiEmployeeFirstMessageRequestState | null | Promise<AiEmployeeFirstMessageRequestState | null>
61
+ save(scope: string, state: AiEmployeeFirstMessageRequestState): void | Promise<void>
62
+ clear(scope: string): void | Promise<void>
63
+ }
64
+
65
+ export interface AiEmployeeTransport {
66
+ createSessionWithFirstMessage(
67
+ request: AiEmployeeFirstMessageRequest,
68
+ ): Promise<AiEmployeeFirstMessageResult>
69
+ listSessions?(): Promise<AiEmployeeSessionSummary[]>
70
+ markSessionRead?(sessionId: string, latestMessageId: string): Promise<void>
71
+ listMessages(sessionId: string): Promise<AiEmployeeMessage[]>
72
+ sendMessage(sessionId: string, content: string): Promise<void>
73
+ getAccessToken(): Promise<string>
74
+ }
75
+
76
+ export type AiEmployeeTransportErrorCode =
77
+ | 'UNAUTHORIZED'
78
+ | 'ACCESS_DENIED'
79
+ | 'AI_EMPLOYEE_NOT_AVAILABLE'
80
+ | 'APPLICATION_NOT_SUPPORTED'
81
+ | 'ORGANIZATION_CONTEXT_REQUIRED'
82
+ | 'INVALID_ARGUMENT'
83
+ | 'IDEMPOTENCY_CONFLICT'
84
+ | 'REALTIME_UNAVAILABLE'
85
+ | 'TOO_MANY_REQUESTS'
86
+
87
+ export type AiEmployeeConnectionState =
88
+ | 'idle'
89
+ | 'connecting'
90
+ | 'authenticating'
91
+ | 'subscribing'
92
+ | 'connected'
93
+ | 'reconnecting'
94
+ | 'disconnected'
95
+ | 'destroyed'
96
+
97
+ export type AiEmployeeErrorCode =
98
+ | 'INVALID_CONFIGURATION'
99
+ | 'SESSION_UNAVAILABLE'
100
+ | 'HISTORY_LOAD_FAILED'
101
+ | 'TOKEN_UNAVAILABLE'
102
+ | 'WEBSOCKET_UNAVAILABLE'
103
+ | 'WEBSOCKET_ERROR'
104
+ | 'AUTHENTICATION_FAILED'
105
+ | 'ORGANIZATION_CONTEXT_REQUIRED'
106
+ | 'REALTIME_UNAVAILABLE'
107
+ | 'CHANNEL_UNAVAILABLE'
108
+ | 'SESSION_NOT_FOUND'
109
+ | 'PROTOCOL_ERROR'
110
+ | 'SEND_FAILED'
111
+ | 'FIRST_MESSAGE_CONFLICT'
112
+ | 'RATE_LIMITED'
113
+ | 'SEND_IN_PROGRESS'
114
+ | 'EMPTY_MESSAGE'
115
+ | 'CLIENT_DESTROYED'
116
+
117
+ export interface AiEmployeeClientErrorDetail {
118
+ code: AiEmployeeErrorCode
119
+ message: string
120
+ recoverable: boolean
121
+ }
122
+
123
+ export interface AiEmployeeClientEventMap {
124
+ ready: { sessionId: string }
125
+ history: { sessionId: string; messages: readonly AiEmployeeMessage[] }
126
+ message: {
127
+ sessionId: string
128
+ message: AiEmployeeMessage
129
+ source: 'local' | 'realtime'
130
+ }
131
+ title: { sessionId: string; title: string }
132
+ 'connection-state-change': {
133
+ previous: AiEmployeeConnectionState
134
+ current: AiEmployeeConnectionState
135
+ }
136
+ 'processing-state-change': { processing: boolean }
137
+ error: AiEmployeeClientErrorDetail
138
+ }
139
+
140
+ export interface AiEmployeeReconnectOptions {
141
+ initialDelayMs: number
142
+ maximumDelayMs: number
143
+ factor: number
144
+ jitter: number
145
+ }
146
+
147
+ export type AiEmployeeWebSocketFactory = (url: string) => WebSocket
148
+
149
+ export interface AiEmployeeClientOptions {
150
+ realtimeUrl: string
151
+ aeCode: string
152
+ agentCode: string
153
+ transport: AiEmployeeTransport
154
+ sessionId?: string
155
+ reconnect?: Partial<AiEmployeeReconnectOptions>
156
+ webSocketFactory?: AiEmployeeWebSocketFactory
157
+ requestIdFactory?: () => string
158
+ firstMessageRequestStore?: AiEmployeeFirstMessageRequestStore | null
159
+ /** Primarily useful for deterministic tests. Production heartbeats are never faster than 5s. */
160
+ minimumHeartbeatMs?: number
161
+ }
162
+
163
+ export interface AiEmployeeClientStartResult {
164
+ sessionId: string | null
165
+ messages: readonly AiEmployeeMessage[]
166
+ }
167
+
168
+ export type AiEmployeeClientListener<K extends keyof AiEmployeeClientEventMap> = (
169
+ detail: AiEmployeeClientEventMap[K],
170
+ ) => void