refactor(gateway): split server runtime

This commit is contained in:
Peter Steinberger
2026-01-14 09:11:21 +00:00
parent ea018a68cc
commit d19bc1562b
30 changed files with 3486 additions and 2542 deletions

View File

@@ -0,0 +1,197 @@
import { randomUUID } from "node:crypto";
import { normalizeChannelId } from "../channels/plugins/index.js";
import { agentCommand } from "../commands/agent.js";
import { loadConfig } from "../config/config.js";
import { saveSessionStore } from "../config/sessions.js";
import { normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js";
import type {
BridgeEvent,
BridgeHandlersContext,
} from "./server-bridge-types.js";
import { loadSessionEntry } from "./session-utils.js";
import { formatForLog } from "./ws-log.js";
export const handleBridgeEvent = async (
ctx: BridgeHandlersContext,
nodeId: string,
evt: BridgeEvent,
) => {
switch (evt.event) {
case "voice.transcript": {
if (!evt.payloadJSON) return;
let payload: unknown;
try {
payload = JSON.parse(evt.payloadJSON) as unknown;
} catch {
return;
}
const obj =
typeof payload === "object" && payload !== null
? (payload as Record<string, unknown>)
: {};
const text = typeof obj.text === "string" ? obj.text.trim() : "";
if (!text) return;
if (text.length > 20_000) return;
const sessionKeyRaw =
typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
const cfg = loadConfig();
const rawMainKey = normalizeMainKey(cfg.session?.mainKey);
const sessionKey = sessionKeyRaw.length > 0 ? sessionKeyRaw : rawMainKey;
const { storePath, store, entry, canonicalKey } =
loadSessionEntry(sessionKey);
const now = Date.now();
const sessionId = entry?.sessionId ?? randomUUID();
store[canonicalKey] = {
sessionId,
updatedAt: now,
thinkingLevel: entry?.thinkingLevel,
verboseLevel: entry?.verboseLevel,
reasoningLevel: entry?.reasoningLevel,
systemSent: entry?.systemSent,
sendPolicy: entry?.sendPolicy,
lastChannel: entry?.lastChannel,
lastTo: entry?.lastTo,
};
if (storePath) {
await saveSessionStore(storePath, store);
}
// Ensure chat UI clients refresh when this run completes (even though it wasn't started via chat.send).
// This maps agent bus events (keyed by sessionId) to chat events (keyed by clientRunId).
ctx.addChatRun(sessionId, {
sessionKey,
clientRunId: `voice-${randomUUID()}`,
});
void agentCommand(
{
message: text,
sessionId,
sessionKey,
thinking: "low",
deliver: false,
messageChannel: "node",
},
defaultRuntime,
ctx.deps,
).catch((err) => {
ctx.logBridge.warn(`agent failed node=${nodeId}: ${formatForLog(err)}`);
});
return;
}
case "agent.request": {
if (!evt.payloadJSON) return;
type AgentDeepLink = {
message?: string;
sessionKey?: string | null;
thinking?: string | null;
deliver?: boolean;
to?: string | null;
channel?: string | null;
timeoutSeconds?: number | null;
key?: string | null;
};
let link: AgentDeepLink | null = null;
try {
link = JSON.parse(evt.payloadJSON) as AgentDeepLink;
} catch {
return;
}
const message = (link?.message ?? "").trim();
if (!message) return;
if (message.length > 20_000) return;
const channelRaw =
typeof link?.channel === "string" ? link.channel.trim() : "";
const channel = normalizeChannelId(channelRaw) ?? undefined;
const to =
typeof link?.to === "string" && link.to.trim()
? link.to.trim()
: undefined;
const deliver = Boolean(link?.deliver) && Boolean(channel);
const sessionKeyRaw = (link?.sessionKey ?? "").trim();
const sessionKey =
sessionKeyRaw.length > 0 ? sessionKeyRaw : `node-${nodeId}`;
const { storePath, store, entry, canonicalKey } =
loadSessionEntry(sessionKey);
const now = Date.now();
const sessionId = entry?.sessionId ?? randomUUID();
store[canonicalKey] = {
sessionId,
updatedAt: now,
thinkingLevel: entry?.thinkingLevel,
verboseLevel: entry?.verboseLevel,
reasoningLevel: entry?.reasoningLevel,
systemSent: entry?.systemSent,
sendPolicy: entry?.sendPolicy,
lastChannel: entry?.lastChannel,
lastTo: entry?.lastTo,
};
if (storePath) {
await saveSessionStore(storePath, store);
}
void agentCommand(
{
message,
sessionId,
sessionKey,
thinking: link?.thinking ?? undefined,
deliver,
to,
channel,
timeout:
typeof link?.timeoutSeconds === "number"
? link.timeoutSeconds.toString()
: undefined,
messageChannel: "node",
},
defaultRuntime,
ctx.deps,
).catch((err) => {
ctx.logBridge.warn(`agent failed node=${nodeId}: ${formatForLog(err)}`);
});
return;
}
case "chat.subscribe": {
if (!evt.payloadJSON) return;
let payload: unknown;
try {
payload = JSON.parse(evt.payloadJSON) as unknown;
} catch {
return;
}
const obj =
typeof payload === "object" && payload !== null
? (payload as Record<string, unknown>)
: {};
const sessionKey =
typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
if (!sessionKey) return;
ctx.bridgeSubscribe(nodeId, sessionKey);
return;
}
case "chat.unsubscribe": {
if (!evt.payloadJSON) return;
let payload: unknown;
try {
payload = JSON.parse(evt.payloadJSON) as unknown;
} catch {
return;
}
const obj =
typeof payload === "object" && payload !== null
? (payload as Record<string, unknown>)
: {};
const sessionKey =
typeof obj.sessionKey === "string" ? obj.sessionKey.trim() : "";
if (!sessionKey) return;
ctx.bridgeUnsubscribe(nodeId, sessionKey);
return;
}
default:
return;
}
};