From 49d57a5b8b41c98c9a924d49c0987e0e7ccc7d10 Mon Sep 17 00:00:00 2001 From: SakataRyuichi Date: Sun, 8 Mar 2026 19:19:38 +0900 Subject: [PATCH] fix(server-runtime): use superjson.parse for incoming WebSocket messages (#1187) --- packages/server-runtime/src/index.ts | 8 ++++++-- packages/stage-ui/src/stores/mods/api/context-bridge.ts | 9 ++++++++- services/discord-bot/src/adapters/airi-adapter.ts | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/server-runtime/src/index.ts b/packages/server-runtime/src/index.ts index 8b2f0d5d6..5986c26dc 100644 --- a/packages/server-runtime/src/index.ts +++ b/packages/server-runtime/src/index.ts @@ -12,7 +12,7 @@ import { availableLogLevelStrings, Format, LogLevelString, logLevelStringToLogLe import { MessageHeartbeat, MessageHeartbeatKind, WebSocketEventSource } from '@proj-airi/server-shared/types' import { defineWebSocketHandler, H3 } from 'h3' import { nanoid } from 'nanoid' -import { stringify } from 'superjson' +import { parse, stringify } from 'superjson' import packageJSON from '../package.json' @@ -200,7 +200,11 @@ export function setupApp(options?: { let event: WebSocketEvent try { - event = message.json() as WebSocketEvent + // NOTICE: SDK clients send events using superjson.stringify, so we must use + // superjson.parse here instead of message.json() (which uses JSON.parse). + // Using JSON.parse on a superjson-encoded string returns the wrapper object + // { json: {...}, meta: {...} } with type=undefined, which breaks all event routing. + event = parse(message.text()) } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err) diff --git a/packages/stage-ui/src/stores/mods/api/context-bridge.ts b/packages/stage-ui/src/stores/mods/api/context-bridge.ts index 07ee56ab2..75e560c86 100644 --- a/packages/stage-ui/src/stores/mods/api/context-bridge.ts +++ b/packages/stage-ui/src/stores/mods/api/context-bridge.ts @@ -89,7 +89,14 @@ export const useContextBridgeStore = defineStore('mods:api:context-bridge', () = } if (activeProvider.value && activeModel.value) { - const chatProvider = await providersStore.getProviderInstance(activeProvider.value) + let chatProvider: ChatProvider + try { + chatProvider = await providersStore.getProviderInstance(activeProvider.value) + } + catch (err) { + console.error('[context-bridge] getProviderInstance failed for provider:', activeProvider.value, err) + return + } let messageText = text const targetSessionId = overrides?.sessionId diff --git a/services/discord-bot/src/adapters/airi-adapter.ts b/services/discord-bot/src/adapters/airi-adapter.ts index 2c398a5e7..d64911aef 100644 --- a/services/discord-bot/src/adapters/airi-adapter.ts +++ b/services/discord-bot/src/adapters/airi-adapter.ts @@ -161,6 +161,7 @@ export class DiscordAdapter { try { const message = (event.data as { message?: { content: string } }).message const discordContext = (event.data)['gen-ai:chat'].input.data.discord + if (message?.content && discordContext?.channelId) { const channel = await this.discordClient.channels.fetch(discordContext.channelId) if (channel?.isTextBased() && 'send' in channel && typeof channel.send === 'function') {