feat(protocol): add shared WebSocket contract package

This commit is contained in:
2026-04-08 18:15:31 +02:00
parent ffab815f6b
commit 4fd27db11e
10 changed files with 286 additions and 26 deletions

View File

@@ -0,0 +1,52 @@
const PROTOCOL_PACKAGE_NAME = '@vela/protocol';
const SESSION_STATES = Object.freeze(['idle', 'listening', 'thinking', 'speaking']);
const CLIENT_EVENT_TYPES = Object.freeze([
'session.start',
'input_audio.append',
'input_audio.commit',
'response.cancel'
]);
const SERVER_EVENT_TYPES = Object.freeze([
'session.ready',
'session.state',
'transcript.partial',
'transcript.final',
'response.text.delta',
'response.completed',
'error'
]);
function createMessageEnvelope(type, payload) {
return { type, payload };
}
function isMessageEnvelope(value) {
return Boolean(
value &&
typeof value === 'object' &&
typeof value.type === 'string' &&
'payload' in value
);
}
function isClientEventType(type) {
return CLIENT_EVENT_TYPES.includes(type);
}
function isServerEventType(type) {
return SERVER_EVENT_TYPES.includes(type);
}
module.exports = {
PROTOCOL_PACKAGE_NAME,
SESSION_STATES,
CLIENT_EVENT_TYPES,
SERVER_EVENT_TYPES,
createMessageEnvelope,
isMessageEnvelope,
isClientEventType,
isServerEventType
};

68
apps/vela-protocol/src/index.d.ts vendored Normal file
View File

@@ -0,0 +1,68 @@
export type SessionState = 'idle' | 'listening' | 'thinking' | 'speaking';
export type MessageEnvelope<TType extends string, TPayload> = {
type: TType;
payload: TPayload;
};
export type ClientEventPayloads = {
'session.start': Record<string, never>;
'input_audio.append': {
chunk: string;
};
'input_audio.commit': Record<string, never>;
'response.cancel': Record<string, never>;
};
export type ServerEventPayloads = {
'session.ready': {
sessionId: string;
};
'session.state': {
value: SessionState;
};
'transcript.partial': {
text: string;
};
'transcript.final': {
text: string;
};
'response.text.delta': {
text: string;
};
'response.completed': Record<string, never>;
'error': {
code: string;
message: string;
retryable?: boolean;
};
};
export type ClientEventType = keyof ClientEventPayloads;
export type ServerEventType = keyof ServerEventPayloads;
export type ClientEvent = {
[Type in ClientEventType]: MessageEnvelope<Type, ClientEventPayloads[Type]>;
}[ClientEventType];
export type ServerEvent = {
[Type in ServerEventType]: MessageEnvelope<Type, ServerEventPayloads[Type]>;
}[ServerEventType];
export const PROTOCOL_PACKAGE_NAME: '@vela/protocol';
export const SESSION_STATES: readonly SessionState[];
export const CLIENT_EVENT_TYPES: readonly ClientEventType[];
export const SERVER_EVENT_TYPES: readonly ServerEventType[];
export function createMessageEnvelope<TType extends ClientEventType>(
type: TType,
payload: ClientEventPayloads[TType]
): MessageEnvelope<TType, ClientEventPayloads[TType]>;
export function createMessageEnvelope<TType extends ServerEventType>(
type: TType,
payload: ServerEventPayloads[TType]
): MessageEnvelope<TType, ServerEventPayloads[TType]>;
export function isMessageEnvelope(value: unknown): value is MessageEnvelope<string, unknown>;
export function isClientEventType(type: string): type is ClientEventType;
export function isServerEventType(type: string): type is ServerEventType;

View File

@@ -0,0 +1,41 @@
export const PROTOCOL_PACKAGE_NAME = '@vela/protocol';
export const SESSION_STATES = Object.freeze(['idle', 'listening', 'thinking', 'speaking']);
export const CLIENT_EVENT_TYPES = Object.freeze([
'session.start',
'input_audio.append',
'input_audio.commit',
'response.cancel'
]);
export const SERVER_EVENT_TYPES = Object.freeze([
'session.ready',
'session.state',
'transcript.partial',
'transcript.final',
'response.text.delta',
'response.completed',
'error'
]);
export function createMessageEnvelope(type, payload) {
return { type, payload };
}
export function isMessageEnvelope(value) {
return Boolean(
value &&
typeof value === 'object' &&
typeof value.type === 'string' &&
'payload' in value
);
}
export function isClientEventType(type) {
return CLIENT_EVENT_TYPES.includes(type);
}
export function isServerEventType(type) {
return SERVER_EVENT_TYPES.includes(type);
}