proxitor 0.6.2 → 0.8.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/README.md +18 -0
- package/dist/cli.mjs +118 -15
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -256,6 +256,24 @@ provider:
|
|
|
256
256
|
curl http://localhost:8828/health
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
+
### Cache usage logging
|
|
260
|
+
|
|
261
|
+
Proxitor automatically logs cache token usage from upstream responses — both non-streaming JSON and streaming SSE. No configuration needed.
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
[abc123] Cache read: 50000, write: 25000 tokens
|
|
265
|
+
[def456] Cache: no cached tokens
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Supports both provider formats:
|
|
269
|
+
|
|
270
|
+
| Provider format | Fields |
|
|
271
|
+
|---|---|
|
|
272
|
+
| Anthropic | `usage.cache_read_input_tokens` / `usage.cache_creation_input_tokens` |
|
|
273
|
+
| OpenAI / OpenRouter | `usage.prompt_tokens_details.cached_tokens` / `cache_write_tokens` |
|
|
274
|
+
|
|
275
|
+
When both formats are present (e.g., OpenRouter relaying an Anthropic response), Anthropic fields take priority.
|
|
276
|
+
|
|
259
277
|
---
|
|
260
278
|
|
|
261
279
|
## Interactive Config Manager
|
package/dist/cli.mjs
CHANGED
|
@@ -10594,7 +10594,7 @@ const proxyConfigSchema = object({
|
|
|
10594
10594
|
verbose: boolean().default(false),
|
|
10595
10595
|
bodyLimit: string$1().min(1).default("50mb"),
|
|
10596
10596
|
provider: providerConfigSchema.optional(),
|
|
10597
|
-
attributionReferer: string$1().min(1).default("
|
|
10597
|
+
attributionReferer: string$1().min(1).default("https://github.com/neiromaster/proxitor"),
|
|
10598
10598
|
attributionTitle: string$1().min(1).default("proxitor"),
|
|
10599
10599
|
headers: record(string$1(), string$1()).optional(),
|
|
10600
10600
|
modelOverrides: record(string$1().min(1), modelOverrideSchema).optional()
|
|
@@ -14626,7 +14626,7 @@ function buildRequestHeaders(incoming, config, inject, extraHeaders) {
|
|
|
14626
14626
|
const headers = filterHeaders(incoming, STRIP_REQUEST);
|
|
14627
14627
|
headers.Authorization = formatAuthHeader(config.openrouterKey, config.authType);
|
|
14628
14628
|
headers["HTTP-Referer"] = config.attributionReferer;
|
|
14629
|
-
headers["X-Title"] = config.attributionTitle;
|
|
14629
|
+
headers["X-OpenRouter-Title"] = config.attributionTitle;
|
|
14630
14630
|
headers["Accept-Encoding"] = "identity";
|
|
14631
14631
|
if (extraHeaders) Object.assign(headers, extraHeaders);
|
|
14632
14632
|
if (inject) headers["Content-Type"] = "application/json";
|
|
@@ -14640,6 +14640,120 @@ function buildResponseHeaders(from) {
|
|
|
14640
14640
|
return headers;
|
|
14641
14641
|
}
|
|
14642
14642
|
//#endregion
|
|
14643
|
+
//#region src/proxy/cache-logging.ts
|
|
14644
|
+
function extractCacheUsage(bodyText) {
|
|
14645
|
+
try {
|
|
14646
|
+
const parsed = JSON.parse(bodyText);
|
|
14647
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
14648
|
+
const usage = parsed.usage;
|
|
14649
|
+
if (typeof usage !== "object" || usage === null) return void 0;
|
|
14650
|
+
const result = {
|
|
14651
|
+
cacheRead: 0,
|
|
14652
|
+
cacheCreate: 0
|
|
14653
|
+
};
|
|
14654
|
+
if (typeof usage.cache_read_input_tokens === "number") result.cacheRead = usage.cache_read_input_tokens;
|
|
14655
|
+
if (typeof usage.cache_creation_input_tokens === "number") result.cacheCreate = usage.cache_creation_input_tokens;
|
|
14656
|
+
const details = usage.prompt_tokens_details;
|
|
14657
|
+
if (typeof details === "object" && details !== null) {
|
|
14658
|
+
if (typeof details.cached_tokens === "number" && details.cached_tokens > 0 && result.cacheRead === 0) result.cacheRead = details.cached_tokens;
|
|
14659
|
+
if (typeof details.cache_write_tokens === "number" && details.cache_write_tokens > 0 && result.cacheCreate === 0) result.cacheCreate = details.cache_write_tokens;
|
|
14660
|
+
}
|
|
14661
|
+
return result;
|
|
14662
|
+
} catch {
|
|
14663
|
+
return;
|
|
14664
|
+
}
|
|
14665
|
+
}
|
|
14666
|
+
function applyAnthropicFields(u, result) {
|
|
14667
|
+
let found = false;
|
|
14668
|
+
if (typeof u.cache_read_input_tokens === "number" && u.cache_read_input_tokens > 0) {
|
|
14669
|
+
result.cacheRead = u.cache_read_input_tokens;
|
|
14670
|
+
found = true;
|
|
14671
|
+
}
|
|
14672
|
+
if (typeof u.cache_creation_input_tokens === "number" && u.cache_creation_input_tokens > 0) {
|
|
14673
|
+
result.cacheCreate = u.cache_creation_input_tokens;
|
|
14674
|
+
found = true;
|
|
14675
|
+
}
|
|
14676
|
+
return found;
|
|
14677
|
+
}
|
|
14678
|
+
function applyOpenAIFields(details, result) {
|
|
14679
|
+
let found = false;
|
|
14680
|
+
if (typeof details.cached_tokens === "number" && details.cached_tokens > 0) {
|
|
14681
|
+
result.cacheRead = details.cached_tokens;
|
|
14682
|
+
found = true;
|
|
14683
|
+
}
|
|
14684
|
+
if (typeof details.cache_write_tokens === "number" && details.cache_write_tokens > 0) {
|
|
14685
|
+
result.cacheCreate = details.cache_write_tokens;
|
|
14686
|
+
found = true;
|
|
14687
|
+
}
|
|
14688
|
+
return found;
|
|
14689
|
+
}
|
|
14690
|
+
function extractFromEvent(parsed, result) {
|
|
14691
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
14692
|
+
const usage = (parsed.message ?? parsed).usage;
|
|
14693
|
+
if (typeof usage !== "object" || usage === null) return false;
|
|
14694
|
+
const u = usage;
|
|
14695
|
+
let found = false;
|
|
14696
|
+
found = applyAnthropicFields(u, result) || found;
|
|
14697
|
+
const details = u.prompt_tokens_details;
|
|
14698
|
+
if (typeof details === "object" && details !== null) found = applyOpenAIFields(details, result) || found;
|
|
14699
|
+
return found;
|
|
14700
|
+
}
|
|
14701
|
+
function extractCacheUsageFromSSE(fullText) {
|
|
14702
|
+
const result = {
|
|
14703
|
+
cacheRead: 0,
|
|
14704
|
+
cacheCreate: 0
|
|
14705
|
+
};
|
|
14706
|
+
let found = false;
|
|
14707
|
+
for (const line of fullText.split("\n")) {
|
|
14708
|
+
if (!line.startsWith("data:")) continue;
|
|
14709
|
+
const payload = line.slice(5).trim();
|
|
14710
|
+
if (payload === "[DONE]") continue;
|
|
14711
|
+
try {
|
|
14712
|
+
if (extractFromEvent(JSON.parse(payload), result)) found = true;
|
|
14713
|
+
} catch {}
|
|
14714
|
+
}
|
|
14715
|
+
return found ? result : void 0;
|
|
14716
|
+
}
|
|
14717
|
+
function formatCacheUsage(usage, reqId) {
|
|
14718
|
+
const parts = [];
|
|
14719
|
+
if (usage.cacheRead > 0) parts.push(`read: ${usage.cacheRead}`);
|
|
14720
|
+
if (usage.cacheCreate > 0) parts.push(`write: ${usage.cacheCreate}`);
|
|
14721
|
+
logger.info(withReq(reqId, parts.length > 0 ? `Cache ${parts.join(", ")} tokens` : "Cache: no cached tokens"));
|
|
14722
|
+
}
|
|
14723
|
+
function createLoggingStream(contentType, reqId) {
|
|
14724
|
+
const chunks = [];
|
|
14725
|
+
return new TransformStream({
|
|
14726
|
+
transform(chunk, controller) {
|
|
14727
|
+
controller.enqueue(chunk);
|
|
14728
|
+
chunks.push(chunk);
|
|
14729
|
+
},
|
|
14730
|
+
flush() {
|
|
14731
|
+
try {
|
|
14732
|
+
const decoder = new TextDecoder();
|
|
14733
|
+
const text = chunks.reduce((acc, chunk) => acc + decoder.decode(chunk, { stream: true }), "") + decoder.decode();
|
|
14734
|
+
const usage = contentType.toLowerCase().includes("text/event-stream") ? extractCacheUsageFromSSE(text) : extractCacheUsage(text);
|
|
14735
|
+
if (usage) formatCacheUsage(usage, reqId);
|
|
14736
|
+
} catch (err) {
|
|
14737
|
+
logger.debug(withReq(reqId, `Cache usage extraction failed: ${err instanceof Error ? err.message : err}`));
|
|
14738
|
+
}
|
|
14739
|
+
}
|
|
14740
|
+
});
|
|
14741
|
+
}
|
|
14742
|
+
function buildUpstreamResponseWithLogging(upstream, method, reqId) {
|
|
14743
|
+
const headers = buildResponseHeaders(upstream.headers);
|
|
14744
|
+
if (method === "HEAD" || !upstream.body) return new Response(null, {
|
|
14745
|
+
status: upstream.status,
|
|
14746
|
+
headers
|
|
14747
|
+
});
|
|
14748
|
+
const contentType = upstream.headers.get("content-type") ?? "";
|
|
14749
|
+
const lower = contentType.toLowerCase();
|
|
14750
|
+
const body = lower.includes("application/json") || lower.includes("text/event-stream") ? upstream.body.pipeThrough(createLoggingStream(contentType, reqId)) : upstream.body;
|
|
14751
|
+
return new Response(body, {
|
|
14752
|
+
status: upstream.status,
|
|
14753
|
+
headers
|
|
14754
|
+
});
|
|
14755
|
+
}
|
|
14756
|
+
//#endregion
|
|
14643
14757
|
//#region src/proxy/inject.ts
|
|
14644
14758
|
/** Extract the model name from a raw request body. Returns undefined if not parseable or absent. */
|
|
14645
14759
|
function extractModel(rawBody) {
|
|
@@ -14705,17 +14819,6 @@ async function fetchUpstream(url, method, headers, body, signal) {
|
|
|
14705
14819
|
duplex: body ? "half" : void 0
|
|
14706
14820
|
});
|
|
14707
14821
|
}
|
|
14708
|
-
function buildUpstreamResponse(upstream, method) {
|
|
14709
|
-
const headers = buildResponseHeaders(upstream.headers);
|
|
14710
|
-
if (method === "HEAD" || !upstream.body) return new Response(null, {
|
|
14711
|
-
status: upstream.status,
|
|
14712
|
-
headers
|
|
14713
|
-
});
|
|
14714
|
-
return new Response(upstream.body, {
|
|
14715
|
-
status: upstream.status,
|
|
14716
|
-
headers
|
|
14717
|
-
});
|
|
14718
|
-
}
|
|
14719
14822
|
async function readRawBody(request, reqId) {
|
|
14720
14823
|
try {
|
|
14721
14824
|
return {
|
|
@@ -14832,7 +14935,7 @@ async function executeUpstream(upstreamUrl, method, headers, body, signal, path,
|
|
|
14832
14935
|
});
|
|
14833
14936
|
}
|
|
14834
14937
|
logger.info(withReq(reqId, `${method} ${path} ← ${upstream.status} (${Date.now() - startedAt}ms)`));
|
|
14835
|
-
return
|
|
14938
|
+
return buildUpstreamResponseWithLogging(upstream, method, reqId);
|
|
14836
14939
|
}
|
|
14837
14940
|
function createProxyServer(config, onReady) {
|
|
14838
14941
|
const app = new Hono();
|
|
@@ -14893,7 +14996,7 @@ function startProxyServer(config, onReady) {
|
|
|
14893
14996
|
}
|
|
14894
14997
|
//#endregion
|
|
14895
14998
|
//#region src/version.ts
|
|
14896
|
-
const version = "0.
|
|
14999
|
+
const version = "0.8.0";
|
|
14897
15000
|
//#endregion
|
|
14898
15001
|
//#region src/cli.ts
|
|
14899
15002
|
const argv = process.argv.slice(2);
|