aweshare 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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +246 -0
  3. package/apps/agent/dist/adapters.d.ts +15 -0
  4. package/apps/agent/dist/adapters.js +25 -0
  5. package/apps/agent/dist/adapters.js.map +1 -0
  6. package/apps/agent/dist/cli.d.ts +2 -0
  7. package/apps/agent/dist/cli.js +175 -0
  8. package/apps/agent/dist/cli.js.map +1 -0
  9. package/apps/agent/dist/config.d.ts +38 -0
  10. package/apps/agent/dist/config.js +150 -0
  11. package/apps/agent/dist/config.js.map +1 -0
  12. package/apps/agent/dist/connection.d.ts +40 -0
  13. package/apps/agent/dist/connection.js +359 -0
  14. package/apps/agent/dist/connection.js.map +1 -0
  15. package/apps/agent/dist/doctor.d.ts +14 -0
  16. package/apps/agent/dist/doctor.js +125 -0
  17. package/apps/agent/dist/doctor.js.map +1 -0
  18. package/apps/agent/dist/health.d.ts +15 -0
  19. package/apps/agent/dist/health.js +45 -0
  20. package/apps/agent/dist/health.js.map +1 -0
  21. package/apps/agent/dist/index.d.ts +5 -0
  22. package/apps/agent/dist/index.js +6 -0
  23. package/apps/agent/dist/index.js.map +1 -0
  24. package/apps/agent/package.json +20 -0
  25. package/apps/hub/dist/admin.d.ts +10 -0
  26. package/apps/hub/dist/admin.js +208 -0
  27. package/apps/hub/dist/admin.js.map +1 -0
  28. package/apps/hub/dist/auth.d.ts +17 -0
  29. package/apps/hub/dist/auth.js +28 -0
  30. package/apps/hub/dist/auth.js.map +1 -0
  31. package/apps/hub/dist/cli.d.ts +2 -0
  32. package/apps/hub/dist/cli.js +243 -0
  33. package/apps/hub/dist/cli.js.map +1 -0
  34. package/apps/hub/dist/config.d.ts +25 -0
  35. package/apps/hub/dist/config.js +53 -0
  36. package/apps/hub/dist/config.js.map +1 -0
  37. package/apps/hub/dist/consumer.d.ts +18 -0
  38. package/apps/hub/dist/consumer.js +157 -0
  39. package/apps/hub/dist/consumer.js.map +1 -0
  40. package/apps/hub/dist/db.d.ts +91 -0
  41. package/apps/hub/dist/db.js +236 -0
  42. package/apps/hub/dist/db.js.map +1 -0
  43. package/apps/hub/dist/http.d.ts +31 -0
  44. package/apps/hub/dist/http.js +118 -0
  45. package/apps/hub/dist/http.js.map +1 -0
  46. package/apps/hub/dist/limiter.d.ts +35 -0
  47. package/apps/hub/dist/limiter.js +78 -0
  48. package/apps/hub/dist/limiter.js.map +1 -0
  49. package/apps/hub/dist/limits.d.ts +30 -0
  50. package/apps/hub/dist/limits.js +52 -0
  51. package/apps/hub/dist/limits.js.map +1 -0
  52. package/apps/hub/dist/meter.d.ts +25 -0
  53. package/apps/hub/dist/meter.js +135 -0
  54. package/apps/hub/dist/meter.js.map +1 -0
  55. package/apps/hub/dist/sanitize.d.ts +3 -0
  56. package/apps/hub/dist/sanitize.js +56 -0
  57. package/apps/hub/dist/sanitize.js.map +1 -0
  58. package/apps/hub/dist/server.d.ts +25 -0
  59. package/apps/hub/dist/server.js +77 -0
  60. package/apps/hub/dist/server.js.map +1 -0
  61. package/apps/hub/dist/tokens.d.ts +9 -0
  62. package/apps/hub/dist/tokens.js +35 -0
  63. package/apps/hub/dist/tokens.js.map +1 -0
  64. package/apps/hub/dist/tunnel.d.ts +108 -0
  65. package/apps/hub/dist/tunnel.js +424 -0
  66. package/apps/hub/dist/tunnel.js.map +1 -0
  67. package/apps/hub/package.json +21 -0
  68. package/bin/aweshare.mjs +43 -0
  69. package/package.json +47 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Best-effort usage extraction from a response stream, on a side channel.
3
+ * Never throws, never blocks the relay; failures simply yield null counts.
4
+ * No content is retained beyond the bytes needed to read token counts.
5
+ */
6
+ export class UsageExtractor {
7
+ protocol;
8
+ text = '';
9
+ promptTokens = null;
10
+ completionTokens = null;
11
+ isSse;
12
+ done = false;
13
+ constructor(protocol, contentType) {
14
+ this.protocol = protocol;
15
+ this.isSse = (contentType ?? '').includes('text/event-stream');
16
+ }
17
+ get streaming() {
18
+ return this.isSse;
19
+ }
20
+ onChunk(bytes) {
21
+ if (this.done)
22
+ return;
23
+ if (!this.isSse) {
24
+ // whole-body JSON: keep up to 4MB, parse once at finish
25
+ if (this.text.length < 4 * 1024 * 1024)
26
+ this.text += Buffer.from(bytes).toString('utf8');
27
+ return;
28
+ }
29
+ this.text += Buffer.from(bytes).toString('utf8');
30
+ // process complete SSE events, keep the partial tail
31
+ const boundary = this.text.lastIndexOf('\n\n');
32
+ if (boundary === -1)
33
+ return;
34
+ const complete = this.text.slice(0, boundary + 2);
35
+ this.text = this.text.slice(boundary + 2);
36
+ for (const event of complete.split('\n\n'))
37
+ this.consumeSseEvent(event);
38
+ }
39
+ finish() {
40
+ if (this.done)
41
+ return this.result();
42
+ this.done = true;
43
+ if (this.isSse && this.text.trim())
44
+ this.consumeSseEvent(this.text);
45
+ if (!this.isSse) {
46
+ try {
47
+ const json = JSON.parse(this.text);
48
+ this.consumeUsage(json);
49
+ }
50
+ catch {
51
+ // malformed or truncated body: counts stay null
52
+ }
53
+ }
54
+ return this.result();
55
+ }
56
+ consumeSseEvent(event) {
57
+ const data = event
58
+ .split('\n')
59
+ .filter((line) => line.startsWith('data:'))
60
+ .map((line) => line.slice(5).trim())
61
+ .join('');
62
+ if (!data || data === '[DONE]')
63
+ return;
64
+ try {
65
+ const json = JSON.parse(data);
66
+ this.consumeUsage(json);
67
+ }
68
+ catch {
69
+ // non-JSON data lines are ignored
70
+ }
71
+ }
72
+ consumeUsage(json) {
73
+ if (this.protocol === 'openai') {
74
+ const usage = json.usage;
75
+ if (usage && typeof usage === 'object') {
76
+ if (typeof usage.prompt_tokens === 'number')
77
+ this.promptTokens = usage.prompt_tokens;
78
+ if (typeof usage.completion_tokens === 'number') {
79
+ this.completionTokens = usage.completion_tokens;
80
+ }
81
+ }
82
+ return;
83
+ }
84
+ if (this.protocol === 'responses') {
85
+ // streaming: usage rides on the response.completed event
86
+ if (json.type === 'response.completed') {
87
+ const response = json.response;
88
+ const usage = response?.usage;
89
+ if (typeof usage?.input_tokens === 'number')
90
+ this.promptTokens = usage.input_tokens;
91
+ if (typeof usage?.output_tokens === 'number') {
92
+ this.completionTokens = usage.output_tokens;
93
+ }
94
+ }
95
+ // non-streaming message body
96
+ const usage = json.usage;
97
+ if (usage && typeof usage === 'object') {
98
+ if (typeof usage.input_tokens === 'number')
99
+ this.promptTokens = usage.input_tokens;
100
+ if (typeof usage.output_tokens === 'number')
101
+ this.completionTokens = usage.output_tokens;
102
+ }
103
+ return;
104
+ }
105
+ // anthropic: message_start carries input_tokens, message_delta carries output_tokens
106
+ if (json.type === 'message_start') {
107
+ const message = json.message;
108
+ const usage = message?.usage;
109
+ if (typeof usage?.input_tokens === 'number')
110
+ this.promptTokens = usage.input_tokens;
111
+ }
112
+ if (json.type === 'message_delta') {
113
+ const usage = json.usage;
114
+ if (typeof usage?.output_tokens === 'number') {
115
+ this.completionTokens = usage.output_tokens;
116
+ }
117
+ }
118
+ // non-streaming message body
119
+ const usage = json.usage;
120
+ if (usage && typeof usage === 'object') {
121
+ if (typeof usage.input_tokens === 'number' && this.promptTokens === null) {
122
+ this.promptTokens = usage.input_tokens;
123
+ }
124
+ if (typeof usage.output_tokens === 'number' && this.completionTokens === null) {
125
+ this.completionTokens = usage.output_tokens;
126
+ }
127
+ }
128
+ }
129
+ result() {
130
+ if (this.promptTokens === null && this.completionTokens === null)
131
+ return null;
132
+ return { promptTokens: this.promptTokens, completionTokens: this.completionTokens };
133
+ }
134
+ }
135
+ //# sourceMappingURL=meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meter.js","sourceRoot":"","sources":["../src/meter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ export declare function sanitizeTunnelRequestHeaders(req: IncomingMessage): Record<string, string>;
3
+ export declare function sanitizeUpstreamResponseHeaders(headers: Record<string, string>): Record<string, string>;
@@ -0,0 +1,56 @@
1
+ /** Hop-by-hop and credential headers that must never enter the tunnel. */
2
+ const STRIP_REQUEST = new Set([
3
+ 'host',
4
+ 'connection',
5
+ 'keep-alive',
6
+ 'transfer-encoding',
7
+ 'upgrade',
8
+ 'te',
9
+ 'trailer',
10
+ 'content-length',
11
+ 'expect',
12
+ // consumer credentials — the agent injects the upstream key, the two never meet
13
+ 'authorization',
14
+ 'x-api-key',
15
+ // cookies authenticate the consumer to the hub domain; upstream must never see them
16
+ 'cookie',
17
+ ]);
18
+ const STRIP_RESPONSE = new Set([
19
+ 'connection',
20
+ 'keep-alive',
21
+ 'transfer-encoding',
22
+ 'upgrade',
23
+ 'te',
24
+ 'trailer',
25
+ // we relay chunked; byte count is identical so a stale length is never correct
26
+ 'content-length',
27
+ // agent-side pickHeaders already drops it; strip again in depth
28
+ 'set-cookie',
29
+ ]);
30
+ export function sanitizeTunnelRequestHeaders(req) {
31
+ const out = {};
32
+ for (const [key, value] of Object.entries(req.headers)) {
33
+ const lower = key.toLowerCase();
34
+ if (STRIP_REQUEST.has(lower) || lower.startsWith('proxy-'))
35
+ continue;
36
+ if (typeof value === 'string') {
37
+ out[lower] = value;
38
+ }
39
+ else if (Array.isArray(value)) {
40
+ out[lower] = value.join(', ');
41
+ }
42
+ }
43
+ out['content-type'] = out['content-type'] ?? 'application/json';
44
+ return out;
45
+ }
46
+ export function sanitizeUpstreamResponseHeaders(headers) {
47
+ const out = {};
48
+ for (const [key, value] of Object.entries(headers)) {
49
+ const lower = key.toLowerCase();
50
+ if (STRIP_RESPONSE.has(lower) || lower.startsWith('proxy-'))
51
+ continue;
52
+ out[lower] = value;
53
+ }
54
+ return out;
55
+ }
56
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAEA,0EAA0E;AAC1E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,MAAM;IACN,YAAY;IACZ,YAAY;IACZ,mBAAmB;IACnB,SAAS;IACT,IAAI;IACJ,SAAS;IACT,gBAAgB;IAChB,QAAQ;IACR,gFAAgF;IAChF,eAAe;IACf,WAAW;IACX,oFAAoF;IACpF,QAAQ;CACT,CAAC,CAAA;AAEF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,YAAY;IACZ,YAAY;IACZ,mBAAmB;IACnB,SAAS;IACT,IAAI;IACJ,SAAS;IACT,+EAA+E;IAC/E,gBAAgB;IAChB,gEAAgE;IAChE,YAAY;CACb,CAAC,CAAA;AAEF,MAAM,UAAU,4BAA4B,CAAC,GAAoB;IAC/D,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC/B,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACpE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;QACpB,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IACD,GAAG,CAAC,cAAc,CAAC,GAAG,GAAG,CAAC,cAAc,CAAC,IAAI,kBAAkB,CAAA;IAC/D,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,OAA+B;IAE/B,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC/B,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,SAAQ;QACrE,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;IACpB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type Server } from 'node:http';
2
+ import type Database from 'better-sqlite3';
3
+ import Logger from 'pino';
4
+ import type { AuthContext } from './auth.js';
5
+ import { type HubConfig, type HubPaths } from './config.js';
6
+ import { Router } from './http.js';
7
+ import { Tunnel } from './tunnel.js';
8
+ export interface HubServer {
9
+ server: Server;
10
+ db: Database.Database;
11
+ router: Router;
12
+ auth: AuthContext;
13
+ tunnel: Tunnel;
14
+ paths: HubPaths;
15
+ config: HubConfig;
16
+ log: Logger.Logger;
17
+ close: () => Promise<void>;
18
+ }
19
+ export interface CreateHubOptions {
20
+ paths?: HubPaths;
21
+ config?: HubConfig;
22
+ log?: Logger.Logger;
23
+ }
24
+ /** Assemble the hub: db + admin API + healthz. Tunnel and /v1 routes attach in later phases. */
25
+ export declare function createHubServer(opts?: CreateHubOptions): HubServer;
@@ -0,0 +1,77 @@
1
+ import { createServer } from 'node:http';
2
+ import Logger from 'pino';
3
+ import { registerAdminRoutes } from './admin.js';
4
+ import { ensureDataDir, hubConfigFromEnv, readAdminToken, resolvePaths, } from './config.js';
5
+ import { registerConsumerRoutes } from './consumer.js';
6
+ import { openDb } from './db.js';
7
+ import { Router, sendError, sendJson } from './http.js';
8
+ import { AliasSlots, ConsumerLimiter, TokenWindow } from './limiter.js';
9
+ import { loadOrCreateAdminToken, loadOrCreatePepper } from './tokens.js';
10
+ import { Tunnel } from './tunnel.js';
11
+ /** Assemble the hub: db + admin API + healthz. Tunnel and /v1 routes attach in later phases. */
12
+ export function createHubServer(opts = {}) {
13
+ const paths = opts.paths ?? resolvePaths();
14
+ const config = opts.config ?? hubConfigFromEnv();
15
+ const log = opts.log ?? Logger({ level: process.env.AWESHARE_LOG ?? 'info' });
16
+ ensureDataDir(paths);
17
+ const db = openDb(paths.dbFile);
18
+ const pepper = loadOrCreatePepper(paths);
19
+ const adminToken = readAdminToken(paths) ?? loadOrCreateAdminToken(paths);
20
+ const auth = { db, pepper, adminToken };
21
+ const router = new Router();
22
+ router.add('GET', '/healthz', (ctx) => sendJson(ctx.res, 200, { ok: true }));
23
+ registerAdminRoutes(router, { db, auth, log });
24
+ const tunnel = new Tunnel(db, auth, config, log);
25
+ registerConsumerRoutes(router, {
26
+ db,
27
+ auth,
28
+ config,
29
+ tunnel,
30
+ limiter: new ConsumerLimiter(),
31
+ tpm: new TokenWindow(),
32
+ slots: new AliasSlots(),
33
+ log,
34
+ });
35
+ const server = createServer(async (req, res) => {
36
+ try {
37
+ const url = new URL(req.url ?? '/', 'http://local');
38
+ const matched = router.match(req.method ?? 'GET', url.pathname);
39
+ if (matched === 'not-found') {
40
+ sendJson(res, 404, { error: { code: 'NOT_FOUND', message: url.pathname } });
41
+ return;
42
+ }
43
+ if (matched === 'method-not-allowed') {
44
+ sendJson(res, 405, { error: { code: 'METHOD_NOT_ALLOWED', message: url.pathname } });
45
+ return;
46
+ }
47
+ await matched.handler({
48
+ req,
49
+ res,
50
+ params: matched.params,
51
+ query: url.searchParams,
52
+ url,
53
+ });
54
+ }
55
+ catch (err) {
56
+ sendError(res, err);
57
+ }
58
+ });
59
+ server.on('clientError', (_err, socket) => socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'));
60
+ tunnel.attach(server);
61
+ return {
62
+ server,
63
+ db,
64
+ router,
65
+ auth,
66
+ tunnel,
67
+ paths,
68
+ config,
69
+ log,
70
+ close: async () => {
71
+ tunnel.close();
72
+ await new Promise((resolve) => server.close(() => resolve()));
73
+ db.close();
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { TokenRole } from 'aweshare-protocol';
2
+ import type { HubPaths } from './config.js';
3
+ /** 32 random bytes -> 43 base64url chars; matches TOKEN_PATTERN. */
4
+ export declare function generateToken(role: TokenRole): string;
5
+ export declare function hashToken(token: string, pepper: string): string;
6
+ export declare function loadOrCreatePepper(paths: HubPaths): string;
7
+ /** Admin token lives only in a server-local file; shown once at init. */
8
+ export declare function loadOrCreateAdminToken(paths: HubPaths): string;
9
+ export declare function safeEqual(a: string, b: string): boolean;
@@ -0,0 +1,35 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { TOKEN_PREFIXES } from 'aweshare-protocol';
4
+ /** 32 random bytes -> 43 base64url chars; matches TOKEN_PATTERN. */
5
+ export function generateToken(role) {
6
+ return TOKEN_PREFIXES[role] + randomBytes(32).toString('base64url');
7
+ }
8
+ export function hashToken(token, pepper) {
9
+ return createHash('sha256').update(`${pepper}:${token}`).digest('hex');
10
+ }
11
+ export function loadOrCreatePepper(paths) {
12
+ if (existsSync(paths.pepperFile)) {
13
+ return readFileSync(paths.pepperFile, 'utf8').trim();
14
+ }
15
+ const pepper = randomBytes(32).toString('hex');
16
+ writeFileSync(paths.pepperFile, `${pepper}\n`, { mode: 0o600 });
17
+ return pepper;
18
+ }
19
+ /** Admin token lives only in a server-local file; shown once at init. */
20
+ export function loadOrCreateAdminToken(paths) {
21
+ if (existsSync(paths.adminTokenFile)) {
22
+ return readFileSync(paths.adminTokenFile, 'utf8').trim();
23
+ }
24
+ const token = generateToken('admin');
25
+ writeFileSync(paths.adminTokenFile, `${token}\n`, { mode: 0o600 });
26
+ return token;
27
+ }
28
+ export function safeEqual(a, b) {
29
+ const ba = Buffer.from(a);
30
+ const bb = Buffer.from(b);
31
+ if (ba.length !== bb.length)
32
+ return false;
33
+ return timingSafeEqual(ba, bb);
34
+ }
35
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEjE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,MAAc;IACrD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACxE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAe;IAChD,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,OAAO,YAAY,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IACtD,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC9C,aAAa,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/D,OAAO,MAAM,CAAA;AACf,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,sBAAsB,CAAC,KAAe;IACpD,IAAI,UAAU,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QACrC,OAAO,YAAY,CAAC,KAAK,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1D,CAAC;IACD,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;IACpC,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAClE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAS,EAAE,CAAS;IAC5C,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzB,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACzC,OAAO,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;AAChC,CAAC"}
@@ -0,0 +1,108 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Server } from 'node:http';
3
+ import { type BackendStatus, type Protocol } from 'aweshare-protocol';
4
+ import type Database from 'better-sqlite3';
5
+ import type { Logger } from 'pino';
6
+ import { WebSocket } from 'ws';
7
+ import type { AuthContext } from './auth.js';
8
+ import type { HubConfig } from './config.js';
9
+ import { type OfferingRow } from './db.js';
10
+ export declare const HEARTBEAT_TIMEOUT_MS = 45000;
11
+ export interface Session {
12
+ producerId: number;
13
+ name: string;
14
+ ws: WebSocket;
15
+ backendStatus: Map<string, BackendStatus>;
16
+ lastHeartbeat: number;
17
+ requests: Map<string, TunnelRequest>;
18
+ }
19
+ export interface DispatchInit {
20
+ protocol: Protocol;
21
+ backendId: string;
22
+ method: string;
23
+ path: string;
24
+ query: Record<string, string> | null;
25
+ headers: Record<string, string>;
26
+ body: Uint8Array;
27
+ }
28
+ export interface RelayResult {
29
+ status: number | null;
30
+ errorCode: string | null;
31
+ canceled: boolean;
32
+ durationMs: number;
33
+ bytesOut: number;
34
+ }
35
+ export interface RelayFailure {
36
+ code: string;
37
+ message: string;
38
+ status: number;
39
+ }
40
+ export declare function statusForCode(code: string): number;
41
+ /**
42
+ * One dispatched consumer request: ticket + dispatch + response pump.
43
+ * Emits: 'head' {status, headers}, 'chunk' (Uint8Array), 'end', 'error' (RelayFailure).
44
+ * waitFinished() always resolves — never rejects — with the final RelayResult.
45
+ */
46
+ export declare class TunnelRequest extends EventEmitter {
47
+ private readonly session;
48
+ private readonly init;
49
+ private readonly config;
50
+ readonly requestId: string;
51
+ readonly startedAt: number;
52
+ bytesOut: number;
53
+ private headPromise;
54
+ private resolveHead;
55
+ private rejectHead;
56
+ private finishedPromise;
57
+ private settleFinished;
58
+ private result;
59
+ private finished;
60
+ private headArrived;
61
+ private streamingStarted;
62
+ private bufferedChunks;
63
+ private pendingEnd;
64
+ private headTimer;
65
+ private idleTimer;
66
+ constructor(session: Session, init: DispatchInit, config: HubConfig);
67
+ dispatch(): Promise<void>;
68
+ waitHead(): Promise<{
69
+ status: number;
70
+ headers: Record<string, string>;
71
+ }>;
72
+ /** Flush chunks buffered between head and consumer attach; after this, chunks emit live. */
73
+ startStreaming(): void;
74
+ waitFinished(): Promise<RelayResult>;
75
+ /** Consumer went away; tell the agent to abort upstream. */
76
+ cancel(): void;
77
+ handleHead(status: number, headers: Record<string, string>): void;
78
+ handleChunk(payload: Uint8Array): void;
79
+ handleEnd(): void;
80
+ handleError(code: string, message: string): void;
81
+ handleSessionClosed(): void;
82
+ private fail;
83
+ private finalize;
84
+ private armIdleTimer;
85
+ private clearTimers;
86
+ private send;
87
+ private sendBestEffort;
88
+ }
89
+ /** WS endpoint /ws/v1/producer: upgrade auth, register, heartbeat, request routing. */
90
+ export declare class Tunnel {
91
+ private readonly db;
92
+ private readonly auth;
93
+ private readonly config;
94
+ private readonly log;
95
+ private sessions;
96
+ private wss;
97
+ private sweeper;
98
+ constructor(db: Database.Database, auth: AuthContext, config: HubConfig, log: Logger);
99
+ attach(server: Server): void;
100
+ getSession(producerId: number): Session | undefined;
101
+ offeringStatus(offering: OfferingRow): 'online' | 'degraded' | 'offline';
102
+ openRequest(session: Session, init: DispatchInit): TunnelRequest;
103
+ close(): void;
104
+ private onConnection;
105
+ private handleControl;
106
+ private validateOffering;
107
+ private sweep;
108
+ }