@jeffusion/bungee-core 5.16.0 → 5.18.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-storage/gateway-target-validation.d.ts +5 -0
- 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 +4326 -128
- package/dist/plugin.types.d.ts +1 -1
- package/dist/plugins/ai-transformer/index.js.map +2 -2
- package/dist/plugins/anthropic-request-sanitizer/index.js.map +2 -2
- package/dist/plugins/anthropic-tool-name-transformer/index.js.map +2 -2
- package/dist/plugins/chatgpt-oauth/index.js +12 -4
- package/dist/plugins/chatgpt-oauth/index.js.map +5 -5
- package/dist/plugins/chatgpt-oauth/manifest.json +41 -0
- package/dist/plugins/codex-router/control.js +49 -0
- package/dist/plugins/codex-router/control.js.map +11 -0
- package/dist/plugins/codex-router/index.js +318 -0
- package/dist/plugins/codex-router/index.js.map +27 -0
- package/dist/plugins/codex-router/manifest.json +149 -0
- package/dist/plugins/deepseek-reasoning-fix/index.js.map +2 -2
- package/dist/plugins/key-access/index.js.map +2 -2
- package/dist/plugins/key-rate-limit/index.js.map +2 -2
- package/dist/plugins/local-accounts/index.js.map +2 -2
- package/dist/plugins/model-mapping/control.js.map +1 -1
- package/dist/plugins/model-mapping/index.js.map +3 -3
- package/dist/plugins/models-dev/control.js +24 -1
- package/dist/plugins/models-dev/control.js.map +5 -5
- package/dist/plugins/models-dev/index.js +24 -1
- package/dist/plugins/models-dev/index.js.map +6 -6
- package/dist/plugins/models-dev/manifest.json +12 -0
- package/dist/plugins/openai-messages-to-chat/index.js.map +2 -2
- package/dist/plugins/signature-repair/index.js.map +2 -2
- package/dist/plugins/token-budget/index.js.map +2 -2
- package/dist/plugins/token-metering/index.js +225 -3
- package/dist/plugins/token-metering/index.js.map +6 -5
- package/dist/plugins/token-stats/control.js.map +1 -1
- package/dist/plugins/token-stats/index.js.map +3 -3
- 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
|
@@ -1986,6 +1986,209 @@ function classifyResponse(body, knownLlmRequest, event) {
|
|
|
1986
1986
|
return;
|
|
1987
1987
|
}
|
|
1988
1988
|
|
|
1989
|
+
// plugins/token-metering/server/websocket.ts
|
|
1990
|
+
import { randomUUID, createHash } from "crypto";
|
|
1991
|
+
var MAX_CONNECTIONS = 128;
|
|
1992
|
+
var MAX_RESPONSES = 1024;
|
|
1993
|
+
var MAX_CONNECTION_RESPONSES = 64;
|
|
1994
|
+
var MAX_TOMBSTONES = 256;
|
|
1995
|
+
var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1996
|
+
var identifier = (value) => typeof value === "string" && value.length > 0 && value.length <= 256 ? value : undefined;
|
|
1997
|
+
var count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1998
|
+
function metadata(event) {
|
|
1999
|
+
return JSON.stringify([event.keyId ?? null, event.routeId, event.upstreamId, event.upstreamUrl, event.servingRevision]);
|
|
2000
|
+
}
|
|
2001
|
+
function responsesUrl(value) {
|
|
2002
|
+
try {
|
|
2003
|
+
return /\/(?:v1\/responses|backend-api\/codex\/responses)\/?$/.test(new URL(value).pathname);
|
|
2004
|
+
} catch {
|
|
2005
|
+
return false;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
function stream(body) {
|
|
2009
|
+
return identifier(body.stream_id) ?? (record2(body.response) ? identifier(body.response.stream_id) : undefined);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
class ResponsesWebSocketMetering {
|
|
2013
|
+
callbacks;
|
|
2014
|
+
connections = new Map;
|
|
2015
|
+
responseCount = 0;
|
|
2016
|
+
closed = false;
|
|
2017
|
+
constructor(callbacks) {
|
|
2018
|
+
this.callbacks = callbacks;
|
|
2019
|
+
}
|
|
2020
|
+
async observe(event) {
|
|
2021
|
+
try {
|
|
2022
|
+
await this.observeActive(event);
|
|
2023
|
+
} finally {
|
|
2024
|
+
if (!event.isActive()) {
|
|
2025
|
+
const connection = this.connections.get(event.connectionId);
|
|
2026
|
+
if (connection) {
|
|
2027
|
+
connection.disabled = true;
|
|
2028
|
+
this.discard(connection);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
async observeActive(event) {
|
|
2034
|
+
if (this.closed || !event.isActive())
|
|
2035
|
+
return;
|
|
2036
|
+
if (event.phase === "open") {
|
|
2037
|
+
if (!responsesUrl(event.upstreamUrl) || !this.callbacks.hasDemand() || this.connections.has(event.connectionId) || this.connections.size >= MAX_CONNECTIONS)
|
|
2038
|
+
return;
|
|
2039
|
+
this.connections.set(event.connectionId, { metadata: metadata(event), pending: [], active: new Map, finished: new Set, disabled: false });
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
const connection = this.connections.get(event.connectionId);
|
|
2043
|
+
if (!connection)
|
|
2044
|
+
return;
|
|
2045
|
+
if (connection.metadata !== metadata(event)) {
|
|
2046
|
+
this.discard(connection);
|
|
2047
|
+
this.connections.delete(event.connectionId);
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (event.phase === "close" || event.phase === "incomplete") {
|
|
2051
|
+
await this.interrupt(connection, event, event.phase === "close" ? "cancelled" : "failed");
|
|
2052
|
+
if (event.phase === "close")
|
|
2053
|
+
this.connections.delete(event.connectionId);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (connection.disabled || event.phase !== "message" || event.message.kind !== "text")
|
|
2057
|
+
return;
|
|
2058
|
+
const body = event.message.json();
|
|
2059
|
+
if (!record2(body))
|
|
2060
|
+
return;
|
|
2061
|
+
if (event.direction === "client") {
|
|
2062
|
+
if (body.type !== "response.create" || !(typeof body.input === "string" || Array.isArray(body.input) || identifier(body.previous_response_id) || identifier(body.model)))
|
|
2063
|
+
return;
|
|
2064
|
+
if (!this.hasCapacity(connection)) {
|
|
2065
|
+
await this.interrupt(connection, event, "failed");
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
const response = await this.start(event, identifier(body.model), stream(body));
|
|
2069
|
+
if (response && event.isActive())
|
|
2070
|
+
connection.pending.push(response);
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
const type = identifier(body.type);
|
|
2074
|
+
if (!type?.startsWith("response."))
|
|
2075
|
+
return;
|
|
2076
|
+
const envelope = record2(body.response) ? body.response : undefined;
|
|
2077
|
+
const terminal = ["response.completed", "response.failed", "response.incomplete"].includes(type);
|
|
2078
|
+
if (terminal && (!envelope || envelope.object !== "response" || !Array.isArray(envelope.output)))
|
|
2079
|
+
return;
|
|
2080
|
+
const responseId = identifier(envelope?.id) ?? identifier(body.response_id);
|
|
2081
|
+
if (!responseId || connection.finished.has(responseId))
|
|
2082
|
+
return;
|
|
2083
|
+
let response = connection.active.get(responseId);
|
|
2084
|
+
if (!response) {
|
|
2085
|
+
if (!envelope || envelope.object !== "response" || !Array.isArray(envelope.output) || !["response.created", "response.in_progress", "response.completed", "response.failed", "response.incomplete"].includes(type))
|
|
2086
|
+
return;
|
|
2087
|
+
const key = stream(body);
|
|
2088
|
+
let pendingIndex = connection.pending.findIndex((item) => item.stream === key);
|
|
2089
|
+
if (pendingIndex < 0 && key !== undefined)
|
|
2090
|
+
pendingIndex = connection.pending.findIndex((item) => item.stream === undefined);
|
|
2091
|
+
if (pendingIndex >= 0 && !terminal)
|
|
2092
|
+
response = connection.pending.splice(pendingIndex, 1)[0];
|
|
2093
|
+
else {
|
|
2094
|
+
if (!this.hasCapacity(connection)) {
|
|
2095
|
+
await this.interrupt(connection, event, "failed");
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
response = await this.start(event, identifier(envelope.model), key);
|
|
2099
|
+
}
|
|
2100
|
+
if (!response || !event.isActive())
|
|
2101
|
+
return;
|
|
2102
|
+
connection.active.set(responseId, response);
|
|
2103
|
+
}
|
|
2104
|
+
const usage = record2(envelope?.usage) ? envelope.usage : undefined;
|
|
2105
|
+
if (!response.bound && !await this.bind(response, event, responseId))
|
|
2106
|
+
return;
|
|
2107
|
+
if (!await this.emit(response, event, { phase: "response", status: 200, protocol: "sse", body }))
|
|
2108
|
+
return;
|
|
2109
|
+
if (!terminal)
|
|
2110
|
+
return;
|
|
2111
|
+
connection.active.delete(responseId);
|
|
2112
|
+
connection.finished.add(responseId);
|
|
2113
|
+
const usageComplete = count(usage?.input_tokens) && count(usage?.output_tokens);
|
|
2114
|
+
await this.finish(response, event, type === "response.completed" && usageComplete ? "completed" : "failed", !usageComplete);
|
|
2115
|
+
if (connection.finished.size > MAX_TOMBSTONES)
|
|
2116
|
+
connection.finished.delete(connection.finished.values().next().value);
|
|
2117
|
+
}
|
|
2118
|
+
hasCapacity(connection) {
|
|
2119
|
+
return this.responseCount < MAX_RESPONSES && connection.pending.length + connection.active.size < MAX_CONNECTION_RESPONSES;
|
|
2120
|
+
}
|
|
2121
|
+
async start(event, model, streamId) {
|
|
2122
|
+
const response = { requestId: randomUUID(), attemptId: randomUUID(), stream: streamId, model };
|
|
2123
|
+
this.responseCount++;
|
|
2124
|
+
this.callbacks.prepareRequest(response.requestId);
|
|
2125
|
+
try {
|
|
2126
|
+
if (await this.emit(response, event, { phase: "selected" }) && await this.emit(response, event, { phase: "request", url: event.upstreamUrl, body: { model, stream: true } }))
|
|
2127
|
+
return response;
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
this.responseCount--;
|
|
2130
|
+
this.callbacks.discardRequest(response.requestId, response.attemptId);
|
|
2131
|
+
throw error;
|
|
2132
|
+
}
|
|
2133
|
+
this.responseCount--;
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
async bind(response, event, responseId) {
|
|
2137
|
+
this.callbacks.discardRequest(response.requestId, response.attemptId);
|
|
2138
|
+
const uuid = (kind) => {
|
|
2139
|
+
const hex = createHash("sha256").update(JSON.stringify(["bungee.ws.response.v1", event.connectionId, responseId, kind])).digest("hex");
|
|
2140
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
2141
|
+
};
|
|
2142
|
+
response.requestId = uuid("request");
|
|
2143
|
+
response.attemptId = uuid("attempt");
|
|
2144
|
+
response.bound = true;
|
|
2145
|
+
this.callbacks.prepareRequest(response.requestId);
|
|
2146
|
+
return await this.emit(response, event, { phase: "selected" }) && await this.emit(response, event, { phase: "request", url: event.upstreamUrl, body: { model: response.model, stream: true } });
|
|
2147
|
+
}
|
|
2148
|
+
async emit(response, event, phase) {
|
|
2149
|
+
if (this.closed || !event.isActive()) {
|
|
2150
|
+
this.callbacks.discardRequest(response.requestId, response.attemptId);
|
|
2151
|
+
return false;
|
|
2152
|
+
}
|
|
2153
|
+
await this.callbacks.emit({ requestId: response.requestId, attemptId: response.attemptId, keyId: event.keyId, routeId: event.routeId, upstreamId: event.upstreamId, isActive: event.isActive, ...phase });
|
|
2154
|
+
if (this.closed || !event.isActive()) {
|
|
2155
|
+
this.callbacks.discardRequest(response.requestId, response.attemptId);
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
return true;
|
|
2159
|
+
}
|
|
2160
|
+
async finish(response, event, outcome, incomplete) {
|
|
2161
|
+
this.responseCount--;
|
|
2162
|
+
if (incomplete && !await this.emit(response, event, { phase: "incomplete", reason: "raw-response-incomplete" }))
|
|
2163
|
+
return;
|
|
2164
|
+
if (!await this.emit(response, event, { phase: "end", sent: true, outcome }))
|
|
2165
|
+
return;
|
|
2166
|
+
await this.emit(response, event, { phase: "request-end" });
|
|
2167
|
+
}
|
|
2168
|
+
async interrupt(connection, event, outcome) {
|
|
2169
|
+
connection.disabled = true;
|
|
2170
|
+
const unfinished = [...connection.pending, ...connection.active.values()];
|
|
2171
|
+
connection.pending.length = 0;
|
|
2172
|
+
connection.active.clear();
|
|
2173
|
+
for (const response of unfinished)
|
|
2174
|
+
await this.finish(response, event, outcome, true);
|
|
2175
|
+
}
|
|
2176
|
+
discard(connection) {
|
|
2177
|
+
for (const response of [...connection.pending, ...connection.active.values()]) {
|
|
2178
|
+
this.responseCount--;
|
|
2179
|
+
this.callbacks.discardRequest(response.requestId, response.attemptId);
|
|
2180
|
+
}
|
|
2181
|
+
connection.pending.length = 0;
|
|
2182
|
+
connection.active.clear();
|
|
2183
|
+
}
|
|
2184
|
+
dispose() {
|
|
2185
|
+
this.closed = true;
|
|
2186
|
+
for (const connection of this.connections.values())
|
|
2187
|
+
this.discard(connection);
|
|
2188
|
+
this.connections.clear();
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
|
|
1989
2192
|
// plugins/token-metering/server/index.ts
|
|
1990
2193
|
var MAX_ACTIVE_ATTEMPTS = 1024;
|
|
1991
2194
|
var LOG_INTERVAL_MS = 60000;
|
|
@@ -2173,6 +2376,16 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2173
2376
|
pending = new Set;
|
|
2174
2377
|
requiredPending = new Map;
|
|
2175
2378
|
closed = false;
|
|
2379
|
+
websocket = new ResponsesWebSocketMetering({
|
|
2380
|
+
emit: (event) => this.handleAttemptObservation(event, true),
|
|
2381
|
+
prepareRequest: (requestId) => this.prepareRequest(requestId),
|
|
2382
|
+
discardRequest: (requestId, attemptId) => {
|
|
2383
|
+
this.attempts.delete(attemptId);
|
|
2384
|
+
this.requests.delete(requestId);
|
|
2385
|
+
this.droppedAttempts.delete(attemptId);
|
|
2386
|
+
},
|
|
2387
|
+
hasDemand: () => !this.closed && this.subscriptions.size > 0
|
|
2388
|
+
});
|
|
2176
2389
|
parsedResponses = 0;
|
|
2177
2390
|
get activeAttempts() {
|
|
2178
2391
|
return this.attempts.size;
|
|
@@ -2247,6 +2460,7 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2247
2460
|
register(hooks) {
|
|
2248
2461
|
hooks.onRequestInit.tapPromise("token-metering-demand", async (event) => this.prepareRequest(event.requestId));
|
|
2249
2462
|
hooks.onAttemptObservation.tapPromise("token-metering", async (event) => this.handleAttemptObservation(event));
|
|
2463
|
+
hooks.onWebSocketObservation.tapPromise("token-metering-websocket", async (event) => this.websocket.observe(event));
|
|
2250
2464
|
}
|
|
2251
2465
|
prepareRequest(requestId) {
|
|
2252
2466
|
if (!this.requests.has(requestId))
|
|
@@ -2297,7 +2511,7 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2297
2511
|
});
|
|
2298
2512
|
}
|
|
2299
2513
|
}
|
|
2300
|
-
async handleAttemptObservation(event) {
|
|
2514
|
+
async handleAttemptObservation(event, websocket = false) {
|
|
2301
2515
|
if (!event.isActive()) {
|
|
2302
2516
|
this.attempts.delete(event.attemptId);
|
|
2303
2517
|
return;
|
|
@@ -2337,7 +2551,7 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2337
2551
|
const body = parseRequestBody(event.body);
|
|
2338
2552
|
if (!body)
|
|
2339
2553
|
return;
|
|
2340
|
-
const classification = classifyRequest(url, body);
|
|
2554
|
+
const classification = websocket ? { llm: true, provider: "openai" } : classifyRequest(url, body);
|
|
2341
2555
|
attempt.llm ||= classification.llm;
|
|
2342
2556
|
if (!classification.llm)
|
|
2343
2557
|
return;
|
|
@@ -2353,6 +2567,13 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2353
2567
|
if (event.phase === "response") {
|
|
2354
2568
|
attempt.responseSeen = true;
|
|
2355
2569
|
const body = event.body;
|
|
2570
|
+
if (websocket && isRecord2(body.response)) {
|
|
2571
|
+
const model = detectModel(body.response);
|
|
2572
|
+
if (model) {
|
|
2573
|
+
attempt.model = model;
|
|
2574
|
+
attempt.session?.consumeRequest({ body: { model } });
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2356
2577
|
attempt.responseFailed ||= event.status >= 400 || body.error !== undefined;
|
|
2357
2578
|
const provider = attempt.provider === "unknown" ? classifyResponse(body, attempt.llm, event.envelope?.event) : attempt.provider;
|
|
2358
2579
|
if (!provider)
|
|
@@ -2428,6 +2649,7 @@ var TokenMeteringPlugin = definePlugin(class {
|
|
|
2428
2649
|
}
|
|
2429
2650
|
async onDestroy() {
|
|
2430
2651
|
this.closed = true;
|
|
2652
|
+
this.websocket.dispose();
|
|
2431
2653
|
await Promise.allSettled([...this.pending]);
|
|
2432
2654
|
this.subscriptions.clear();
|
|
2433
2655
|
this.requests.clear();
|
|
@@ -2442,4 +2664,4 @@ export {
|
|
|
2442
2664
|
server_default as default
|
|
2443
2665
|
};
|
|
2444
2666
|
|
|
2445
|
-
//# debugId=
|
|
2667
|
+
//# debugId=E40F8525D6A44D4364756E2164756E21
|