fix(server-*): added simple message queue for helping selecting consumer

Close #1387
This commit is contained in:
Neko Ayaka
2026-03-29 23:26:29 +08:00
parent 8946abdfac
commit a9c281c9f2
9 changed files with 718 additions and 45 deletions
+107 -14
View File
@@ -1,3 +1,4 @@
import type { Eventa } from '@moeru/eventa'
import type { AssistantMessage, CommonContentPart, Message, ToolMessage, UserMessage } from '@xsai/shared-chat'
import { defineEventa } from '@moeru/eventa'
@@ -394,6 +395,40 @@ export interface ModulePermissionError {
recoverable?: boolean
}
export type DeliveryMode = 'broadcast' | 'consumer' | 'consumer-group'
export type DeliverySelectionStrategy = 'first' | 'round-robin' | 'priority' | 'sticky'
export interface DeliveryConfig {
mode?: DeliveryMode
group?: string
required?: boolean
selection?: DeliverySelectionStrategy
stickyKey?: string
}
export interface ProtocolEventaMetadata {
delivery?: DeliveryConfig
}
export interface ProtocolEventaInvokeMetadata {
delivery?: Partial<DeliveryConfig>
}
export type ProtocolEventa<P = undefined>
= Eventa<P, ProtocolEventaMetadata, ProtocolEventaInvokeMetadata>
function defineProtocolEventa<P = undefined>(
id: string,
options?: {
inheritFrom?: ProtocolEventa<P>
metadata?: ProtocolEventaMetadata
invokeMetadata?: ProtocolEventaInvokeMetadata
},
): ProtocolEventa<P> {
return defineEventa<P, ProtocolEventaMetadata, ProtocolEventaInvokeMetadata>(id, options)
}
export type RouteTargetExpression
= | { type: 'and', all: RouteTargetExpression[] }
| { type: 'or', any: RouteTargetExpression[] }
@@ -408,6 +443,7 @@ export type RouteTargetExpression
export interface RouteConfig {
destinations?: Array<string | RouteTargetExpression>
bypass?: boolean
delivery?: DeliveryConfig
}
export enum MessageHeartbeatKind {
@@ -892,6 +928,19 @@ interface ModuleConfigureEvent<C = undefined> {
config: C | Record<string, unknown>
}
interface ModuleConsumerRegisterEvent {
event: string
mode?: Exclude<DeliveryMode, 'broadcast'>
group?: string
priority?: number
}
interface ModuleConsumerUnregisterEvent {
event: string
mode?: Exclude<DeliveryMode, 'broadcast'>
group?: string
}
interface UiConfigureEvent<C = undefined> {
moduleName: string
moduleIndex?: number
@@ -1047,26 +1096,62 @@ export const moduleContributeCapabilityConfigurationCommitStatus = defineEventa<
export const moduleContributeCapabilityConfigurationConfigured = defineEventa<ModuleContributeCapabilityConfigurationConfiguredEvent>('module:contribute:capability:configuration:configured')
export const moduleContributeCapabilityActivated = defineEventa<ModuleContributeCapabilityActivatedEvent>('module:contribute:capability:activated')
export const moduleStatusChange = defineEventa<ModuleStatusChangeEvent>('module:status:change')
export const moduleStatusChange = defineProtocolEventa<ModuleStatusChangeEvent>('module:status:change')
export const moduleConfigure = defineEventa<ModuleConfigureEvent>('module:configure')
export const moduleConfigure = defineProtocolEventa<ModuleConfigureEvent>('module:configure')
export const moduleConsumerRegister = defineProtocolEventa<ModuleConsumerRegisterEvent>('module:consumer:register')
export const moduleConsumerUnregister = defineProtocolEventa<ModuleConsumerUnregisterEvent>('module:consumer:unregister')
export const uiConfigure = defineEventa<UiConfigureEvent>('ui:configure')
export const uiConfigure = defineProtocolEventa<UiConfigureEvent>('ui:configure')
export const inputText = defineEventa<WebSocketEventInputText>('input:text')
export const inputTextVoice = defineEventa<WebSocketEventInputTextVoice>('input:text:voice')
export const inputVoice = defineEventa<WebSocketEventInputVoice>('input:voice')
export const inputText = defineProtocolEventa<WebSocketEventInputText>('input:text', {
metadata: {
delivery: {
mode: 'consumer-group',
group: 'chat-ingestion',
selection: 'first',
},
},
})
export const inputTextVoice = defineProtocolEventa<WebSocketEventInputTextVoice>('input:text:voice', {
metadata: {
delivery: {
mode: 'consumer-group',
group: 'chat-ingestion',
selection: 'first',
},
},
})
export const inputVoice = defineProtocolEventa<WebSocketEventInputVoice>('input:voice', {
metadata: {
delivery: {
mode: 'consumer-group',
group: 'chat-ingestion',
selection: 'first',
},
},
})
export const outputGenAiChatToolCall = defineEventa<OutputGenAiChatToolCallEvent>('output:gen-ai:chat:tool-call')
export const outputGenAiChatMessage = defineEventa<OutputGenAiChatMessageEvent>('output:gen-ai:chat:message')
export const outputGenAiChatComplete = defineEventa<OutputGenAiChatCompleteEvent>('output:gen-ai:chat:complete')
export const outputGenAiChatToolCall = defineProtocolEventa<OutputGenAiChatToolCallEvent>('output:gen-ai:chat:tool-call')
export const outputGenAiChatMessage = defineProtocolEventa<OutputGenAiChatMessageEvent>('output:gen-ai:chat:message')
export const outputGenAiChatComplete = defineProtocolEventa<OutputGenAiChatCompleteEvent>('output:gen-ai:chat:complete')
export const sparkNotify = defineEventa<SparkNotifyEvent>('spark:notify')
export const sparkEmit = defineEventa<SparkEmitEvent>('spark:emit')
export const sparkCommand = defineEventa<SparkCommandEvent>('spark:command')
export const sparkNotify = defineProtocolEventa<SparkNotifyEvent>('spark:notify')
export const sparkEmit = defineProtocolEventa<SparkEmitEvent>('spark:emit')
export const sparkCommand = defineProtocolEventa<SparkCommandEvent>('spark:command')
export const transportConnectionHeartbeat = defineEventa<TransportConnectionHeartbeatEvent>('transport:connection:heartbeat')
export const contextUpdate = defineEventa<ContextUpdateEvent>('context:update')
export const transportConnectionHeartbeat = defineProtocolEventa<TransportConnectionHeartbeatEvent>('transport:connection:heartbeat')
export const contextUpdate = defineProtocolEventa<ContextUpdateEvent>('context:update')
export const protocolEventMetadataByType = {
[inputText.id]: inputText.metadata,
[inputTextVoice.id]: inputTextVoice.metadata,
[inputVoice.id]: inputVoice.metadata,
} satisfies Partial<Record<keyof ProtocolEvents, ProtocolEventaMetadata | undefined>>
export function getProtocolEventMetadata(eventType: keyof ProtocolEvents | string) {
return protocolEventMetadataByType[eventType as keyof typeof protocolEventMetadataByType]
}
// Thanks to:
//
@@ -1202,6 +1287,14 @@ export interface ProtocolEvents<C = undefined> {
* Push configuration down to module (host → module).
*/
'module:configure': ModuleConfigureEvent<C>
/**
* Register the current module instance as a consumer for an event or event group.
*/
'module:consumer:register': ModuleConsumerRegisterEvent
/**
* Unregister the current module instance from an event consumer registration.
*/
'module:consumer:unregister': ModuleConsumerUnregisterEvent
'ui:configure': UiConfigureEvent<C>