@blockrun/clawrouter 0.12.66 → 0.12.67
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 +24 -0
- package/dist/cli.js +438 -330
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +438 -330
- package/dist/index.js.map +1 -1
- package/docs/clawrouter-vs-openrouter-llm-routing-comparison.md +24 -0
- package/package.json +1 -1
- package/scripts/reinstall.sh +7 -7
- package/scripts/update.sh +17 -12
package/dist/index.js
CHANGED
|
@@ -33378,8 +33378,8 @@ var BLOCKRUN_MODELS = [
|
|
|
33378
33378
|
id: "xai/grok-4-0709",
|
|
33379
33379
|
name: "Grok 4 (0709)",
|
|
33380
33380
|
version: "4-0709",
|
|
33381
|
-
inputPrice:
|
|
33382
|
-
outputPrice:
|
|
33381
|
+
inputPrice: 3,
|
|
33382
|
+
outputPrice: 15,
|
|
33383
33383
|
contextWindow: 131072,
|
|
33384
33384
|
maxOutput: 16384,
|
|
33385
33385
|
reasoning: true,
|
|
@@ -33437,8 +33437,8 @@ var BLOCKRUN_MODELS = [
|
|
|
33437
33437
|
id: "nvidia/kimi-k2.5",
|
|
33438
33438
|
name: "NVIDIA Kimi K2.5",
|
|
33439
33439
|
version: "k2.5",
|
|
33440
|
-
inputPrice: 0.
|
|
33441
|
-
outputPrice:
|
|
33440
|
+
inputPrice: 0.6,
|
|
33441
|
+
outputPrice: 3,
|
|
33442
33442
|
contextWindow: 262144,
|
|
33443
33443
|
maxOutput: 16384,
|
|
33444
33444
|
toolCalling: true
|
|
@@ -33553,6 +33553,7 @@ var blockrunProvider = {
|
|
|
33553
33553
|
};
|
|
33554
33554
|
|
|
33555
33555
|
// src/proxy.ts
|
|
33556
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
33556
33557
|
import { createServer } from "http";
|
|
33557
33558
|
import { finished } from "stream";
|
|
33558
33559
|
import { homedir as homedir5 } from "os";
|
|
@@ -43494,13 +43495,18 @@ function getFallbackChain(tier, tierConfigs) {
|
|
|
43494
43495
|
const config = tierConfigs[tier];
|
|
43495
43496
|
return [config.primary, ...config.fallback];
|
|
43496
43497
|
}
|
|
43498
|
+
var SERVER_MARGIN_PERCENT = 5;
|
|
43499
|
+
var MIN_PAYMENT_USD = 1e-3;
|
|
43497
43500
|
function calculateModelCost(model, modelPricing, estimatedInputTokens, maxOutputTokens, routingProfile) {
|
|
43498
43501
|
const pricing = modelPricing.get(model);
|
|
43499
43502
|
const inputPrice = pricing?.inputPrice ?? 0;
|
|
43500
43503
|
const outputPrice = pricing?.outputPrice ?? 0;
|
|
43501
43504
|
const inputCost = estimatedInputTokens / 1e6 * inputPrice;
|
|
43502
43505
|
const outputCost = maxOutputTokens / 1e6 * outputPrice;
|
|
43503
|
-
const costEstimate =
|
|
43506
|
+
const costEstimate = Math.max(
|
|
43507
|
+
(inputCost + outputCost) * (1 + SERVER_MARGIN_PERCENT / 100),
|
|
43508
|
+
MIN_PAYMENT_USD
|
|
43509
|
+
);
|
|
43504
43510
|
const opusPricing = modelPricing.get(BASELINE_MODEL_ID);
|
|
43505
43511
|
const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;
|
|
43506
43512
|
const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;
|
|
@@ -47095,6 +47101,7 @@ ${lines.join("\n")}`;
|
|
|
47095
47101
|
};
|
|
47096
47102
|
|
|
47097
47103
|
// src/proxy.ts
|
|
47104
|
+
var paymentStore = new AsyncLocalStorage();
|
|
47098
47105
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
47099
47106
|
var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
|
|
47100
47107
|
var IMAGE_DIR = join8(homedir5(), ".openclaw", "blockrun", "images");
|
|
@@ -47712,7 +47719,21 @@ function estimateAmount(modelId, bodyLength, maxTokens) {
|
|
|
47712
47719
|
const amountMicros = Math.max(1e3, Math.ceil(costUsd * 1.2 * 1e6));
|
|
47713
47720
|
return amountMicros.toString();
|
|
47714
47721
|
}
|
|
47715
|
-
|
|
47722
|
+
var IMAGE_PRICING = {
|
|
47723
|
+
"openai/dall-e-3": { default: 0.04, sizes: { "1024x1024": 0.04, "1792x1024": 0.08, "1024x1792": 0.08 } },
|
|
47724
|
+
"openai/gpt-image-1": { default: 0.02, sizes: { "1024x1024": 0.02, "1536x1024": 0.04, "1024x1536": 0.04 } },
|
|
47725
|
+
"black-forest/flux-1.1-pro": { default: 0.04 },
|
|
47726
|
+
"google/nano-banana": { default: 0.05 },
|
|
47727
|
+
"google/nano-banana-pro": { default: 0.1, sizes: { "1024x1024": 0.1, "2048x2048": 0.1, "4096x4096": 0.15 } }
|
|
47728
|
+
};
|
|
47729
|
+
function estimateImageCost(model, size5, n = 1) {
|
|
47730
|
+
const pricing = IMAGE_PRICING[model];
|
|
47731
|
+
if (!pricing) return 0.04 * n * 1.05;
|
|
47732
|
+
const sizePrice = size5 && pricing.sizes ? pricing.sizes[size5] : void 0;
|
|
47733
|
+
const pricePerImage = sizePrice ?? pricing.default;
|
|
47734
|
+
return pricePerImage * n * 1.05;
|
|
47735
|
+
}
|
|
47736
|
+
async function proxyPartnerRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
|
|
47716
47737
|
const startTime = Date.now();
|
|
47717
47738
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
47718
47739
|
const bodyChunks = [];
|
|
@@ -47749,13 +47770,13 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
|
|
|
47749
47770
|
res.end();
|
|
47750
47771
|
const latencyMs = Date.now() - startTime;
|
|
47751
47772
|
console.log(`[ClawRouter] Partner response: ${upstream.status} (${latencyMs}ms)`);
|
|
47773
|
+
const partnerCost = getActualPaymentUsd();
|
|
47752
47774
|
logUsage({
|
|
47753
47775
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47754
47776
|
model: "partner",
|
|
47755
47777
|
tier: "PARTNER",
|
|
47756
|
-
cost:
|
|
47757
|
-
|
|
47758
|
-
baselineCost: 0,
|
|
47778
|
+
cost: partnerCost,
|
|
47779
|
+
baselineCost: partnerCost,
|
|
47759
47780
|
savings: 0,
|
|
47760
47781
|
latencyMs,
|
|
47761
47782
|
partnerId: (req.url?.split("?")[0] ?? "").replace(/^\/v1\//, "").replace(/\//g, "_") || "unknown",
|
|
@@ -47888,7 +47909,11 @@ async function startProxy(options) {
|
|
|
47888
47909
|
x402.onAfterPaymentCreation(async (context) => {
|
|
47889
47910
|
const network = context.selectedRequirements.network;
|
|
47890
47911
|
const chain3 = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
|
|
47891
|
-
|
|
47912
|
+
const amountMicros = parseInt(context.selectedRequirements.amount || "0", 10);
|
|
47913
|
+
const amountUsd = amountMicros / 1e6;
|
|
47914
|
+
const store = paymentStore.getStore();
|
|
47915
|
+
if (store) store.amountUsd = amountUsd;
|
|
47916
|
+
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
47892
47917
|
});
|
|
47893
47918
|
const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
47894
47919
|
skipPreAuth: paymentChain === "solana"
|
|
@@ -47913,311 +47938,382 @@ async function startProxy(options) {
|
|
|
47913
47938
|
const sessionStore = new SessionStore(options.sessionConfig);
|
|
47914
47939
|
const sessionJournal = new SessionJournal();
|
|
47915
47940
|
const connections = /* @__PURE__ */ new Set();
|
|
47916
|
-
const server = createServer(
|
|
47917
|
-
|
|
47918
|
-
|
|
47919
|
-
|
|
47920
|
-
|
|
47921
|
-
|
|
47922
|
-
|
|
47923
|
-
|
|
47924
|
-
|
|
47925
|
-
|
|
47926
|
-
|
|
47927
|
-
});
|
|
47928
|
-
finished(req, (err) => {
|
|
47929
|
-
if (err && err.code !== "ERR_STREAM_DESTROYED") {
|
|
47930
|
-
console.error(`[ClawRouter] Request finished with error: ${err.message}`);
|
|
47931
|
-
}
|
|
47932
|
-
});
|
|
47933
|
-
if (req.url === "/health" || req.url?.startsWith("/health?")) {
|
|
47934
|
-
const url = new URL(req.url, "http://localhost");
|
|
47935
|
-
const full = url.searchParams.get("full") === "true";
|
|
47936
|
-
const response = {
|
|
47937
|
-
status: "ok",
|
|
47938
|
-
wallet: account.address,
|
|
47939
|
-
paymentChain
|
|
47940
|
-
};
|
|
47941
|
-
if (solanaAddress) {
|
|
47942
|
-
response.solana = solanaAddress;
|
|
47943
|
-
}
|
|
47944
|
-
if (full) {
|
|
47945
|
-
try {
|
|
47946
|
-
const balanceInfo = await balanceMonitor.checkBalance();
|
|
47947
|
-
response.balance = balanceInfo.balanceUSD;
|
|
47948
|
-
response.isLow = balanceInfo.isLow;
|
|
47949
|
-
response.isEmpty = balanceInfo.isEmpty;
|
|
47950
|
-
} catch {
|
|
47951
|
-
response.balanceError = "Could not fetch balance";
|
|
47941
|
+
const server = createServer((req, res) => {
|
|
47942
|
+
paymentStore.run({ amountUsd: 0 }, async () => {
|
|
47943
|
+
req.on("error", (err) => {
|
|
47944
|
+
console.error(`[ClawRouter] Request stream error: ${err.message}`);
|
|
47945
|
+
});
|
|
47946
|
+
res.on("error", (err) => {
|
|
47947
|
+
console.error(`[ClawRouter] Response stream error: ${err.message}`);
|
|
47948
|
+
});
|
|
47949
|
+
finished(res, (err) => {
|
|
47950
|
+
if (err && err.code !== "ERR_STREAM_DESTROYED") {
|
|
47951
|
+
console.error(`[ClawRouter] Response finished with error: ${err.message}`);
|
|
47952
47952
|
}
|
|
47953
|
-
}
|
|
47954
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
47955
|
-
res.end(JSON.stringify(response));
|
|
47956
|
-
return;
|
|
47957
|
-
}
|
|
47958
|
-
if (req.url === "/cache" || req.url?.startsWith("/cache?")) {
|
|
47959
|
-
const stats = responseCache2.getStats();
|
|
47960
|
-
res.writeHead(200, {
|
|
47961
|
-
"Content-Type": "application/json",
|
|
47962
|
-
"Cache-Control": "no-cache"
|
|
47963
47953
|
});
|
|
47964
|
-
|
|
47965
|
-
|
|
47966
|
-
|
|
47967
|
-
|
|
47968
|
-
|
|
47969
|
-
|
|
47954
|
+
finished(req, (err) => {
|
|
47955
|
+
if (err && err.code !== "ERR_STREAM_DESTROYED") {
|
|
47956
|
+
console.error(`[ClawRouter] Request finished with error: ${err.message}`);
|
|
47957
|
+
}
|
|
47958
|
+
});
|
|
47959
|
+
if (req.url === "/health" || req.url?.startsWith("/health?")) {
|
|
47960
|
+
const url = new URL(req.url, "http://localhost");
|
|
47961
|
+
const full = url.searchParams.get("full") === "true";
|
|
47962
|
+
const response = {
|
|
47963
|
+
status: "ok",
|
|
47964
|
+
wallet: account.address,
|
|
47965
|
+
paymentChain
|
|
47966
|
+
};
|
|
47967
|
+
if (solanaAddress) {
|
|
47968
|
+
response.solana = solanaAddress;
|
|
47969
|
+
}
|
|
47970
|
+
if (full) {
|
|
47971
|
+
try {
|
|
47972
|
+
const balanceInfo = await balanceMonitor.checkBalance();
|
|
47973
|
+
response.balance = balanceInfo.balanceUSD;
|
|
47974
|
+
response.isLow = balanceInfo.isLow;
|
|
47975
|
+
response.isEmpty = balanceInfo.isEmpty;
|
|
47976
|
+
} catch {
|
|
47977
|
+
response.balanceError = "Could not fetch balance";
|
|
47978
|
+
}
|
|
47979
|
+
}
|
|
47970
47980
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
47971
|
-
res.end(JSON.stringify(
|
|
47972
|
-
|
|
47973
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
47974
|
-
res.end(
|
|
47975
|
-
JSON.stringify({
|
|
47976
|
-
error: `Failed to clear stats: ${err instanceof Error ? err.message : String(err)}`
|
|
47977
|
-
})
|
|
47978
|
-
);
|
|
47981
|
+
res.end(JSON.stringify(response));
|
|
47982
|
+
return;
|
|
47979
47983
|
}
|
|
47980
|
-
|
|
47981
|
-
|
|
47982
|
-
if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
|
|
47983
|
-
try {
|
|
47984
|
-
const url = new URL(req.url, "http://localhost");
|
|
47985
|
-
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
47986
|
-
const stats = await getStats(Math.min(days, 30));
|
|
47984
|
+
if (req.url === "/cache" || req.url?.startsWith("/cache?")) {
|
|
47985
|
+
const stats = responseCache2.getStats();
|
|
47987
47986
|
res.writeHead(200, {
|
|
47988
47987
|
"Content-Type": "application/json",
|
|
47989
47988
|
"Cache-Control": "no-cache"
|
|
47990
47989
|
});
|
|
47991
|
-
res.end(
|
|
47992
|
-
|
|
47993
|
-
{
|
|
47994
|
-
...stats,
|
|
47995
|
-
providerErrors: Object.fromEntries(perProviderErrors)
|
|
47996
|
-
},
|
|
47997
|
-
null,
|
|
47998
|
-
2
|
|
47999
|
-
)
|
|
48000
|
-
);
|
|
48001
|
-
} catch (err) {
|
|
48002
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
48003
|
-
res.end(
|
|
48004
|
-
JSON.stringify({
|
|
48005
|
-
error: `Failed to get stats: ${err instanceof Error ? err.message : String(err)}`
|
|
48006
|
-
})
|
|
48007
|
-
);
|
|
47990
|
+
res.end(JSON.stringify(stats, null, 2));
|
|
47991
|
+
return;
|
|
48008
47992
|
}
|
|
48009
|
-
|
|
48010
|
-
|
|
48011
|
-
|
|
48012
|
-
|
|
48013
|
-
|
|
48014
|
-
|
|
48015
|
-
|
|
48016
|
-
|
|
48017
|
-
|
|
48018
|
-
|
|
48019
|
-
|
|
48020
|
-
|
|
48021
|
-
|
|
47993
|
+
if (req.url === "/stats" && req.method === "DELETE") {
|
|
47994
|
+
try {
|
|
47995
|
+
const result = await clearStats();
|
|
47996
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
47997
|
+
res.end(JSON.stringify({ cleared: true, deletedFiles: result.deletedFiles }));
|
|
47998
|
+
} catch (err) {
|
|
47999
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
48000
|
+
res.end(
|
|
48001
|
+
JSON.stringify({
|
|
48002
|
+
error: `Failed to clear stats: ${err instanceof Error ? err.message : String(err)}`
|
|
48003
|
+
})
|
|
48004
|
+
);
|
|
48005
|
+
}
|
|
48022
48006
|
return;
|
|
48023
48007
|
}
|
|
48024
|
-
|
|
48025
|
-
|
|
48026
|
-
|
|
48027
|
-
|
|
48028
|
-
|
|
48029
|
-
|
|
48030
|
-
|
|
48031
|
-
|
|
48032
|
-
|
|
48033
|
-
|
|
48034
|
-
|
|
48035
|
-
|
|
48036
|
-
|
|
48037
|
-
|
|
48038
|
-
|
|
48039
|
-
|
|
48040
|
-
|
|
48041
|
-
|
|
48042
|
-
|
|
48043
|
-
|
|
48044
|
-
|
|
48008
|
+
if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
|
|
48009
|
+
try {
|
|
48010
|
+
const url = new URL(req.url, "http://localhost");
|
|
48011
|
+
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
48012
|
+
const stats = await getStats(Math.min(days, 30));
|
|
48013
|
+
res.writeHead(200, {
|
|
48014
|
+
"Content-Type": "application/json",
|
|
48015
|
+
"Cache-Control": "no-cache"
|
|
48016
|
+
});
|
|
48017
|
+
res.end(
|
|
48018
|
+
JSON.stringify(
|
|
48019
|
+
{
|
|
48020
|
+
...stats,
|
|
48021
|
+
providerErrors: Object.fromEntries(perProviderErrors)
|
|
48022
|
+
},
|
|
48023
|
+
null,
|
|
48024
|
+
2
|
|
48025
|
+
)
|
|
48026
|
+
);
|
|
48027
|
+
} catch (err) {
|
|
48028
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
48029
|
+
res.end(
|
|
48030
|
+
JSON.stringify({
|
|
48031
|
+
error: `Failed to get stats: ${err instanceof Error ? err.message : String(err)}`
|
|
48032
|
+
})
|
|
48033
|
+
);
|
|
48034
|
+
}
|
|
48035
|
+
return;
|
|
48045
48036
|
}
|
|
48046
|
-
|
|
48047
|
-
|
|
48048
|
-
|
|
48049
|
-
|
|
48050
|
-
|
|
48051
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
48037
|
+
if (req.url === "/v1/models" && req.method === "GET") {
|
|
48038
|
+
const models = buildProxyModelList();
|
|
48039
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
48040
|
+
res.end(JSON.stringify({ object: "list", data: models }));
|
|
48041
|
+
return;
|
|
48052
48042
|
}
|
|
48053
|
-
|
|
48054
|
-
|
|
48055
|
-
|
|
48056
|
-
|
|
48057
|
-
|
|
48058
|
-
body: reqBody
|
|
48059
|
-
});
|
|
48060
|
-
const text = await upstream.text();
|
|
48061
|
-
if (!upstream.ok) {
|
|
48062
|
-
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
48063
|
-
res.end(text);
|
|
48043
|
+
if (req.url?.startsWith("/images/") && req.method === "GET") {
|
|
48044
|
+
const filename = req.url.slice("/images/".length).split("?")[0].replace(/[^a-zA-Z0-9._-]/g, "");
|
|
48045
|
+
if (!filename) {
|
|
48046
|
+
res.writeHead(400);
|
|
48047
|
+
res.end("Bad request");
|
|
48064
48048
|
return;
|
|
48065
48049
|
}
|
|
48066
|
-
|
|
48050
|
+
const filePath = join8(IMAGE_DIR, filename);
|
|
48067
48051
|
try {
|
|
48068
|
-
|
|
48052
|
+
const s3 = await fsStat(filePath);
|
|
48053
|
+
if (!s3.isFile()) throw new Error("not a file");
|
|
48054
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
|
|
48055
|
+
const mime = {
|
|
48056
|
+
png: "image/png",
|
|
48057
|
+
jpg: "image/jpeg",
|
|
48058
|
+
jpeg: "image/jpeg",
|
|
48059
|
+
webp: "image/webp",
|
|
48060
|
+
gif: "image/gif"
|
|
48061
|
+
};
|
|
48062
|
+
const data = await readFile(filePath);
|
|
48063
|
+
res.writeHead(200, {
|
|
48064
|
+
"Content-Type": mime[ext] ?? "application/octet-stream",
|
|
48065
|
+
"Content-Length": data.length
|
|
48066
|
+
});
|
|
48067
|
+
res.end(data);
|
|
48069
48068
|
} catch {
|
|
48070
|
-
res.writeHead(
|
|
48071
|
-
res.end(
|
|
48072
|
-
return;
|
|
48069
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
48070
|
+
res.end(JSON.stringify({ error: "Image not found" }));
|
|
48073
48071
|
}
|
|
48074
|
-
|
|
48075
|
-
|
|
48076
|
-
|
|
48077
|
-
|
|
48078
|
-
|
|
48079
|
-
|
|
48080
|
-
|
|
48081
|
-
|
|
48082
|
-
|
|
48083
|
-
|
|
48084
|
-
|
|
48085
|
-
|
|
48086
|
-
|
|
48087
|
-
|
|
48088
|
-
|
|
48089
|
-
|
|
48090
|
-
|
|
48091
|
-
|
|
48092
|
-
|
|
48093
|
-
|
|
48094
|
-
|
|
48095
|
-
|
|
48096
|
-
|
|
48072
|
+
return;
|
|
48073
|
+
}
|
|
48074
|
+
if (req.url === "/v1/images/generations" && req.method === "POST") {
|
|
48075
|
+
const imgStartTime = Date.now();
|
|
48076
|
+
const chunks = [];
|
|
48077
|
+
for await (const chunk of req) {
|
|
48078
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
48079
|
+
}
|
|
48080
|
+
const reqBody = Buffer.concat(chunks);
|
|
48081
|
+
let imgModel = "unknown";
|
|
48082
|
+
let imgCost = 0;
|
|
48083
|
+
try {
|
|
48084
|
+
const parsed = JSON.parse(reqBody.toString());
|
|
48085
|
+
imgModel = parsed.model || "openai/dall-e-3";
|
|
48086
|
+
const n = parsed.n || 1;
|
|
48087
|
+
imgCost = estimateImageCost(imgModel, parsed.size, n);
|
|
48088
|
+
} catch {
|
|
48089
|
+
}
|
|
48090
|
+
try {
|
|
48091
|
+
const upstream = await payFetch(`${apiBase}/v1/images/generations`, {
|
|
48092
|
+
method: "POST",
|
|
48093
|
+
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
48094
|
+
body: reqBody
|
|
48095
|
+
});
|
|
48096
|
+
const text = await upstream.text();
|
|
48097
|
+
if (!upstream.ok) {
|
|
48098
|
+
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
48099
|
+
res.end(text);
|
|
48100
|
+
return;
|
|
48101
|
+
}
|
|
48102
|
+
let result;
|
|
48103
|
+
try {
|
|
48104
|
+
result = JSON.parse(text);
|
|
48105
|
+
} catch {
|
|
48106
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
48107
|
+
res.end(text);
|
|
48108
|
+
return;
|
|
48109
|
+
}
|
|
48110
|
+
if (result.data?.length) {
|
|
48111
|
+
await mkdir3(IMAGE_DIR, { recursive: true });
|
|
48112
|
+
const port2 = server.address()?.port ?? 8402;
|
|
48113
|
+
for (const img of result.data) {
|
|
48114
|
+
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
48115
|
+
if (dataUriMatch) {
|
|
48116
|
+
const [, mimeType, b64] = dataUriMatch;
|
|
48117
|
+
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
48118
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
48119
|
+
await writeFile2(join8(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
48120
|
+
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
48121
|
+
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
48122
|
+
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
48123
|
+
try {
|
|
48124
|
+
const imgResp = await fetch(img.url);
|
|
48125
|
+
if (imgResp.ok) {
|
|
48126
|
+
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
48127
|
+
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
48128
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
48129
|
+
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
48130
|
+
await writeFile2(join8(IMAGE_DIR, filename), buf);
|
|
48131
|
+
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
48132
|
+
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
48133
|
+
}
|
|
48134
|
+
} catch (downloadErr) {
|
|
48135
|
+
console.warn(
|
|
48136
|
+
`[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
|
|
48137
|
+
);
|
|
48097
48138
|
}
|
|
48098
|
-
} catch (downloadErr) {
|
|
48099
|
-
console.warn(
|
|
48100
|
-
`[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
|
|
48101
|
-
);
|
|
48102
48139
|
}
|
|
48103
48140
|
}
|
|
48104
48141
|
}
|
|
48105
|
-
|
|
48106
|
-
|
|
48107
|
-
|
|
48108
|
-
|
|
48109
|
-
|
|
48110
|
-
|
|
48111
|
-
|
|
48112
|
-
|
|
48113
|
-
|
|
48114
|
-
|
|
48115
|
-
|
|
48116
|
-
|
|
48117
|
-
|
|
48118
|
-
|
|
48119
|
-
|
|
48120
|
-
|
|
48121
|
-
|
|
48122
|
-
|
|
48123
|
-
|
|
48124
|
-
let reqBody;
|
|
48125
|
-
try {
|
|
48126
|
-
const parsed = JSON.parse(rawBody.toString());
|
|
48127
|
-
for (const field of ["image", "mask"]) {
|
|
48128
|
-
const val = parsed[field];
|
|
48129
|
-
if (typeof val !== "string" || !val) continue;
|
|
48130
|
-
if (val.startsWith("data:")) {
|
|
48131
|
-
} else if (val.startsWith("https://") || val.startsWith("http://")) {
|
|
48132
|
-
const imgResp = await fetch(val);
|
|
48133
|
-
if (!imgResp.ok)
|
|
48134
|
-
throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
|
|
48135
|
-
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
48136
|
-
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
48137
|
-
parsed[field] = `data:${contentType};base64,${buf.toString("base64")}`;
|
|
48138
|
-
console.log(
|
|
48139
|
-
`[ClawRouter] img2img: downloaded ${field} URL \u2192 data URI (${buf.length} bytes)`
|
|
48140
|
-
);
|
|
48141
|
-
} else {
|
|
48142
|
-
parsed[field] = readImageFileAsDataUri(val);
|
|
48143
|
-
console.log(`[ClawRouter] img2img: read ${field} file \u2192 data URI`);
|
|
48142
|
+
const imgActualCost = paymentStore.getStore()?.amountUsd ?? imgCost;
|
|
48143
|
+
logUsage({
|
|
48144
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
48145
|
+
model: imgModel,
|
|
48146
|
+
tier: "IMAGE",
|
|
48147
|
+
cost: imgActualCost,
|
|
48148
|
+
baselineCost: imgActualCost,
|
|
48149
|
+
savings: 0,
|
|
48150
|
+
latencyMs: Date.now() - imgStartTime
|
|
48151
|
+
}).catch(() => {
|
|
48152
|
+
});
|
|
48153
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
48154
|
+
res.end(JSON.stringify(result));
|
|
48155
|
+
} catch (err) {
|
|
48156
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
48157
|
+
console.error(`[ClawRouter] Image generation error: ${msg}`);
|
|
48158
|
+
if (!res.headersSent) {
|
|
48159
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
48160
|
+
res.end(JSON.stringify({ error: "Image generation failed", details: msg }));
|
|
48144
48161
|
}
|
|
48145
48162
|
}
|
|
48146
|
-
if (!parsed.model) parsed.model = "openai/gpt-image-1";
|
|
48147
|
-
reqBody = JSON.stringify(parsed);
|
|
48148
|
-
} catch (parseErr) {
|
|
48149
|
-
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
48150
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
48151
|
-
res.end(JSON.stringify({ error: "Invalid request", details: msg }));
|
|
48152
48163
|
return;
|
|
48153
48164
|
}
|
|
48154
|
-
|
|
48155
|
-
const
|
|
48156
|
-
|
|
48157
|
-
|
|
48158
|
-
|
|
48159
|
-
});
|
|
48160
|
-
const text = await upstream.text();
|
|
48161
|
-
if (!upstream.ok) {
|
|
48162
|
-
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
48163
|
-
res.end(text);
|
|
48164
|
-
return;
|
|
48165
|
+
if (req.url === "/v1/images/image2image" && req.method === "POST") {
|
|
48166
|
+
const img2imgStartTime = Date.now();
|
|
48167
|
+
const chunks = [];
|
|
48168
|
+
for await (const chunk of req) {
|
|
48169
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
48165
48170
|
}
|
|
48166
|
-
|
|
48171
|
+
const rawBody = Buffer.concat(chunks);
|
|
48172
|
+
let reqBody;
|
|
48173
|
+
let img2imgModel = "openai/gpt-image-1";
|
|
48174
|
+
let img2imgCost = 0;
|
|
48167
48175
|
try {
|
|
48168
|
-
|
|
48169
|
-
|
|
48170
|
-
|
|
48171
|
-
|
|
48176
|
+
const parsed = JSON.parse(rawBody.toString());
|
|
48177
|
+
for (const field of ["image", "mask"]) {
|
|
48178
|
+
const val = parsed[field];
|
|
48179
|
+
if (typeof val !== "string" || !val) continue;
|
|
48180
|
+
if (val.startsWith("data:")) {
|
|
48181
|
+
} else if (val.startsWith("https://") || val.startsWith("http://")) {
|
|
48182
|
+
const imgResp = await fetch(val);
|
|
48183
|
+
if (!imgResp.ok)
|
|
48184
|
+
throw new Error(`Failed to download ${field} from ${val}: HTTP ${imgResp.status}`);
|
|
48185
|
+
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
48186
|
+
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
48187
|
+
parsed[field] = `data:${contentType};base64,${buf.toString("base64")}`;
|
|
48188
|
+
console.log(
|
|
48189
|
+
`[ClawRouter] img2img: downloaded ${field} URL \u2192 data URI (${buf.length} bytes)`
|
|
48190
|
+
);
|
|
48191
|
+
} else {
|
|
48192
|
+
parsed[field] = readImageFileAsDataUri(val);
|
|
48193
|
+
console.log(`[ClawRouter] img2img: read ${field} file \u2192 data URI`);
|
|
48194
|
+
}
|
|
48195
|
+
}
|
|
48196
|
+
if (!parsed.model) parsed.model = "openai/gpt-image-1";
|
|
48197
|
+
img2imgModel = parsed.model;
|
|
48198
|
+
img2imgCost = estimateImageCost(img2imgModel, parsed.size, parsed.n || 1);
|
|
48199
|
+
reqBody = JSON.stringify(parsed);
|
|
48200
|
+
} catch (parseErr) {
|
|
48201
|
+
const msg = parseErr instanceof Error ? parseErr.message : String(parseErr);
|
|
48202
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
48203
|
+
res.end(JSON.stringify({ error: "Invalid request", details: msg }));
|
|
48172
48204
|
return;
|
|
48173
48205
|
}
|
|
48174
|
-
|
|
48175
|
-
await
|
|
48176
|
-
|
|
48177
|
-
|
|
48178
|
-
|
|
48179
|
-
|
|
48180
|
-
|
|
48181
|
-
|
|
48182
|
-
|
|
48183
|
-
|
|
48184
|
-
|
|
48185
|
-
|
|
48186
|
-
|
|
48187
|
-
|
|
48188
|
-
|
|
48189
|
-
|
|
48190
|
-
|
|
48191
|
-
|
|
48192
|
-
|
|
48193
|
-
|
|
48194
|
-
|
|
48195
|
-
|
|
48196
|
-
|
|
48206
|
+
try {
|
|
48207
|
+
const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
48208
|
+
method: "POST",
|
|
48209
|
+
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
48210
|
+
body: reqBody
|
|
48211
|
+
});
|
|
48212
|
+
const text = await upstream.text();
|
|
48213
|
+
if (!upstream.ok) {
|
|
48214
|
+
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
48215
|
+
res.end(text);
|
|
48216
|
+
return;
|
|
48217
|
+
}
|
|
48218
|
+
let result;
|
|
48219
|
+
try {
|
|
48220
|
+
result = JSON.parse(text);
|
|
48221
|
+
} catch {
|
|
48222
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
48223
|
+
res.end(text);
|
|
48224
|
+
return;
|
|
48225
|
+
}
|
|
48226
|
+
if (result.data?.length) {
|
|
48227
|
+
await mkdir3(IMAGE_DIR, { recursive: true });
|
|
48228
|
+
const port2 = server.address()?.port ?? 8402;
|
|
48229
|
+
for (const img of result.data) {
|
|
48230
|
+
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
48231
|
+
if (dataUriMatch) {
|
|
48232
|
+
const [, mimeType, b64] = dataUriMatch;
|
|
48233
|
+
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
48234
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
48235
|
+
await writeFile2(join8(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
48236
|
+
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
48237
|
+
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
48238
|
+
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
48239
|
+
try {
|
|
48240
|
+
const imgResp = await fetch(img.url);
|
|
48241
|
+
if (imgResp.ok) {
|
|
48242
|
+
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
48243
|
+
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
48244
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
48245
|
+
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
48246
|
+
await writeFile2(join8(IMAGE_DIR, filename), buf);
|
|
48247
|
+
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
48248
|
+
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
48249
|
+
}
|
|
48250
|
+
} catch (downloadErr) {
|
|
48251
|
+
console.warn(
|
|
48252
|
+
`[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
|
|
48253
|
+
);
|
|
48197
48254
|
}
|
|
48198
|
-
} catch (downloadErr) {
|
|
48199
|
-
console.warn(
|
|
48200
|
-
`[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
|
|
48201
|
-
);
|
|
48202
48255
|
}
|
|
48203
48256
|
}
|
|
48204
48257
|
}
|
|
48258
|
+
const img2imgActualCost = paymentStore.getStore()?.amountUsd ?? img2imgCost;
|
|
48259
|
+
logUsage({
|
|
48260
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
48261
|
+
model: img2imgModel,
|
|
48262
|
+
tier: "IMAGE",
|
|
48263
|
+
cost: img2imgActualCost,
|
|
48264
|
+
baselineCost: img2imgActualCost,
|
|
48265
|
+
savings: 0,
|
|
48266
|
+
latencyMs: Date.now() - img2imgStartTime
|
|
48267
|
+
}).catch(() => {
|
|
48268
|
+
});
|
|
48269
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
48270
|
+
res.end(JSON.stringify(result));
|
|
48271
|
+
} catch (err) {
|
|
48272
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
48273
|
+
console.error(`[ClawRouter] Image editing error: ${msg}`);
|
|
48274
|
+
if (!res.headersSent) {
|
|
48275
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
48276
|
+
res.end(JSON.stringify({ error: "Image editing failed", details: msg }));
|
|
48277
|
+
}
|
|
48205
48278
|
}
|
|
48206
|
-
|
|
48207
|
-
|
|
48208
|
-
|
|
48209
|
-
|
|
48210
|
-
|
|
48211
|
-
|
|
48212
|
-
|
|
48213
|
-
|
|
48279
|
+
return;
|
|
48280
|
+
}
|
|
48281
|
+
if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
|
|
48282
|
+
try {
|
|
48283
|
+
await proxyPartnerRequest(req, res, apiBase, payFetch, () => paymentStore.getStore()?.amountUsd ?? 0);
|
|
48284
|
+
} catch (err) {
|
|
48285
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
48286
|
+
options.onError?.(error);
|
|
48287
|
+
if (!res.headersSent) {
|
|
48288
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
48289
|
+
res.end(
|
|
48290
|
+
JSON.stringify({
|
|
48291
|
+
error: { message: `Partner proxy error: ${error.message}`, type: "partner_error" }
|
|
48292
|
+
})
|
|
48293
|
+
);
|
|
48294
|
+
}
|
|
48214
48295
|
}
|
|
48296
|
+
return;
|
|
48297
|
+
}
|
|
48298
|
+
if (!req.url?.startsWith("/v1")) {
|
|
48299
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
48300
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
48301
|
+
return;
|
|
48215
48302
|
}
|
|
48216
|
-
return;
|
|
48217
|
-
}
|
|
48218
|
-
if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
|
|
48219
48303
|
try {
|
|
48220
|
-
await
|
|
48304
|
+
await proxyRequest(
|
|
48305
|
+
req,
|
|
48306
|
+
res,
|
|
48307
|
+
apiBase,
|
|
48308
|
+
payFetch,
|
|
48309
|
+
options,
|
|
48310
|
+
routerOpts,
|
|
48311
|
+
deduplicator,
|
|
48312
|
+
balanceMonitor,
|
|
48313
|
+
sessionStore,
|
|
48314
|
+
responseCache2,
|
|
48315
|
+
sessionJournal
|
|
48316
|
+
);
|
|
48221
48317
|
} catch (err) {
|
|
48222
48318
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
48223
48319
|
options.onError?.(error);
|
|
@@ -48225,52 +48321,20 @@ async function startProxy(options) {
|
|
|
48225
48321
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
48226
48322
|
res.end(
|
|
48227
48323
|
JSON.stringify({
|
|
48228
|
-
error: { message: `
|
|
48324
|
+
error: { message: `Proxy error: ${error.message}`, type: "proxy_error" }
|
|
48229
48325
|
})
|
|
48230
48326
|
);
|
|
48231
|
-
}
|
|
48232
|
-
|
|
48233
|
-
|
|
48234
|
-
}
|
|
48235
|
-
if (!req.url?.startsWith("/v1")) {
|
|
48236
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
48237
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
48238
|
-
return;
|
|
48239
|
-
}
|
|
48240
|
-
try {
|
|
48241
|
-
await proxyRequest(
|
|
48242
|
-
req,
|
|
48243
|
-
res,
|
|
48244
|
-
apiBase,
|
|
48245
|
-
payFetch,
|
|
48246
|
-
options,
|
|
48247
|
-
routerOpts,
|
|
48248
|
-
deduplicator,
|
|
48249
|
-
balanceMonitor,
|
|
48250
|
-
sessionStore,
|
|
48251
|
-
responseCache2,
|
|
48252
|
-
sessionJournal
|
|
48253
|
-
);
|
|
48254
|
-
} catch (err) {
|
|
48255
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
48256
|
-
options.onError?.(error);
|
|
48257
|
-
if (!res.headersSent) {
|
|
48258
|
-
res.writeHead(502, { "Content-Type": "application/json" });
|
|
48259
|
-
res.end(
|
|
48260
|
-
JSON.stringify({
|
|
48261
|
-
error: { message: `Proxy error: ${error.message}`, type: "proxy_error" }
|
|
48262
|
-
})
|
|
48263
|
-
);
|
|
48264
|
-
} else if (!res.writableEnded) {
|
|
48265
|
-
res.write(
|
|
48266
|
-
`data: ${JSON.stringify({ error: { message: error.message, type: "proxy_error" } })}
|
|
48327
|
+
} else if (!res.writableEnded) {
|
|
48328
|
+
res.write(
|
|
48329
|
+
`data: ${JSON.stringify({ error: { message: error.message, type: "proxy_error" } })}
|
|
48267
48330
|
|
|
48268
48331
|
`
|
|
48269
|
-
|
|
48270
|
-
|
|
48271
|
-
|
|
48332
|
+
);
|
|
48333
|
+
res.write("data: [DONE]\n\n");
|
|
48334
|
+
res.end();
|
|
48335
|
+
}
|
|
48272
48336
|
}
|
|
48273
|
-
}
|
|
48337
|
+
});
|
|
48274
48338
|
});
|
|
48275
48339
|
const tryListen = (attempt) => {
|
|
48276
48340
|
return new Promise((resolveAttempt, rejectAttempt) => {
|
|
@@ -48784,6 +48848,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
48784
48848
|
responseText = lines.join("\n");
|
|
48785
48849
|
}
|
|
48786
48850
|
console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
|
|
48851
|
+
const imagegenActualCost = paymentStore.getStore()?.amountUsd ?? estimateImageCost(imageModel, imageSize, 1);
|
|
48852
|
+
logUsage({
|
|
48853
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
48854
|
+
model: imageModel,
|
|
48855
|
+
tier: "IMAGE",
|
|
48856
|
+
cost: imagegenActualCost,
|
|
48857
|
+
baselineCost: imagegenActualCost,
|
|
48858
|
+
savings: 0,
|
|
48859
|
+
latencyMs: 0
|
|
48860
|
+
}).catch(() => {
|
|
48861
|
+
});
|
|
48787
48862
|
}
|
|
48788
48863
|
const completionId = `chatcmpl-image-${Date.now()}`;
|
|
48789
48864
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
@@ -48993,6 +49068,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
48993
49068
|
responseText = lines.join("\n");
|
|
48994
49069
|
}
|
|
48995
49070
|
console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
|
|
49071
|
+
const img2imgActualCost2 = paymentStore.getStore()?.amountUsd ?? estimateImageCost(img2imgModel, img2imgSize, 1);
|
|
49072
|
+
logUsage({
|
|
49073
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49074
|
+
model: img2imgModel,
|
|
49075
|
+
tier: "IMAGE",
|
|
49076
|
+
cost: img2imgActualCost2,
|
|
49077
|
+
baselineCost: img2imgActualCost2,
|
|
49078
|
+
savings: 0,
|
|
49079
|
+
latencyMs: 0
|
|
49080
|
+
}).catch(() => {
|
|
49081
|
+
});
|
|
48996
49082
|
}
|
|
48997
49083
|
sendImg2ImgText(responseText);
|
|
48998
49084
|
} catch (err) {
|
|
@@ -50091,22 +50177,44 @@ data: [DONE]
|
|
|
50091
50177
|
}
|
|
50092
50178
|
const logModel = routingDecision?.model ?? modelId;
|
|
50093
50179
|
if (logModel) {
|
|
50094
|
-
const
|
|
50095
|
-
|
|
50096
|
-
|
|
50097
|
-
|
|
50098
|
-
|
|
50099
|
-
|
|
50100
|
-
|
|
50101
|
-
|
|
50102
|
-
|
|
50180
|
+
const actualPayment = paymentStore.getStore()?.amountUsd ?? 0;
|
|
50181
|
+
let logCost;
|
|
50182
|
+
let logBaseline;
|
|
50183
|
+
let logSavings;
|
|
50184
|
+
if (actualPayment > 0) {
|
|
50185
|
+
logCost = actualPayment;
|
|
50186
|
+
const chargedInputTokens = Math.ceil(body.length / 4);
|
|
50187
|
+
const modelDef = BLOCKRUN_MODELS.find((m) => m.id === logModel);
|
|
50188
|
+
const chargedOutputTokens = modelDef ? Math.min(maxTokens, modelDef.maxOutput) : maxTokens;
|
|
50189
|
+
const baseline = calculateModelCost(
|
|
50190
|
+
logModel,
|
|
50191
|
+
routerOpts.modelPricing,
|
|
50192
|
+
chargedInputTokens,
|
|
50193
|
+
chargedOutputTokens,
|
|
50194
|
+
routingProfile ?? void 0
|
|
50195
|
+
);
|
|
50196
|
+
logBaseline = baseline.baselineCost;
|
|
50197
|
+
logSavings = logBaseline > 0 ? Math.max(0, (logBaseline - logCost) / logBaseline) : 0;
|
|
50198
|
+
} else {
|
|
50199
|
+
const chargedInputTokens = Math.ceil(body.length / 4);
|
|
50200
|
+
const costs = calculateModelCost(
|
|
50201
|
+
logModel,
|
|
50202
|
+
routerOpts.modelPricing,
|
|
50203
|
+
chargedInputTokens,
|
|
50204
|
+
maxTokens,
|
|
50205
|
+
routingProfile ?? void 0
|
|
50206
|
+
);
|
|
50207
|
+
logCost = costs.costEstimate;
|
|
50208
|
+
logBaseline = costs.baselineCost;
|
|
50209
|
+
logSavings = costs.savings;
|
|
50210
|
+
}
|
|
50103
50211
|
const entry = {
|
|
50104
50212
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
50105
50213
|
model: logModel,
|
|
50106
50214
|
tier: routingDecision?.tier ?? "DIRECT",
|
|
50107
|
-
cost:
|
|
50108
|
-
baselineCost:
|
|
50109
|
-
savings:
|
|
50215
|
+
cost: logCost,
|
|
50216
|
+
baselineCost: logBaseline,
|
|
50217
|
+
savings: logSavings,
|
|
50110
50218
|
latencyMs: Date.now() - startTime,
|
|
50111
50219
|
...responseInputTokens !== void 0 && { inputTokens: responseInputTokens },
|
|
50112
50220
|
...responseOutputTokens !== void 0 && { outputTokens: responseOutputTokens }
|