@jaralabs/codex-bridge 0.4.3 → 0.4.5

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/README.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @jaralabs/codex-bridge
2
2
 
3
+ > **Disclaimer.** This project is not affiliated with, endorsed by, or
4
+ > sponsored by OpenAI. It is an independent tool that reuses the OAuth
5
+ > session written by the official Codex CLI to `~/.codex/auth.json` and
6
+ > speaks the same upstream endpoint (`chatgpt.com/backend-api/codex/responses`)
7
+ > that the CLI itself uses. The endpoint is not part of any published OpenAI
8
+ > API, so the bridge can stop working the day OpenAI rotates it. Use it as
9
+ > a developer convenience for your own account, not as a service you sell
10
+ > or redistribute. The authors and contributors are not responsible for
11
+ > account actions taken by OpenAI as a result of how you use this tool.
12
+
3
13
  > Use your ChatGPT Business / Team / Plus subscription from OpenCode instead
4
14
  > of paying for the OpenAI API.
5
15
 
@@ -31,6 +31,15 @@ const state = {
31
31
  lastError: null,
32
32
  lastSuccessAt: 0,
33
33
  lastFailureAt: 0,
34
+ // token usage accumulated since process start. The bridge reads
35
+ // `usage` (input_tokens / output_tokens / total_tokens) out of every
36
+ // successful Responses API response and sums it here. This is the
37
+ // closest thing the bridge has to a "credits used" counter: the
38
+ // ChatGPT subscription does not expose a per-account quota, so
39
+ // the only observable usage is what flows through the proxy.
40
+ promptTokens: 0,
41
+ completionTokens: 0,
42
+ totalTokens: 0,
34
43
  // circuit breaker
35
44
  failureTimes: [], // timestamps of recent failures
36
45
  circuitOpenedAt: 0, // 0 = closed; >0 = open since this timestamp
@@ -116,11 +125,33 @@ export function metrics() {
116
125
  lastError: state.lastError,
117
126
  lastSuccessAt: state.lastSuccessAt,
118
127
  lastFailureAt: state.lastFailureAt,
128
+ tokens: {
129
+ prompt: state.promptTokens,
130
+ completion: state.completionTokens,
131
+ total: state.totalTokens,
132
+ },
119
133
  circuit: circuitStatus(),
120
134
  circuitFailuresInWindow: state.failureTimes.length,
121
135
  };
122
136
  }
123
137
 
138
+ // Sum the token counts from a successful Responses API response
139
+ // into the in-memory counters. The wire shape is
140
+ // `{ input_tokens, output_tokens, total_tokens }`; missing fields
141
+ // default to 0. Idempotent and safe to call multiple times for the
142
+ // same response (callers should only call once per success).
143
+ export function recordUsage(usage) {
144
+ if (!usage || typeof usage !== "object") return;
145
+ const prompt = Number.isFinite(usage.input_tokens) ? usage.input_tokens : 0;
146
+ const completion = Number.isFinite(usage.output_tokens) ? usage.output_tokens : 0;
147
+ const total = Number.isFinite(usage.total_tokens)
148
+ ? usage.total_tokens
149
+ : prompt + completion;
150
+ state.promptTokens += prompt;
151
+ state.completionTokens += completion;
152
+ state.totalTokens += total;
153
+ }
154
+
124
155
  // Wrap an async function with retry + circuit breaker semantics.
125
156
  // fn() must throw an Error with .status attached when the response
126
157
  // is a retryable HTTP error. onRetry(attempt, delayMs, err) is
package/lib/server.mjs CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  import {
31
31
  withResilience,
32
32
  metrics as resilienceMetrics,
33
+ recordUsage,
33
34
  } from "./resilience.mjs";
34
35
  import { probeCapabilities } from "./capabilities.mjs";
35
36
 
@@ -440,6 +441,13 @@ async function handleChatCompletions(payload, req, res) {
440
441
  const id = genId("chatcmpl");
441
442
  const created = now();
442
443
  const usage = toUsage(result.usage);
444
+ // Sum the token usage of this successful turn into the in-memory
445
+ // counters that /v1/health exposes. We record once per turn (here,
446
+ // after the upstream Responses API returned a `response.completed`
447
+ // event with a `usage` block), so streaming and JSON responses are
448
+ // counted identically. Multi-turn tool-call loops in the same
449
+ // request accumulate multiple `recordUsage` calls.
450
+ if (result.usage) recordUsage(result.usage);
443
451
  const hasToolCalls = result.toolCalls && result.toolCalls.length > 0;
444
452
  const finishReason = hasToolCalls ? "tool_calls" : "stop";
445
453
  const message = { role: "assistant", content: result.text || "" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaralabs/codex-bridge",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "private": false,
5
5
  "description": "OpenAI-compatible HTTP bridge that reuses a local ChatGPT Business / Team / Plus session via codex, so OpenCode can list and call those models without paying for the OpenAI API.",
6
6
  "type": "module",