@jeffusion/bungee-core 6.1.1 → 6.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 (38) hide show
  1. package/dist/main.js +94 -65
  2. package/dist/observability-worker.js +21 -1
  3. package/dist/plugins/anthropic-request-sanitizer/index.js.map +3 -3
  4. package/dist/plugins/anthropic-tool-name-transformer/index.js.map +3 -3
  5. package/dist/plugins/chatgpt-oauth/control.js.map +1 -1
  6. package/dist/plugins/chatgpt-oauth/index.js.map +3 -3
  7. package/dist/plugins/codex-router/control.js.map +1 -1
  8. package/dist/plugins/codex-router/index.js.map +3 -3
  9. package/dist/plugins/deepseek-reasoning-fix/index.js.map +3 -3
  10. package/dist/plugins/key-access/control.js.map +1 -1
  11. package/dist/plugins/key-access/index.js.map +3 -3
  12. package/dist/plugins/key-access/ingress.js.map +1 -1
  13. package/dist/plugins/key-rate-limit/index.js.map +3 -3
  14. package/dist/plugins/llm-protocol-adapter/control.js.map +1 -1
  15. package/dist/plugins/llm-protocol-adapter/index.js.map +4 -4
  16. package/dist/plugins/llm-tps/control.js +212 -0
  17. package/dist/plugins/llm-tps/control.js.map +13 -0
  18. package/dist/plugins/llm-tps/index.js +286 -0
  19. package/dist/plugins/llm-tps/index.js.map +27 -0
  20. package/dist/plugins/llm-tps/manifest.json +115 -0
  21. package/dist/plugins/llm-tps/ui/TpsEndpoints.svelte +9 -0
  22. package/dist/plugins/llm-tps/ui/TpsOverview.svelte +9 -0
  23. package/dist/plugins/llm-tps/ui/TpsWidget.svelte +115 -0
  24. package/dist/plugins/llm-tps/ui/presentation.ts +16 -0
  25. package/dist/plugins/llm-tps/ui/stats-resource.ts +77 -0
  26. package/dist/plugins/llm-tps/ui/stats.ts +7 -0
  27. package/dist/plugins/model-mapping/index.js.map +3 -3
  28. package/dist/plugins/models-dev/index.js.map +3 -3
  29. package/dist/plugins/signature-repair/index.js.map +3 -3
  30. package/dist/plugins/token-budget/control.js.map +1 -1
  31. package/dist/plugins/token-budget/index.js.map +3 -3
  32. package/dist/plugins/token-metering/index.js +138 -11
  33. package/dist/plugins/token-metering/index.js.map +8 -7
  34. package/dist/plugins/token-stats/control.js.map +1 -1
  35. package/dist/plugins/token-stats/index.js.map +3 -3
  36. package/dist/plugins/user-auth/control.js.map +1 -1
  37. package/dist/plugins/user-auth/index.js.map +3 -3
  38. package/package.json +2 -2
@@ -2663,7 +2663,7 @@ class ResponsesWebSocketMetering {
2663
2663
  connection.active.delete(responseId);
2664
2664
  connection.finished.add(responseId);
2665
2665
  const usageComplete = count(usage?.input_tokens) && count(usage?.output_tokens);
2666
- await this.finish(response, event, type === "response.completed" && usageComplete ? "completed" : "failed", !usageComplete);
2666
+ await this.finish(response, event, type === "response.completed" && usageComplete ? "completed" : "failed", !usageComplete, type === "response.completed" ? body : undefined);
2667
2667
  if (connection.finished.size > MAX_TOMBSTONES)
2668
2668
  connection.finished.delete(connection.finished.values().next().value);
2669
2669
  }
@@ -2697,23 +2697,23 @@ class ResponsesWebSocketMetering {
2697
2697
  this.callbacks.prepareRequest(response.requestId);
2698
2698
  return await this.emit(response, event, { phase: "selected" }) && await this.emit(response, event, { phase: "request", url: event.upstreamUrl, body: { model: response.model, stream: true } });
2699
2699
  }
2700
- async emit(response, event, phase) {
2700
+ async emit(response, event, phase, settlement) {
2701
2701
  if (this.closed || !event.isActive()) {
2702
2702
  this.callbacks.discardRequest(response.requestId, response.attemptId);
2703
2703
  return false;
2704
2704
  }
2705
- await this.callbacks.emit({ requestId: response.requestId, attemptId: response.attemptId, keyId: event.keyId, routeId: event.routeId, upstreamId: event.upstreamId, isActive: event.isActive, ...phase });
2705
+ await this.callbacks.emit({ requestId: response.requestId, attemptId: response.attemptId, observedAtMonotonicMs: event.observedAtMonotonicMs, keyId: event.keyId, routeId: event.routeId, upstreamId: event.upstreamId, isActive: event.isActive, ...phase }, settlement);
2706
2706
  if (this.closed || !event.isActive()) {
2707
2707
  this.callbacks.discardRequest(response.requestId, response.attemptId);
2708
2708
  return false;
2709
2709
  }
2710
2710
  return true;
2711
2711
  }
2712
- async finish(response, event, outcome, incomplete) {
2712
+ async finish(response, event, outcome, incomplete, generationResponse) {
2713
2713
  this.responseCount--;
2714
- if (incomplete && !await this.emit(response, event, { phase: "incomplete", reason: "raw-response-incomplete" }))
2714
+ if (incomplete && !await this.emit(response, event, { phase: "incomplete", reason: "raw-response-incomplete" }, { accountingUsageIncomplete: generationResponse !== undefined }))
2715
2715
  return;
2716
- if (!await this.emit(response, event, { phase: "end", sent: true, outcome }))
2716
+ if (!await this.emit(response, event, { phase: "end", sent: true, outcome }, { generationResponse }))
2717
2717
  return;
2718
2718
  await this.emit(response, event, { phase: "request-end" });
2719
2719
  }
@@ -2741,6 +2741,93 @@ class ResponsesWebSocketMetering {
2741
2741
  }
2742
2742
  }
2743
2743
 
2744
+ // plugins/token-metering/server/generation-timing.ts
2745
+ var record4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2746
+ var text = (value) => typeof value === "string" && value.length > 0;
2747
+ var values = (value) => Array.isArray(value) ? value : [];
2748
+ var parameters = (value) => text(value) || record4(value) && Object.keys(value).length > 0;
2749
+ var content = (value) => text(value) || values(value).some((part) => record4(part) && (text(part.text) || text(part.thinking)));
2750
+ function generationSignals(provider, body, event) {
2751
+ const type = event || body.type;
2752
+ if (provider === "openai") {
2753
+ const choices = values(body.choices).filter(record4);
2754
+ const output = choices.some((choice) => text(choice.text) || record4(choice.delta) && (content(choice.delta.content) || text(choice.delta.reasoning_content) || text(choice.delta.reasoning) || record4(choice.delta.function_call) && text(choice.delta.function_call.arguments) || values(choice.delta.tool_calls).some((tool) => record4(tool) && record4(tool.function) && text(tool.function.arguments)))) || typeof type === "string" && [
2755
+ "response.output_text.delta",
2756
+ "response.reasoning_text.delta",
2757
+ "response.reasoning_summary_text.delta",
2758
+ "response.function_call_arguments.delta",
2759
+ "response.refusal.delta"
2760
+ ].includes(type) && text(body.delta);
2761
+ return { output, completed: type === "response.completed" || choices.some((choice) => choice.finish_reason !== undefined && choice.finish_reason !== null) };
2762
+ }
2763
+ if (provider === "anthropic") {
2764
+ const delta = record4(body.delta) ? body.delta : undefined;
2765
+ const block = type === "content_block_start" && record4(body.content_block) ? body.content_block : undefined;
2766
+ return {
2767
+ output: type === "content_block_delta" && !!delta && (delta.type === "text_delta" && text(delta.text) || delta.type === "thinking_delta" && text(delta.thinking) || delta.type === "input_json_delta" && text(delta.partial_json)) || !!block && (text(block.text) || text(block.thinking) || block.type === "tool_use" && parameters(block.input)),
2768
+ completed: type === "message_stop" || type === "message_delta" && !!delta && text(delta.stop_reason)
2769
+ };
2770
+ }
2771
+ const candidates = values(body.candidates).filter(record4);
2772
+ return {
2773
+ output: candidates.some((candidate) => record4(candidate.content) && values(candidate.content.parts).some((part) => record4(part) && (text(part.text) || record4(part.functionCall) && parameters(part.functionCall.args)))),
2774
+ completed: candidates.some((candidate) => text(candidate.finishReason))
2775
+ };
2776
+ }
2777
+
2778
+ class GenerationTiming {
2779
+ firstOutput;
2780
+ completedAt;
2781
+ terminal = false;
2782
+ missingTime = false;
2783
+ failed = false;
2784
+ branches = new Map;
2785
+ observe(provider, body, at, event) {
2786
+ const type = event || body.type;
2787
+ const response = record4(body.response) ? body.response : undefined;
2788
+ this.failed ||= body.error != null || response?.error != null || type === "error" || type === "response.failed" || type === "response.incomplete" || response?.status === "failed" || response?.status === "incomplete";
2789
+ if (this.terminal)
2790
+ return;
2791
+ const signal = generationSignals(provider, body, event);
2792
+ const valid = at !== undefined && Number.isFinite(at);
2793
+ if (signal.output && this.firstOutput === undefined) {
2794
+ if (valid)
2795
+ this.firstOutput = at;
2796
+ else
2797
+ this.missingTime = true;
2798
+ }
2799
+ const branches = provider === "openai" ? values(body.choices) : provider === "gemini" ? values(body.candidates) : [];
2800
+ if (branches.length) {
2801
+ for (const [position, branch] of branches.entries()) {
2802
+ if (!record4(branch))
2803
+ continue;
2804
+ const id = Number.isSafeInteger(branch.index) && branch.index >= 0 ? branch.index : position;
2805
+ const completed = provider === "openai" ? text(branch.finish_reason) : text(branch.finishReason);
2806
+ if (completed && !valid)
2807
+ this.missingTime = true;
2808
+ const previous = this.branches.get(id);
2809
+ this.branches.set(id, completed ? { completed: true, at } : previous ?? { completed: false });
2810
+ }
2811
+ } else if (signal.completed) {
2812
+ this.terminal = true;
2813
+ if (valid)
2814
+ this.completedAt = at;
2815
+ else
2816
+ this.missingTime = true;
2817
+ }
2818
+ }
2819
+ duration(endedAt) {
2820
+ if (this.failed || this.missingTime || this.firstOutput === undefined)
2821
+ return;
2822
+ const completedBranches = this.branches.size > 0 && [...this.branches.values()].every((branch) => branch.completed);
2823
+ const end = this.terminal ? this.completedAt : completedBranches ? Math.max(...[...this.branches.values()].map((branch) => branch.at)) : endedAt;
2824
+ if (end === undefined || !Number.isFinite(end))
2825
+ return;
2826
+ const duration = end - this.firstOutput;
2827
+ return Number.isFinite(duration) && duration > 0 ? duration : undefined;
2828
+ }
2829
+ }
2830
+
2744
2831
  // plugins/token-metering/server/index.ts
2745
2832
  var MAX_ACTIVE_ATTEMPTS = 1024;
2746
2833
  var LOG_INTERVAL_MS = 60000;
@@ -2773,6 +2860,7 @@ function createAttempt(event) {
2773
2860
  keyId: event.keyId ?? null,
2774
2861
  routeId: event.routeId || "unknown",
2775
2862
  upstreamId: event.upstreamId || "unknown",
2863
+ timing: new GenerationTiming,
2776
2864
  provider: "unknown",
2777
2865
  sent: false,
2778
2866
  llm: false,
@@ -2780,6 +2868,7 @@ function createAttempt(event) {
2780
2868
  responseSeen: false,
2781
2869
  responseFailed: false,
2782
2870
  observationIncomplete: false,
2871
+ generationObservationIncomplete: false,
2783
2872
  incompleteReasonLogged: false
2784
2873
  };
2785
2874
  }
@@ -2873,6 +2962,34 @@ function finalOutcome(input, canonicalOutcome, officialComplete = false) {
2873
2962
  }
2874
2963
  function finalizeAttempt(input) {
2875
2964
  let event = input.observedEvent;
2965
+ const generationComplete = (input.generationCompleted ?? input.endOutcome === "completed") && input.responseSeen && !input.responseFailed && !(input.generationObservationIncomplete ?? input.observationIncomplete) && input.generationDurationMs !== undefined;
2966
+ let generationOutput;
2967
+ if (generationComplete) {
2968
+ try {
2969
+ let measured;
2970
+ if (input.generationResponse) {
2971
+ const measurement = createTokenAccountingSession({
2972
+ provider: "openai",
2973
+ model: input.model,
2974
+ requestId: input.requestId,
2975
+ attemptId: input.attemptId,
2976
+ routeId: input.routeId,
2977
+ upstreamId: input.upstreamId,
2978
+ streaming: true
2979
+ }, { deferFinalization: true });
2980
+ measurement.consumeStreamChunk({ chunk: input.generationResponse });
2981
+ measured = measurement.finalizeCompletedStream();
2982
+ } else
2983
+ measured = input.session?.finalizeCompletedStream();
2984
+ if (measured) {
2985
+ assertCanonicalTokenAccountingEventV2(measured);
2986
+ const output = input.generationResponse ? measured : mergeOfficialUsage({ ...measured }, event);
2987
+ if (output.outputTokens !== undefined && ["official", "local", "heuristic"].includes(output.outputAuthority)) {
2988
+ generationOutput = Object.freeze({ tokens: output.outputTokens, source: output.outputAuthority === "official" ? "official" : "estimated" });
2989
+ }
2990
+ }
2991
+ } catch {}
2992
+ }
2876
2993
  try {
2877
2994
  const finalized = input.endOutcome === "completed" && input.responseSeen && !input.responseFailed && !input.observationIncomplete ? input.session?.finalizeCompletedStream() : input.session?.finalizeAbortedStream();
2878
2995
  if (finalized) {
@@ -2914,7 +3031,8 @@ function finalizeAttempt(input) {
2914
3031
  observationIncomplete: input.observationIncomplete,
2915
3032
  complete: outcome === "completed" && event?.inputTokens !== undefined && event?.outputTokens !== undefined && event.inputAuthority !== "partial" && event.outputAuthority !== "partial" && (!input.observationIncomplete || event.inputAuthority === "official" && event.outputAuthority === "official"),
2916
3033
  finishedAtMs: input.finishedAtMs,
2917
- settlementVersion: 1
3034
+ settlementVersion: 1,
3035
+ ...generationComplete ? { generationDurationMs: input.generationDurationMs, generationOutput } : {}
2918
3036
  });
2919
3037
  }
2920
3038
  var TokenMeteringPlugin = definePlugin(class {
@@ -2929,7 +3047,7 @@ var TokenMeteringPlugin = definePlugin(class {
2929
3047
  requiredPending = new Map;
2930
3048
  closed = false;
2931
3049
  websocket = new ResponsesWebSocketMetering({
2932
- emit: (event) => this.handleAttemptObservation(event, true),
3050
+ emit: (event, settlement) => this.handleAttemptObservation(event, true, settlement),
2933
3051
  prepareRequest: (requestId) => this.prepareRequest(requestId),
2934
3052
  discardRequest: (requestId, attemptId) => {
2935
3053
  this.attempts.delete(attemptId);
@@ -3063,7 +3181,7 @@ var TokenMeteringPlugin = definePlugin(class {
3063
3181
  });
3064
3182
  }
3065
3183
  }
3066
- async handleAttemptObservation(event, websocket = false) {
3184
+ async handleAttemptObservation(event, websocket = false, settlement = {}) {
3067
3185
  if (!event.isActive()) {
3068
3186
  this.attempts.delete(event.attemptId);
3069
3187
  return;
@@ -3126,7 +3244,7 @@ var TokenMeteringPlugin = definePlugin(class {
3126
3244
  attempt.session?.consumeRequest({ body: { model } });
3127
3245
  }
3128
3246
  }
3129
- attempt.responseFailed ||= event.status >= 400 || body.error !== undefined;
3247
+ attempt.responseFailed ||= event.status >= 400 || body.error != null;
3130
3248
  const provider = attempt.provider === "unknown" ? classifyResponse(body, attempt.llm, event.envelope?.event) : attempt.provider;
3131
3249
  if (!provider)
3132
3250
  return;
@@ -3138,6 +3256,8 @@ var TokenMeteringPlugin = definePlugin(class {
3138
3256
  attempt.streaming = event.protocol === "sse";
3139
3257
  ensureSession(attempt, provider);
3140
3258
  }
3259
+ if (event.protocol === "sse")
3260
+ attempt.timing.observe(provider, body, event.observedAtMonotonicMs, event.envelope?.event);
3141
3261
  const session = attempt.session;
3142
3262
  if (!session)
3143
3263
  return;
@@ -3151,6 +3271,7 @@ var TokenMeteringPlugin = definePlugin(class {
3151
3271
  attempt.latestEvent = attempt.responseFailed ? officialOnly(merged) : merged;
3152
3272
  } catch (error) {
3153
3273
  attempt.observationIncomplete = true;
3274
+ attempt.generationObservationIncomplete = true;
3154
3275
  if (attempt.latestEvent)
3155
3276
  attempt.latestEvent = officialOnly(attempt.latestEvent);
3156
3277
  this.logger.debug("Failed to consume token metering attempt observation", { attemptId: event.attemptId, error });
@@ -3158,6 +3279,8 @@ var TokenMeteringPlugin = definePlugin(class {
3158
3279
  return;
3159
3280
  }
3160
3281
  if (event.phase === "incomplete") {
3282
+ if (!settlement.accountingUsageIncomplete)
3283
+ attempt.generationObservationIncomplete = true;
3161
3284
  if (!attempt.observationIncomplete) {
3162
3285
  attempt.observationIncomplete = true;
3163
3286
  if (!attempt.incompleteReasonLogged) {
@@ -3192,6 +3315,10 @@ var TokenMeteringPlugin = definePlugin(class {
3192
3315
  observedEvent: attempt.latestEvent ? { ...attempt.latestEvent } : undefined,
3193
3316
  finishedAtMs: Date.now(),
3194
3317
  endOutcome: event.outcome,
3318
+ generationDurationMs: attempt.timing.duration(event.observedAtMonotonicMs),
3319
+ generationCompleted: websocket ? settlement.generationResponse !== undefined : event.outcome === "completed",
3320
+ generationResponse: settlement.generationResponse,
3321
+ generationObservationIncomplete: attempt.generationObservationIncomplete,
3195
3322
  responseSeen: attempt.responseSeen,
3196
3323
  responseFailed: attempt.responseFailed,
3197
3324
  observationIncomplete: attempt.observationIncomplete
@@ -3216,4 +3343,4 @@ export {
3216
3343
  server_default as default
3217
3344
  };
3218
3345
 
3219
- //# debugId=6E560425A55B5FB164756E2164756E21
3346
+ //# debugId=DB115B0B5828A5AC64756E2164756E21