f30a8bc318
Phase 2 of the Flynn roadmap. Adds a WebSocket gateway server that starts alongside the Telegram bot, providing real-time API access to the agent, sessions, and tools. Protocol: JSON-RPC-like (request/response/event) over WebSocket. 8 methods: agent.send, agent.cancel, sessions.list, sessions.history, sessions.create, tools.list, tools.invoke, system.health. Auth: Bearer token + Tailscale identity header support. Session bridge: per-connection agent instances with shared model router. New files: src/gateway/ (protocol, router, server, auth, session-bridge, handlers for agent/sessions/tools/system). 57 new tests (181 total), typecheck clean.
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { Router } from './router.js';
|
|
import type { GatewayRequest, OutboundMessage } from './protocol.js';
|
|
import { makeResponse, ErrorCode } from './protocol.js';
|
|
|
|
describe('Router', () => {
|
|
it('dispatches to registered handler', async () => {
|
|
const router = new Router();
|
|
const handler = vi.fn(async (req: GatewayRequest) => makeResponse(req.id, { ok: true }));
|
|
router.register('test.method', handler);
|
|
|
|
const request: GatewayRequest = { id: 1, method: 'test.method', params: {} };
|
|
const send = vi.fn();
|
|
const result = await router.dispatch(request, send);
|
|
|
|
expect(handler).toHaveBeenCalledWith(request, send);
|
|
expect(result).toEqual({ id: 1, result: { ok: true } });
|
|
});
|
|
|
|
it('returns MethodNotFound for unregistered method', async () => {
|
|
const router = new Router();
|
|
const request: GatewayRequest = { id: 1, method: 'unknown.method' };
|
|
const send = vi.fn();
|
|
const result = await router.dispatch(request, send);
|
|
|
|
expect(result).toEqual({
|
|
id: 1,
|
|
error: { code: ErrorCode.MethodNotFound, message: 'Unknown method: unknown.method' },
|
|
});
|
|
});
|
|
|
|
it('lists registered methods', () => {
|
|
const router = new Router();
|
|
router.register('a.method', async () => {});
|
|
router.register('b.method', async () => {});
|
|
|
|
expect(router.listMethods()).toEqual(['a.method', 'b.method']);
|
|
});
|
|
|
|
it('handler can send streaming events via send function', async () => {
|
|
const router = new Router();
|
|
router.register('stream.test', async (req, send) => {
|
|
send({ id: req.id, event: 'content', data: { text: 'chunk1' } });
|
|
send({ id: req.id, event: 'content', data: { text: 'chunk2' } });
|
|
send({ id: req.id, event: 'done', data: { content: 'chunk1chunk2' } });
|
|
});
|
|
|
|
const request: GatewayRequest = { id: 1, method: 'stream.test' };
|
|
const sent: OutboundMessage[] = [];
|
|
const send = vi.fn((msg: OutboundMessage) => sent.push(msg));
|
|
|
|
await router.dispatch(request, send);
|
|
|
|
expect(sent).toHaveLength(3);
|
|
expect(sent[0]).toEqual({ id: 1, event: 'content', data: { text: 'chunk1' } });
|
|
expect(sent[2]).toEqual({ id: 1, event: 'done', data: { content: 'chunk1chunk2' } });
|
|
});
|
|
});
|