@kb-labs/gateway-app 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.kb/database/kb.sqlite-shm +0 -0
- package/.kb/database/kb.sqlite-wal +0 -0
- package/package.json +49 -0
- package/src/__tests__/auth-routes.test.ts +279 -0
- package/src/__tests__/execute-routes.test.ts +408 -0
- package/src/__tests__/execution-registry.test.ts +218 -0
- package/src/__tests__/health.test.ts +215 -0
- package/src/__tests__/live-gateway.e2e.test.ts +648 -0
- package/src/__tests__/llm-gateway.test.ts +361 -0
- package/src/__tests__/observability-collector.test.ts +59 -0
- package/src/__tests__/platform-api.test.ts +317 -0
- package/src/__tests__/registry.test.ts +546 -0
- package/src/__tests__/retry-executor.test.ts +244 -0
- package/src/__tests__/server.integration.test.ts +417 -0
- package/src/__tests__/subscription-registry.test.ts +308 -0
- package/src/__tests__/telemetry-ingest.test.ts +309 -0
- package/src/__tests__/tokens.test.ts +83 -0
- package/src/__tests__/ws-client-connect.e2e.test.ts +381 -0
- package/src/__tests__/ws-handshake.e2e.test.ts +288 -0
- package/src/auth/middleware.ts +50 -0
- package/src/auth/routes.ts +57 -0
- package/src/auth/tokens.ts +41 -0
- package/src/bootstrap.ts +98 -0
- package/src/clients/subscription-registry.ts +137 -0
- package/src/clients/ws-handler.ts +196 -0
- package/src/config.ts +20 -0
- package/src/docs/routes.ts +70 -0
- package/src/execute/errors.ts +21 -0
- package/src/execute/execution-registry.ts +84 -0
- package/src/execute/retry-executor.ts +159 -0
- package/src/execute/routes.ts +239 -0
- package/src/hosts/dispatcher.ts +2 -0
- package/src/hosts/registry.ts +305 -0
- package/src/hosts/ws-handler.ts +445 -0
- package/src/index.ts +7 -0
- package/src/llm/routes.ts +343 -0
- package/src/manifest.ts +21 -0
- package/src/observability/collector.ts +346 -0
- package/src/platform/routes.ts +195 -0
- package/src/server.ts +447 -0
- package/src/telemetry/routes.ts +89 -0
- package/src/ws/gateway-ws.ts +73 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +8 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Telemetry Ingestion — unified event collection endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Exposes `POST /telemetry/v1/ingest` for external products to send events.
|
|
5
|
+
* Events are written to platform analytics via IAnalytics.track().
|
|
6
|
+
* Auth required — tenantId extracted from auth context.
|
|
7
|
+
*/
|
|
8
|
+
import type { FastifyInstance } from 'fastify';
|
|
9
|
+
import { platform } from '@kb-labs/core-runtime';
|
|
10
|
+
import type { ILogger } from '@kb-labs/core-platform';
|
|
11
|
+
import {
|
|
12
|
+
TelemetryIngestRequestSchema,
|
|
13
|
+
type TelemetryIngestResponse,
|
|
14
|
+
} from '@kb-labs/gateway-contracts';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Register telemetry ingestion routes on the given Fastify scope.
|
|
18
|
+
* The scope is expected to have the auth middleware already applied.
|
|
19
|
+
*/
|
|
20
|
+
export function registerTelemetryRoutes(app: FastifyInstance, logger: ILogger): void {
|
|
21
|
+
app.post('/telemetry/v1/ingest', { schema: { tags: ['Telemetry'], summary: 'Ingest telemetry events' } }, async (request, reply) => {
|
|
22
|
+
const auth = request.authContext;
|
|
23
|
+
if (!auth) {
|
|
24
|
+
return reply.code(401).send({ error: 'Unauthorized' });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const parsed = TelemetryIngestRequestSchema.safeParse(request.body);
|
|
28
|
+
if (!parsed.success) {
|
|
29
|
+
return reply.code(400).send({
|
|
30
|
+
error: 'Bad Request',
|
|
31
|
+
issues: parsed.error.issues,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const analytics = platform.analytics;
|
|
36
|
+
if (!analytics) {
|
|
37
|
+
return reply.code(503).send({
|
|
38
|
+
error: 'Analytics adapter not configured',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { events } = parsed.data;
|
|
43
|
+
let accepted = 0;
|
|
44
|
+
let rejected = 0;
|
|
45
|
+
const errors: Array<{ index: number; message: string }> = [];
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < events.length; i++) {
|
|
48
|
+
const event = events[i]!;
|
|
49
|
+
try {
|
|
50
|
+
await analytics.track(event.type, {
|
|
51
|
+
// Source attribution — who sent this event
|
|
52
|
+
_source: event.source,
|
|
53
|
+
_tenantId: auth.namespaceId,
|
|
54
|
+
_ts: event.timestamp ?? new Date().toISOString(),
|
|
55
|
+
// Tags as flat properties for indexing
|
|
56
|
+
...event.tags,
|
|
57
|
+
// Free-form payload
|
|
58
|
+
...event.payload,
|
|
59
|
+
});
|
|
60
|
+
accepted++;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
rejected++;
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
errors.push({ index: i, message });
|
|
65
|
+
logger.warn('Telemetry event rejected', {
|
|
66
|
+
index: i,
|
|
67
|
+
type: event.type,
|
|
68
|
+
source: event.source,
|
|
69
|
+
error: message,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
logger.info('Telemetry ingest', {
|
|
75
|
+
tenantId: auth.namespaceId,
|
|
76
|
+
accepted,
|
|
77
|
+
rejected,
|
|
78
|
+
totalEvents: events.length,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const response: TelemetryIngestResponse = {
|
|
82
|
+
accepted,
|
|
83
|
+
rejected,
|
|
84
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return reply.code(accepted > 0 ? 200 : 422).send(response);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module gateway-app/ws/gateway-ws
|
|
3
|
+
*
|
|
4
|
+
* Unified WebSocket upgrade handler for the gateway.
|
|
5
|
+
*
|
|
6
|
+
* Intercepts upgrade requests BEFORE @fastify/http-proxy:
|
|
7
|
+
* - Gateway-own WS paths (/hosts/connect, /clients/connect) → raw ws handlers
|
|
8
|
+
* - Everything else → delegated to @fastify/http-proxy for upstream WS proxy
|
|
9
|
+
*
|
|
10
|
+
* This eliminates the conflict between @fastify/websocket and @fastify/http-proxy
|
|
11
|
+
* which both try to attach 'upgrade' listeners and call assignSocket().
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Server, IncomingMessage } from 'node:http';
|
|
15
|
+
import type { Duplex } from 'node:stream';
|
|
16
|
+
import { WebSocketServer } from 'ws';
|
|
17
|
+
import type { ICache, ILogger } from '@kb-labs/core-platform';
|
|
18
|
+
import type { JwtConfig } from '@kb-labs/gateway-auth';
|
|
19
|
+
import { createWsHandler } from '../hosts/ws-handler.js';
|
|
20
|
+
import { createClientWsHandler } from '../clients/ws-handler.js';
|
|
21
|
+
|
|
22
|
+
const GATEWAY_WS_PATHS = new Set(['/hosts/connect', '/clients/connect']);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Attach gateway-own WebSocket endpoints using raw `ws` package.
|
|
26
|
+
*
|
|
27
|
+
* Must be called AFTER `app.ready()` (so http-proxy has registered its
|
|
28
|
+
* upgrade listener) but BEFORE `app.listen()`.
|
|
29
|
+
*
|
|
30
|
+
* Captures all existing 'upgrade' listeners, removes them, then installs
|
|
31
|
+
* a single unified handler that dispatches:
|
|
32
|
+
* - Gateway WS paths → raw ws handlers
|
|
33
|
+
* - All other paths → delegated to captured listeners (http-proxy)
|
|
34
|
+
*/
|
|
35
|
+
export function attachGatewayWs(
|
|
36
|
+
server: Server,
|
|
37
|
+
cache: ICache,
|
|
38
|
+
jwtConfig: JwtConfig,
|
|
39
|
+
logger: ILogger,
|
|
40
|
+
hostRegistry?: import('../hosts/registry.js').HostRegistry,
|
|
41
|
+
): void {
|
|
42
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
43
|
+
const hostsHandler = createWsHandler(cache, jwtConfig, logger, hostRegistry);
|
|
44
|
+
const clientsHandler = createClientWsHandler(cache, jwtConfig, logger);
|
|
45
|
+
|
|
46
|
+
// Capture @fastify/http-proxy's upgrade listener(s)
|
|
47
|
+
const existingListeners = server.listeners('upgrade').slice() as Array<
|
|
48
|
+
(req: IncomingMessage, socket: Duplex, head: Buffer) => void
|
|
49
|
+
>;
|
|
50
|
+
server.removeAllListeners('upgrade');
|
|
51
|
+
|
|
52
|
+
server.on('upgrade', (req: IncomingMessage, socket: Duplex, head: Buffer) => {
|
|
53
|
+
const pathname = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
54
|
+
|
|
55
|
+
if (GATEWAY_WS_PATHS.has(pathname)) {
|
|
56
|
+
// Gateway-own WS — handle directly with raw ws
|
|
57
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
58
|
+
if (pathname === '/hosts/connect') {
|
|
59
|
+
hostsHandler(ws, req);
|
|
60
|
+
} else {
|
|
61
|
+
clientsHandler(ws, req);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
// Delegate to @fastify/http-proxy for upstream WS proxy
|
|
66
|
+
for (const listener of existingListeners) {
|
|
67
|
+
listener.call(server, req, socket, head);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
logger.info('Gateway WS endpoints attached', { paths: [...GATEWAY_WS_PATHS] });
|
|
73
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mergeConfig } from 'vitest/config';
|
|
2
|
+
import base from '@kb-labs/devkit/vitest/node.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Gateway app vitest config.
|
|
6
|
+
*
|
|
7
|
+
* live-gateway.e2e.test.ts is excluded from the default test run because it
|
|
8
|
+
* requires a standalone Gateway instance (no upstream proxy for /api/v1).
|
|
9
|
+
* In the standard dev setup the gateway proxies /api/v1 to the REST API,
|
|
10
|
+
* which shadows the gateway's own execute routes.
|
|
11
|
+
*
|
|
12
|
+
* To run the live tests: vitest run src/__tests__/live-gateway.e2e.test.ts
|
|
13
|
+
*/
|
|
14
|
+
export default mergeConfig(base, {
|
|
15
|
+
test: {
|
|
16
|
+
exclude: [
|
|
17
|
+
'**/node_modules/**',
|
|
18
|
+
'**/dist/**',
|
|
19
|
+
'**/*.live.test.ts',
|
|
20
|
+
'**/live-gateway.e2e.test.ts',
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
});
|