glotfile 0.7.1 → 0.7.2
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/dist/server/server.js
CHANGED
|
@@ -3082,6 +3082,67 @@ async function runBatched(reqs, batchSize, callBatch, onBatchComplete, signal, o
|
|
|
3082
3082
|
return results;
|
|
3083
3083
|
}
|
|
3084
3084
|
|
|
3085
|
+
// src/server/ai/pricing.ts
|
|
3086
|
+
function addUsage(into, add) {
|
|
3087
|
+
into.inputTokens += add.inputTokens;
|
|
3088
|
+
into.outputTokens += add.outputTokens;
|
|
3089
|
+
into.cacheCreationInputTokens += add.cacheCreationInputTokens;
|
|
3090
|
+
into.cacheReadInputTokens += add.cacheReadInputTokens;
|
|
3091
|
+
}
|
|
3092
|
+
var BATCH_PRICE_MULTIPLIER = 0.5;
|
|
3093
|
+
var CACHE_WRITE_MULTIPLIER = 1.25;
|
|
3094
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
3095
|
+
function usageCostUsd(usage, ai, multiplier = 1) {
|
|
3096
|
+
if (!usage) return void 0;
|
|
3097
|
+
const pricing = resolvePricing(ai);
|
|
3098
|
+
return pricing ? estimateUsageCostUsd(usage, pricing, multiplier) : void 0;
|
|
3099
|
+
}
|
|
3100
|
+
function estimateUsageCostUsd(usage, pricing, multiplier = 1) {
|
|
3101
|
+
const inputCost = (usage.inputTokens + usage.cacheCreationInputTokens * CACHE_WRITE_MULTIPLIER + usage.cacheReadInputTokens * CACHE_READ_MULTIPLIER) * pricing.inputPerMTok;
|
|
3102
|
+
return (inputCost + usage.outputTokens * pricing.outputPerMTok) / 1e6 * multiplier;
|
|
3103
|
+
}
|
|
3104
|
+
var PRICE_TABLE = [
|
|
3105
|
+
["claude-fable-5", 10, 50],
|
|
3106
|
+
["claude-mythos-5", 10, 50],
|
|
3107
|
+
// Deprecated Opus 4.1 / 4.0 cost 3x the 4.5+ generation — they must outrank
|
|
3108
|
+
// the shorter "claude-opus-4" prefix (4-2025 covers the dated Opus 4 full IDs).
|
|
3109
|
+
["claude-opus-4-1", 15, 75],
|
|
3110
|
+
["claude-opus-4-0", 15, 75],
|
|
3111
|
+
["claude-opus-4-2025", 15, 75],
|
|
3112
|
+
["claude-opus-4", 5, 25],
|
|
3113
|
+
["claude-sonnet-4", 3, 15],
|
|
3114
|
+
["claude-haiku-4", 1, 5],
|
|
3115
|
+
["claude-3-5-haiku", 0.8, 4],
|
|
3116
|
+
["gpt-5.5-pro", 30, 180],
|
|
3117
|
+
["gpt-5.5", 5, 30],
|
|
3118
|
+
["gpt-5.4-pro", 30, 180],
|
|
3119
|
+
["gpt-5.4-mini", 0.75, 4.5],
|
|
3120
|
+
["gpt-5.4-nano", 0.2, 1.25],
|
|
3121
|
+
["gpt-5.4", 2.5, 15],
|
|
3122
|
+
["gpt-5.3-codex", 1.75, 14]
|
|
3123
|
+
];
|
|
3124
|
+
var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
|
|
3125
|
+
function bareModelId(model) {
|
|
3126
|
+
let id = model.trim().toLowerCase();
|
|
3127
|
+
const slash = id.lastIndexOf("/");
|
|
3128
|
+
if (slash !== -1) id = id.slice(slash + 1);
|
|
3129
|
+
const anth = id.lastIndexOf("anthropic.");
|
|
3130
|
+
if (anth !== -1) id = id.slice(anth + "anthropic.".length);
|
|
3131
|
+
return id;
|
|
3132
|
+
}
|
|
3133
|
+
function resolvePricing(ai) {
|
|
3134
|
+
if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
|
|
3135
|
+
return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
|
|
3136
|
+
}
|
|
3137
|
+
if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
|
|
3138
|
+
const id = bareModelId(ai.model);
|
|
3139
|
+
let best;
|
|
3140
|
+
for (const row of PRICE_TABLE) {
|
|
3141
|
+
if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
|
|
3142
|
+
}
|
|
3143
|
+
return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3085
3146
|
// src/server/ai/anthropic.ts
|
|
3086
3147
|
var AnthropicProvider = class {
|
|
3087
3148
|
constructor(config, client) {
|
|
@@ -3097,9 +3158,25 @@ var AnthropicProvider = class {
|
|
|
3097
3158
|
}
|
|
3098
3159
|
config;
|
|
3099
3160
|
client;
|
|
3161
|
+
usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
3100
3162
|
supportsVision() {
|
|
3101
3163
|
return true;
|
|
3102
3164
|
}
|
|
3165
|
+
recordUsage(usage) {
|
|
3166
|
+
if (!usage) return;
|
|
3167
|
+
addUsage(this.usage, {
|
|
3168
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
3169
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
3170
|
+
cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
|
|
3171
|
+
cacheReadInputTokens: usage.cache_read_input_tokens ?? 0
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
takeUsage() {
|
|
3175
|
+
const taken = this.usage;
|
|
3176
|
+
this.usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
3177
|
+
const any = taken.inputTokens || taken.outputTokens || taken.cacheCreationInputTokens || taken.cacheReadInputTokens;
|
|
3178
|
+
return any ? taken : void 0;
|
|
3179
|
+
}
|
|
3103
3180
|
translate(reqs, onBatchComplete, signal, onMalformedReply) {
|
|
3104
3181
|
return runBatched(reqs, this.config.batchSize, (batch, sig) => this.callBatch(batch, sig), onBatchComplete, signal, onMalformedReply);
|
|
3105
3182
|
}
|
|
@@ -3132,6 +3209,7 @@ var AnthropicProvider = class {
|
|
|
3132
3209
|
output_config: { format: { type: "json_schema", schema: req.schema } },
|
|
3133
3210
|
messages: [{ role: "user", content }]
|
|
3134
3211
|
});
|
|
3212
|
+
this.recordUsage(res.usage);
|
|
3135
3213
|
const text = res.content.find((b) => b.type === "text")?.text ?? "{}";
|
|
3136
3214
|
try {
|
|
3137
3215
|
return JSON.parse(text);
|
|
@@ -3173,6 +3251,7 @@ var AnthropicProvider = class {
|
|
|
3173
3251
|
out.set(entry.custom_id, { type: "failed", error: entry.result.error?.message ?? entry.result.type });
|
|
3174
3252
|
continue;
|
|
3175
3253
|
}
|
|
3254
|
+
this.recordUsage(entry.result.message?.usage);
|
|
3176
3255
|
const text = entry.result.message?.content.find((b) => b.type === "text")?.text ?? "";
|
|
3177
3256
|
try {
|
|
3178
3257
|
out.set(entry.custom_id, { type: "items", items: parseReplyItems(text) });
|
|
@@ -3195,6 +3274,7 @@ var AnthropicProvider = class {
|
|
|
3195
3274
|
output_config: { format: { type: "json_schema", schema: BATCH_SCHEMA } },
|
|
3196
3275
|
messages: [{ role: "user", content }]
|
|
3197
3276
|
}, { signal });
|
|
3277
|
+
this.recordUsage(res.usage);
|
|
3198
3278
|
const text = res.content.find((b) => b.type === "text")?.text ?? "";
|
|
3199
3279
|
return parseReplyItems(text);
|
|
3200
3280
|
}
|
|
@@ -3760,6 +3840,24 @@ function clearPendingBatch(projectRoot) {
|
|
|
3760
3840
|
rmSync4(pendingBatchPath(projectRoot), { force: true });
|
|
3761
3841
|
}
|
|
3762
3842
|
|
|
3843
|
+
// src/server/log.ts
|
|
3844
|
+
import { appendFileSync, readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3845
|
+
import { resolve as resolve6 } from "path";
|
|
3846
|
+
function logPath(projectRoot) {
|
|
3847
|
+
return resolve6(projectRoot, ".glotfile", "log.jsonl");
|
|
3848
|
+
}
|
|
3849
|
+
function appendLog(projectRoot, entry) {
|
|
3850
|
+
ensureGlotfileDir(projectRoot);
|
|
3851
|
+
appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
|
|
3852
|
+
}
|
|
3853
|
+
function readLog(projectRoot, limit = 100) {
|
|
3854
|
+
const path = logPath(projectRoot);
|
|
3855
|
+
if (!existsSync9(path)) return [];
|
|
3856
|
+
const lines = readFileSync9(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
3857
|
+
const entries = lines.map((l) => JSON.parse(l));
|
|
3858
|
+
return entries.reverse().slice(0, limit);
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3763
3861
|
// src/server/ai/batch-run.ts
|
|
3764
3862
|
function buildBatchJobs(reqs, batchSize) {
|
|
3765
3863
|
const byLocale = /* @__PURE__ */ new Map();
|
|
@@ -3807,7 +3905,9 @@ async function submitBatchTranslation(state, provider, reqs, batchSize, model, p
|
|
|
3807
3905
|
return pending;
|
|
3808
3906
|
}
|
|
3809
3907
|
async function applyBatchResults(load, persist, provider, pending, projectRoot, ai) {
|
|
3908
|
+
provider.takeUsage?.();
|
|
3810
3909
|
const outcomes = await provider.translationBatchResults(pending.batchId);
|
|
3910
|
+
const batchUsage = provider.takeUsage?.();
|
|
3811
3911
|
const fresh = load();
|
|
3812
3912
|
const isStale = (r) => {
|
|
3813
3913
|
const entry = fresh.keys[r.key];
|
|
@@ -3816,13 +3916,19 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3816
3916
|
const applied = [];
|
|
3817
3917
|
const results = [];
|
|
3818
3918
|
const retryReqs = [];
|
|
3819
|
-
|
|
3919
|
+
const stale = [];
|
|
3920
|
+
const jobFailures = [];
|
|
3820
3921
|
for (const job of pending.jobs) {
|
|
3821
3922
|
const outcome = outcomes.get(job.customId);
|
|
3822
3923
|
const itemsById = outcome?.type === "items" ? new Map(outcome.items.map((i) => [i.id, i])) : null;
|
|
3924
|
+
if (!itemsById) {
|
|
3925
|
+
if (!outcome) jobFailures.push({ customId: job.customId, locale: job.locale, type: "missing" });
|
|
3926
|
+
else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "malformed", raw: outcome.raw });
|
|
3927
|
+
else if (outcome.type === "failed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "failed", error: outcome.error });
|
|
3928
|
+
}
|
|
3823
3929
|
for (const stored of job.requests) {
|
|
3824
3930
|
if (isStale(stored)) {
|
|
3825
|
-
|
|
3931
|
+
stale.push({ key: stored.key, locale: stored.targetLocale });
|
|
3826
3932
|
continue;
|
|
3827
3933
|
}
|
|
3828
3934
|
const { sourceHash: _hash, ...req } = stored;
|
|
@@ -3841,7 +3947,20 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3841
3947
|
const retryResults = await runLocaleParallel(
|
|
3842
3948
|
retryReqs,
|
|
3843
3949
|
provider,
|
|
3844
|
-
{
|
|
3950
|
+
{
|
|
3951
|
+
// Record the raw reply so an unparseable retry response is diagnosable
|
|
3952
|
+
// from the activity log instead of vanishing into per-item errors.
|
|
3953
|
+
onMalformedReply: (raw, batchSize, locale) => {
|
|
3954
|
+
appendLog(projectRoot, {
|
|
3955
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3956
|
+
kind: "translate",
|
|
3957
|
+
summary: `Malformed model reply (${locale}, batch of ${batchSize})`,
|
|
3958
|
+
model: pending.model,
|
|
3959
|
+
locale,
|
|
3960
|
+
raw
|
|
3961
|
+
});
|
|
3962
|
+
}
|
|
3963
|
+
},
|
|
3845
3964
|
ai.concurrency,
|
|
3846
3965
|
void 0,
|
|
3847
3966
|
ai.batchSize
|
|
@@ -3849,53 +3968,34 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
|
3849
3968
|
applied.push(...retryReqs);
|
|
3850
3969
|
results.push(...retryResults);
|
|
3851
3970
|
}
|
|
3971
|
+
const retryUsage = provider.takeUsage?.();
|
|
3972
|
+
const pricing = resolvePricing({ ...ai, model: pending.model });
|
|
3973
|
+
let estimatedCostUsd;
|
|
3974
|
+
if (pricing && (batchUsage || retryUsage)) {
|
|
3975
|
+
estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
|
|
3976
|
+
}
|
|
3977
|
+
let usage;
|
|
3978
|
+
if (batchUsage || retryUsage) {
|
|
3979
|
+
usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
|
|
3980
|
+
if (retryUsage) addUsage(usage, retryUsage);
|
|
3981
|
+
}
|
|
3852
3982
|
const { written, errors } = applyResults(fresh, applied, results);
|
|
3853
3983
|
persist(fresh);
|
|
3854
3984
|
clearPendingBatch(projectRoot);
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
["claude-haiku-4", 1, 5],
|
|
3870
|
-
["claude-3-5-haiku", 0.8, 4],
|
|
3871
|
-
["gpt-5.5-pro", 30, 180],
|
|
3872
|
-
["gpt-5.5", 5, 30],
|
|
3873
|
-
["gpt-5.4-pro", 30, 180],
|
|
3874
|
-
["gpt-5.4-mini", 0.75, 4.5],
|
|
3875
|
-
["gpt-5.4-nano", 0.2, 1.25],
|
|
3876
|
-
["gpt-5.4", 2.5, 15],
|
|
3877
|
-
["gpt-5.3-codex", 1.75, 14]
|
|
3878
|
-
];
|
|
3879
|
-
var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
|
|
3880
|
-
function bareModelId(model) {
|
|
3881
|
-
let id = model.trim().toLowerCase();
|
|
3882
|
-
const slash = id.lastIndexOf("/");
|
|
3883
|
-
if (slash !== -1) id = id.slice(slash + 1);
|
|
3884
|
-
const anth = id.lastIndexOf("anthropic.");
|
|
3885
|
-
if (anth !== -1) id = id.slice(anth + "anthropic.".length);
|
|
3886
|
-
return id;
|
|
3887
|
-
}
|
|
3888
|
-
function resolvePricing(ai) {
|
|
3889
|
-
if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
|
|
3890
|
-
return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
|
|
3891
|
-
}
|
|
3892
|
-
if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
|
|
3893
|
-
const id = bareModelId(ai.model);
|
|
3894
|
-
let best;
|
|
3895
|
-
for (const row of PRICE_TABLE) {
|
|
3896
|
-
if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
|
|
3897
|
-
}
|
|
3898
|
-
return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
|
|
3985
|
+
const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
|
|
3986
|
+
appendLog(projectRoot, {
|
|
3987
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3988
|
+
kind: "translate",
|
|
3989
|
+
summary: `Applied batch ${pending.batchId}: wrote ${written}, ${errors.length} error(s), ${retryReqs.length} retried, ${stale.length} stale${costSuffix}`,
|
|
3990
|
+
model: pending.model,
|
|
3991
|
+
items: applied.map((r) => ({ id: r.id, key: r.key, source: r.source, targetLocale: r.targetLocale })),
|
|
3992
|
+
results,
|
|
3993
|
+
jobFailures: jobFailures.length ? jobFailures : void 0,
|
|
3994
|
+
stale: stale.length ? stale : void 0,
|
|
3995
|
+
usage,
|
|
3996
|
+
estimatedCostUsd
|
|
3997
|
+
});
|
|
3998
|
+
return { written, errors, staleSkipped: stale.length, retried: retryReqs.length, screenshotsSkipped };
|
|
3899
3999
|
}
|
|
3900
4000
|
|
|
3901
4001
|
// src/server/ai/estimate.ts
|
|
@@ -3951,24 +4051,6 @@ function estimateTranslation(state, ai, opts) {
|
|
|
3951
4051
|
};
|
|
3952
4052
|
}
|
|
3953
4053
|
|
|
3954
|
-
// src/server/log.ts
|
|
3955
|
-
import { appendFileSync, readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3956
|
-
import { resolve as resolve6 } from "path";
|
|
3957
|
-
function logPath(projectRoot) {
|
|
3958
|
-
return resolve6(projectRoot, ".glotfile", "log.jsonl");
|
|
3959
|
-
}
|
|
3960
|
-
function appendLog(projectRoot, entry) {
|
|
3961
|
-
ensureGlotfileDir(projectRoot);
|
|
3962
|
-
appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
|
|
3963
|
-
}
|
|
3964
|
-
function readLog(projectRoot, limit = 100) {
|
|
3965
|
-
const path = logPath(projectRoot);
|
|
3966
|
-
if (!existsSync9(path)) return [];
|
|
3967
|
-
const lines = readFileSync9(path, "utf8").split("\n").filter((l) => l.trim() !== "");
|
|
3968
|
-
const entries = lines.map((l) => JSON.parse(l));
|
|
3969
|
-
return entries.reverse().slice(0, limit);
|
|
3970
|
-
}
|
|
3971
|
-
|
|
3972
4054
|
// src/server/import/run.ts
|
|
3973
4055
|
import { relative as relative3 } from "path";
|
|
3974
4056
|
|
|
@@ -6293,6 +6375,7 @@ function createApi(deps) {
|
|
|
6293
6375
|
persist(fresh);
|
|
6294
6376
|
totalWritten += written;
|
|
6295
6377
|
allErrors.push(...errors);
|
|
6378
|
+
const usage = provider.takeUsage?.();
|
|
6296
6379
|
appendLog(projectRoot, {
|
|
6297
6380
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6298
6381
|
kind: "translate",
|
|
@@ -6303,7 +6386,9 @@ function createApi(deps) {
|
|
|
6303
6386
|
const req = reqById.get(r.id);
|
|
6304
6387
|
return { id: r.id, key: req?.key ?? "", source: req?.source ?? "", targetLocale: req?.targetLocale, context: req?.context, glossary: req?.glossary, screenshot: req ? fresh.keys[req.key]?.screenshot : void 0 };
|
|
6305
6388
|
}),
|
|
6306
|
-
results: batchResults
|
|
6389
|
+
results: batchResults,
|
|
6390
|
+
usage,
|
|
6391
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg)
|
|
6307
6392
|
});
|
|
6308
6393
|
const ld = (localeDone.get(locale) ?? 0) + batchResults.length;
|
|
6309
6394
|
localeDone.set(locale, ld);
|
|
@@ -6375,11 +6460,14 @@ function createApi(deps) {
|
|
|
6375
6460
|
}, aiCfg.concurrency, void 0, aiCfg.batchSize);
|
|
6376
6461
|
const latest = load();
|
|
6377
6462
|
({ written, errors } = applyResults(latest, toTranslate, results, void 0, force));
|
|
6463
|
+
const usage = provider.takeUsage?.();
|
|
6378
6464
|
const entry = {
|
|
6379
6465
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6380
6466
|
kind: "translate",
|
|
6381
6467
|
summary: `Translated ${toTranslate.length} item(s)`,
|
|
6382
6468
|
model: aiCfg.model,
|
|
6469
|
+
usage,
|
|
6470
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg),
|
|
6383
6471
|
system: buildSystemPrompt(toTranslate.some((r) => r.plural !== void 0)),
|
|
6384
6472
|
// Log the screenshot PATH only — never the image bytes.
|
|
6385
6473
|
items: toTranslate.map((r) => ({
|
|
@@ -6477,17 +6565,7 @@ function createApi(deps) {
|
|
|
6477
6565
|
if (!supportsBatchTranslate(provider)) {
|
|
6478
6566
|
return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
|
|
6479
6567
|
}
|
|
6480
|
-
const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot,
|
|
6481
|
-
batchSize: aiCfg.batchSize,
|
|
6482
|
-
concurrency: aiCfg.concurrency
|
|
6483
|
-
});
|
|
6484
|
-
appendLog(projectRoot, {
|
|
6485
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6486
|
-
kind: "translate",
|
|
6487
|
-
summary: `Applied batch ${pending.batchId}: wrote ${outcome.written}, ${outcome.retried} retried, ${outcome.staleSkipped} stale`,
|
|
6488
|
-
model: aiCfg.model,
|
|
6489
|
-
results: []
|
|
6490
|
-
});
|
|
6568
|
+
const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
|
|
6491
6569
|
console.log(`[batch] applied ${pending.batchId} \u2014 wrote ${outcome.written}, ${outcome.errors.length} error(s)`);
|
|
6492
6570
|
return c.json(outcome);
|
|
6493
6571
|
}));
|
|
@@ -6638,6 +6716,7 @@ function createApi(deps) {
|
|
|
6638
6716
|
const batch = raw;
|
|
6639
6717
|
const fresh = load();
|
|
6640
6718
|
const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], void 0, body.force === true);
|
|
6719
|
+
const usage = provider.takeUsage?.();
|
|
6641
6720
|
appendLog(projectRoot, {
|
|
6642
6721
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6643
6722
|
kind: "context",
|
|
@@ -6645,7 +6724,9 @@ function createApi(deps) {
|
|
|
6645
6724
|
model: aiCfg.model,
|
|
6646
6725
|
system,
|
|
6647
6726
|
items: chunk2.map((t) => ({ id: t.id, key: t.key, source: t.source })),
|
|
6648
|
-
results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error }))
|
|
6727
|
+
results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error })),
|
|
6728
|
+
usage,
|
|
6729
|
+
estimatedCostUsd: usageCostUsd(usage, aiCfg)
|
|
6649
6730
|
});
|
|
6650
6731
|
persist(fresh);
|
|
6651
6732
|
totalWritten += written;
|