@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.
@@ -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=10FC3B2EECF76FCF64756E2164756E21
2667
+ //# debugId=E40F8525D6A44D4364756E2164756E21