@jeffusion/bungee-core 5.15.2 → 5.17.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/dist/config-publication/worker-runtime-contract.d.ts +1 -1
- package/dist/config-worker/lifecycle.d.ts +6 -0
- package/dist/data-admission/worker.d.ts +1 -0
- package/dist/gateway/contracts.d.ts +1 -1
- package/dist/gateway/plugin.d.ts +1 -0
- package/dist/gateway/plugin.js +15 -1
- package/dist/gateway/runtime.d.ts +2 -0
- package/dist/gateway/websocket-contracts.d.ts +62 -0
- package/dist/gateway/websocket-credential.d.ts +3 -0
- package/dist/gateway/websocket-plugin.d.ts +9 -0
- package/dist/hooks/plugin-hooks.d.ts +8 -0
- package/dist/logger/access-log-writer.d.ts +1 -1
- package/dist/main.js +4220 -99
- package/dist/plugins/chatgpt-oauth/index.js +6 -1
- package/dist/plugins/chatgpt-oauth/index.js.map +3 -3
- package/dist/plugins/chatgpt-oauth/manifest.json +41 -0
- package/dist/plugins/token-metering/index.js +225 -3
- package/dist/plugins/token-metering/index.js.map +5 -4
- package/dist/plugins/token-stats/manifest.json +4 -0
- package/dist/plugins/token-stats/ui/TokenStatsPage.svelte +49 -19
- package/dist/plugins/token-stats/ui/overview-trends.ts +21 -8
- package/dist/public-listener/websocket.d.ts +4 -0
- package/dist/scoped-plugin-registry.d.ts +12 -0
- package/dist/websocket/headers.d.ts +9 -0
- package/dist/websocket/index.d.ts +3 -0
- package/dist/websocket/message.d.ts +3 -0
- package/dist/websocket/observation.d.ts +17 -0
- package/dist/websocket/transport.d.ts +77 -0
- package/package.json +4 -2
|
@@ -9,7 +9,7 @@ export interface ConfigWorkerLifecycle<ServingHandle> {
|
|
|
9
9
|
readonly plugin_status: PluginRuntimeOrchestratorStatusReport;
|
|
10
10
|
}>;
|
|
11
11
|
stop(handle: ServingHandle): Promise<void>;
|
|
12
|
-
stopAccepting(handle: ServingHandle): Promise<void>;
|
|
12
|
+
stopAccepting(handle: ServingHandle, drainTimeoutMs?: number): Promise<void>;
|
|
13
13
|
drain(handle: ServingHandle): Promise<void>;
|
|
14
14
|
forceStop?(handle: ServingHandle): Promise<void>;
|
|
15
15
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WebSocketBridge, type WebSocketBridgeData } from '../websocket';
|
|
1
2
|
import type { AppConfig } from '@jeffusion/bungee-types';
|
|
2
3
|
import type { Server } from 'bun';
|
|
3
4
|
import { type ConfigWorkerLifecycle } from '../config-publication/worker-runtime-contract';
|
|
@@ -8,6 +9,9 @@ import type { PluginServiceHost } from '../plugin-services';
|
|
|
8
9
|
type LifecycleServer = Pick<Server<unknown>, 'port' | 'stop'>;
|
|
9
10
|
export type ConfigWorkerServingHandle = {
|
|
10
11
|
readonly server: LifecycleServer;
|
|
12
|
+
readonly websocket?: WebSocketBridge;
|
|
13
|
+
readonly sessions?: Set<Promise<void>>;
|
|
14
|
+
closeTimer?: ReturnType<typeof setTimeout>;
|
|
11
15
|
readonly cleanup: () => Promise<void>;
|
|
12
16
|
drainPromise: Promise<void> | null;
|
|
13
17
|
drainComplete: boolean;
|
|
@@ -16,6 +20,7 @@ export type ConfigWorkerServingHandle = {
|
|
|
16
20
|
stopPromise: Promise<void> | null;
|
|
17
21
|
};
|
|
18
22
|
type LifecycleFetch = (request: Request) => Response | Promise<Response>;
|
|
23
|
+
type WebSocketFetch = (request: Request, server?: Bun.Server<any>) => Response | undefined | Promise<Response | undefined>;
|
|
19
24
|
export type ServingRequestContext = {
|
|
20
25
|
readonly servingRevision: number;
|
|
21
26
|
readonly logging?: RequestLoggerDependencies;
|
|
@@ -34,6 +39,7 @@ export type ProductionResources = {
|
|
|
34
39
|
cleanupPluginRuntime(): Promise<void>;
|
|
35
40
|
handleRequest(request: Request, config: AppConfig, context: ServingRequestContext): Promise<Response>;
|
|
36
41
|
serve(fetch: LifecycleFetch): LifecycleServer;
|
|
42
|
+
serveWebSocket?(fetch: WebSocketFetch, websocket: Bun.WebSocketHandler<WebSocketBridgeData>): LifecycleServer;
|
|
37
43
|
closeAccessLog(): Promise<void>;
|
|
38
44
|
closeFileLog(): Promise<void>;
|
|
39
45
|
};
|
|
@@ -42,6 +42,7 @@ export declare class WorkerRequestAdmission {
|
|
|
42
42
|
url: string;
|
|
43
43
|
model: string | null;
|
|
44
44
|
body: unknown;
|
|
45
|
+
transport?: 'websocket';
|
|
45
46
|
}, signal: AbortSignal, loadBody?: () => Promise<unknown>): Promise<PreparedAdmissionAttempt[]>;
|
|
46
47
|
release(): Promise<void>;
|
|
47
48
|
}
|
|
@@ -107,4 +107,4 @@ export interface GatewayLogResult {
|
|
|
107
107
|
readonly logger?: RequestLogger;
|
|
108
108
|
}
|
|
109
109
|
/** Provider stages are fixed, required, and registered exactly once at startup. */
|
|
110
|
-
export declare const GATEWAY_PROVIDER_STAGES: readonly ["onGatewayBody", "onGatewayRequest", "onGatewayRoute", "onGatewayAdmission", "onGatewayAdmissionSession", "onGatewayAdmissionPrepare", "onGatewaySelect", "onGatewayFailover", "onGatewayRetry", "onGatewayForward", "onGatewayHeaderRules", "onGatewayCors", "onGatewayBodyRules", "onGatewayQueryRules", "onGatewayResponseRules", "onGatewayLog"];
|
|
110
|
+
export declare const GATEWAY_PROVIDER_STAGES: readonly ["onGatewayWebSocket", "onGatewayBody", "onGatewayRequest", "onGatewayRoute", "onGatewayAdmission", "onGatewayAdmissionSession", "onGatewayAdmissionPrepare", "onGatewaySelect", "onGatewayFailover", "onGatewayRetry", "onGatewayForward", "onGatewayHeaderRules", "onGatewayCors", "onGatewayBodyRules", "onGatewayQueryRules", "onGatewayResponseRules", "onGatewayLog"];
|
package/dist/gateway/plugin.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export type * from '../plugin-extensions';
|
|
|
6
6
|
export type * from '../plugin-services';
|
|
7
7
|
export type * from './contracts';
|
|
8
8
|
export type * from './body-contracts';
|
|
9
|
+
export type * from './websocket-contracts';
|
|
9
10
|
export { protocolSSEOutput } from '../plugins/sse-envelope';
|
|
10
11
|
export { logger } from '../plugin-logger';
|
|
11
12
|
export { ANONYMOUS_PRINCIPAL } from '../plugin-extensions';
|
package/dist/gateway/plugin.js
CHANGED
|
@@ -319,6 +319,9 @@ var init_impl = __esm(() => {
|
|
|
319
319
|
// src/hooks/plugin-hooks.ts
|
|
320
320
|
function createPluginHooks() {
|
|
321
321
|
return {
|
|
322
|
+
onGatewayWebSocket: new AsyncSeriesBailHook("onGatewayWebSocket"),
|
|
323
|
+
onWebSocketHandshake: new AsyncSeriesWaterfallHook("onWebSocketHandshake"),
|
|
324
|
+
onWebSocketObservation: new WebSocketObservationHook("onWebSocketObservation"),
|
|
322
325
|
onGatewayBody: new SyncBailHook("onGatewayBody"),
|
|
323
326
|
onGatewayRequest: new AsyncSeriesBailHook("onGatewayRequest"),
|
|
324
327
|
onGatewayRoute: new AsyncSeriesBailHook("onGatewayRoute"),
|
|
@@ -358,7 +361,7 @@ function clearHooks(hooks) {
|
|
|
358
361
|
for (const hook of Object.values(hooks))
|
|
359
362
|
hook.clear();
|
|
360
363
|
}
|
|
361
|
-
var AttemptObservationHook;
|
|
364
|
+
var AttemptObservationHook, WebSocketObservationHook;
|
|
362
365
|
var init_plugin_hooks = __esm(() => {
|
|
363
366
|
init_impl();
|
|
364
367
|
AttemptObservationHook = class AttemptObservationHook extends AsyncParallelHook {
|
|
@@ -374,6 +377,17 @@ var init_plugin_hooks = __esm(() => {
|
|
|
374
377
|
}
|
|
375
378
|
}
|
|
376
379
|
};
|
|
380
|
+
WebSocketObservationHook = class WebSocketObservationHook extends AsyncParallelHook {
|
|
381
|
+
async promise(...args) {
|
|
382
|
+
this.callCount++;
|
|
383
|
+
const started = performance.now();
|
|
384
|
+
try {
|
|
385
|
+
await Promise.all(this.taps.map((tap) => this.executeTap(tap, args)));
|
|
386
|
+
} finally {
|
|
387
|
+
this.totalTimeMs += performance.now() - started;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
377
391
|
});
|
|
378
392
|
|
|
379
393
|
// src/hooks/index.ts
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type PluginHooks } from '../hooks';
|
|
2
2
|
import type { Plugin } from '../plugin.types';
|
|
3
3
|
import { type executeHttpRequest } from './request-plugin';
|
|
4
|
+
import type { GatewayWebSocketInput, GatewayWebSocketResult } from './websocket-contracts';
|
|
4
5
|
export declare function gatewayBuiltins(): Plugin[];
|
|
5
6
|
/** The host only owns registration; providers use the existing Plugin contract and Hook executor. */
|
|
6
7
|
export declare function registerGatewayBuiltins(hooks: PluginHooks, plugins?: readonly Plugin[]): void;
|
|
@@ -10,3 +11,4 @@ export declare function gatewayHooks(): PluginHooks;
|
|
|
10
11
|
export declare function initializeGateway(): void;
|
|
11
12
|
export declare function requireGatewayResult<T>(value: T | undefined, stage: string): T;
|
|
12
13
|
export declare const runGatewayRequest: typeof executeHttpRequest;
|
|
14
|
+
export declare function runGatewayWebSocket(input: GatewayWebSocketInput): Promise<GatewayWebSocketResult>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Read-only WebSocket SDK. HTTP bodies and WebSocket messages have separate owners. */
|
|
2
|
+
export interface WebSocketMessageView {
|
|
3
|
+
readonly kind: 'text' | 'binary';
|
|
4
|
+
readonly byteLength: number;
|
|
5
|
+
/** Demand-driven, shared, deeply frozen JSON; undefined for binary/invalid JSON. */
|
|
6
|
+
json(): unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface WebSocketSessionContext {
|
|
9
|
+
readonly connectionId: string;
|
|
10
|
+
readonly keyId?: string | null;
|
|
11
|
+
readonly routeId: string;
|
|
12
|
+
readonly upstreamId: string;
|
|
13
|
+
readonly upstreamUrl: string;
|
|
14
|
+
readonly servingRevision?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface WebSocketSessionMetrics {
|
|
17
|
+
readonly durationMs: number;
|
|
18
|
+
readonly clientMessages: number;
|
|
19
|
+
readonly upstreamMessages: number;
|
|
20
|
+
readonly clientBytes: number;
|
|
21
|
+
readonly upstreamBytes: number;
|
|
22
|
+
}
|
|
23
|
+
export type WebSocketObservationEvent = Readonly<WebSocketSessionContext & {
|
|
24
|
+
/** Delivery lease expires when the observer returns or exceeds its deadline. */
|
|
25
|
+
readonly isActive: () => boolean;
|
|
26
|
+
} & ({
|
|
27
|
+
readonly phase: 'open';
|
|
28
|
+
} | {
|
|
29
|
+
readonly phase: 'message';
|
|
30
|
+
readonly direction: 'client' | 'upstream';
|
|
31
|
+
readonly message: WebSocketMessageView;
|
|
32
|
+
} | {
|
|
33
|
+
readonly phase: 'incomplete';
|
|
34
|
+
readonly reason: 'observer-timeout' | 'observer-error' | 'buffer-limit' | 'message-limit';
|
|
35
|
+
} | {
|
|
36
|
+
readonly phase: 'close';
|
|
37
|
+
readonly code: number;
|
|
38
|
+
readonly reason: string;
|
|
39
|
+
readonly metrics: WebSocketSessionMetrics;
|
|
40
|
+
})>;
|
|
41
|
+
/** Handshake-only transforms; message data is never mutable through this hook. */
|
|
42
|
+
export interface WebSocketHandshakeContext {
|
|
43
|
+
readonly connectionId: string;
|
|
44
|
+
readonly routeId: string;
|
|
45
|
+
readonly upstreamId: string;
|
|
46
|
+
readonly signal: AbortSignal;
|
|
47
|
+
url: URL;
|
|
48
|
+
headers: Headers;
|
|
49
|
+
}
|
|
50
|
+
export interface GatewayWebSocketInput {
|
|
51
|
+
readonly request: Request;
|
|
52
|
+
readonly nativeRequest: Request;
|
|
53
|
+
readonly server: Bun.Server<any>;
|
|
54
|
+
readonly bridge: import('../websocket').WebSocketBridge;
|
|
55
|
+
readonly config: import('@jeffusion/bungee-types').AppConfig;
|
|
56
|
+
readonly servingRevision: number;
|
|
57
|
+
/** Host shutdown barrier; retains observer/credential owners until close cleanup. */
|
|
58
|
+
readonly retain: (completion: Promise<void>) => void;
|
|
59
|
+
}
|
|
60
|
+
export interface GatewayWebSocketResult {
|
|
61
|
+
readonly response?: Response;
|
|
62
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RuntimeUpstream } from '../worker/types';
|
|
2
|
+
/** Uses the same exact endpoint/binding/origin credential policy as HTTP attempts. */
|
|
3
|
+
export declare function websocketCredential(upstream: RuntimeUpstream, url: URL, sourceHeaders: Headers, revision: number, attemptId: string, signal: AbortSignal): Promise<Headers>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Plugin, PluginHooks } from '@jeffusion/bungee-core/plugin';
|
|
2
|
+
/** WebSocket is a registered gateway provider, with handshake and read-only message hooks. */
|
|
3
|
+
export declare class WebSocketGatewayPlugin implements Plugin {
|
|
4
|
+
bodyRequirements(): {
|
|
5
|
+
request: "none";
|
|
6
|
+
};
|
|
7
|
+
register(hooks: PluginHooks): void;
|
|
8
|
+
private upgrade;
|
|
9
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WebSocketObservationEvent, WebSocketHandshakeContext, GatewayWebSocketInput, GatewayWebSocketResult } from '../gateway/websocket-contracts';
|
|
1
2
|
/**
|
|
2
3
|
* Bungee Plugin Hooks 定义
|
|
3
4
|
*
|
|
@@ -109,6 +110,10 @@ export type AttemptObservationEvent = Readonly<{
|
|
|
109
110
|
declare class AttemptObservationHook extends AsyncParallelHook<[AttemptObservationEvent]> {
|
|
110
111
|
promise(...args: [AttemptObservationEvent]): Promise<void>;
|
|
111
112
|
}
|
|
113
|
+
/** Strict, isolated WebSocket observers use the same plugin registration contract. */
|
|
114
|
+
declare class WebSocketObservationHook extends AsyncParallelHook<[WebSocketObservationEvent]> {
|
|
115
|
+
promise(...args: [WebSocketObservationEvent]): Promise<void>;
|
|
116
|
+
}
|
|
112
117
|
/**
|
|
113
118
|
* 错误上下文
|
|
114
119
|
*/
|
|
@@ -220,6 +225,9 @@ export declare function createPluginHooks(): PluginHooks;
|
|
|
220
225
|
* Plugin Hooks 类型
|
|
221
226
|
*/
|
|
222
227
|
export interface PluginHooks {
|
|
228
|
+
onGatewayWebSocket: AsyncSeriesBailHook<[GatewayWebSocketInput], GatewayWebSocketResult>;
|
|
229
|
+
onWebSocketHandshake: AsyncSeriesWaterfallHook<WebSocketHandshakeContext>;
|
|
230
|
+
onWebSocketObservation: WebSocketObservationHook;
|
|
223
231
|
onGatewayBody: SyncBailHook<GatewayBodyArguments, BodySource>;
|
|
224
232
|
onGatewayHeaderRules: AsyncSeriesBailHook<GatewayHeaderRuleArguments, true>;
|
|
225
233
|
onGatewayCors: AsyncSeriesBailHook<GatewayCorsArguments, Response>;
|
|
@@ -100,7 +100,7 @@ export declare const accessLogWriter: {
|
|
|
100
100
|
getDatabase: () => Database;
|
|
101
101
|
updateResponseBodyId: (requestId: string, respBodyId: string) => void;
|
|
102
102
|
updateBodyId: (requestId: string, type: "request" | "response" | "original-request", bodyId: string) => void;
|
|
103
|
-
updateProtocolOutcome: (requestId: string, outcome: "
|
|
103
|
+
updateProtocolOutcome: (requestId: string, outcome: "incomplete" | "cancelled" | "failed" | "completed", success: boolean, code?: string | undefined, diagnosticMessage?: string | undefined) => void;
|
|
104
104
|
updateTransportOutcome: (requestId: string, outcome: "cancelled" | "unknown" | "failed" | "pending" | "completed", code?: string | undefined) => void;
|
|
105
105
|
flush: () => Promise<void>;
|
|
106
106
|
close: () => Promise<void>;
|