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.
@@ -2216,6 +2216,73 @@ var init_batch = __esm({
2216
2216
  }
2217
2217
  });
2218
2218
 
2219
+ // src/server/ai/pricing.ts
2220
+ function addUsage(into, add) {
2221
+ into.inputTokens += add.inputTokens;
2222
+ into.outputTokens += add.outputTokens;
2223
+ into.cacheCreationInputTokens += add.cacheCreationInputTokens;
2224
+ into.cacheReadInputTokens += add.cacheReadInputTokens;
2225
+ }
2226
+ function usageCostUsd(usage, ai, multiplier = 1) {
2227
+ if (!usage) return void 0;
2228
+ const pricing = resolvePricing(ai);
2229
+ return pricing ? estimateUsageCostUsd(usage, pricing, multiplier) : void 0;
2230
+ }
2231
+ function estimateUsageCostUsd(usage, pricing, multiplier = 1) {
2232
+ const inputCost = (usage.inputTokens + usage.cacheCreationInputTokens * CACHE_WRITE_MULTIPLIER + usage.cacheReadInputTokens * CACHE_READ_MULTIPLIER) * pricing.inputPerMTok;
2233
+ return (inputCost + usage.outputTokens * pricing.outputPerMTok) / 1e6 * multiplier;
2234
+ }
2235
+ function bareModelId(model) {
2236
+ let id = model.trim().toLowerCase();
2237
+ const slash = id.lastIndexOf("/");
2238
+ if (slash !== -1) id = id.slice(slash + 1);
2239
+ const anth = id.lastIndexOf("anthropic.");
2240
+ if (anth !== -1) id = id.slice(anth + "anthropic.".length);
2241
+ return id;
2242
+ }
2243
+ function resolvePricing(ai) {
2244
+ if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
2245
+ return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
2246
+ }
2247
+ if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
2248
+ const id = bareModelId(ai.model);
2249
+ let best;
2250
+ for (const row of PRICE_TABLE) {
2251
+ if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
2252
+ }
2253
+ return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
2254
+ }
2255
+ var BATCH_PRICE_MULTIPLIER, CACHE_WRITE_MULTIPLIER, CACHE_READ_MULTIPLIER, PRICE_TABLE, FREE_PROVIDERS;
2256
+ var init_pricing = __esm({
2257
+ "src/server/ai/pricing.ts"() {
2258
+ "use strict";
2259
+ BATCH_PRICE_MULTIPLIER = 0.5;
2260
+ CACHE_WRITE_MULTIPLIER = 1.25;
2261
+ CACHE_READ_MULTIPLIER = 0.1;
2262
+ PRICE_TABLE = [
2263
+ ["claude-fable-5", 10, 50],
2264
+ ["claude-mythos-5", 10, 50],
2265
+ // Deprecated Opus 4.1 / 4.0 cost 3x the 4.5+ generation — they must outrank
2266
+ // the shorter "claude-opus-4" prefix (4-2025 covers the dated Opus 4 full IDs).
2267
+ ["claude-opus-4-1", 15, 75],
2268
+ ["claude-opus-4-0", 15, 75],
2269
+ ["claude-opus-4-2025", 15, 75],
2270
+ ["claude-opus-4", 5, 25],
2271
+ ["claude-sonnet-4", 3, 15],
2272
+ ["claude-haiku-4", 1, 5],
2273
+ ["claude-3-5-haiku", 0.8, 4],
2274
+ ["gpt-5.5-pro", 30, 180],
2275
+ ["gpt-5.5", 5, 30],
2276
+ ["gpt-5.4-pro", 30, 180],
2277
+ ["gpt-5.4-mini", 0.75, 4.5],
2278
+ ["gpt-5.4-nano", 0.2, 1.25],
2279
+ ["gpt-5.4", 2.5, 15],
2280
+ ["gpt-5.3-codex", 1.75, 14]
2281
+ ];
2282
+ FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
2283
+ }
2284
+ });
2285
+
2219
2286
  // src/server/ai/anthropic.ts
2220
2287
  import Anthropic from "@anthropic-ai/sdk";
2221
2288
  var AnthropicProvider;
@@ -2224,6 +2291,7 @@ var init_anthropic = __esm({
2224
2291
  "use strict";
2225
2292
  init_provider();
2226
2293
  init_batch();
2294
+ init_pricing();
2227
2295
  AnthropicProvider = class {
2228
2296
  constructor(config, client) {
2229
2297
  this.config = config;
@@ -2238,9 +2306,25 @@ var init_anthropic = __esm({
2238
2306
  }
2239
2307
  config;
2240
2308
  client;
2309
+ usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
2241
2310
  supportsVision() {
2242
2311
  return true;
2243
2312
  }
2313
+ recordUsage(usage) {
2314
+ if (!usage) return;
2315
+ addUsage(this.usage, {
2316
+ inputTokens: usage.input_tokens ?? 0,
2317
+ outputTokens: usage.output_tokens ?? 0,
2318
+ cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
2319
+ cacheReadInputTokens: usage.cache_read_input_tokens ?? 0
2320
+ });
2321
+ }
2322
+ takeUsage() {
2323
+ const taken = this.usage;
2324
+ this.usage = { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
2325
+ const any = taken.inputTokens || taken.outputTokens || taken.cacheCreationInputTokens || taken.cacheReadInputTokens;
2326
+ return any ? taken : void 0;
2327
+ }
2244
2328
  translate(reqs, onBatchComplete, signal, onMalformedReply) {
2245
2329
  return runBatched(reqs, this.config.batchSize, (batch, sig) => this.callBatch(batch, sig), onBatchComplete, signal, onMalformedReply);
2246
2330
  }
@@ -2273,6 +2357,7 @@ var init_anthropic = __esm({
2273
2357
  output_config: { format: { type: "json_schema", schema: req.schema } },
2274
2358
  messages: [{ role: "user", content }]
2275
2359
  });
2360
+ this.recordUsage(res.usage);
2276
2361
  const text = res.content.find((b) => b.type === "text")?.text ?? "{}";
2277
2362
  try {
2278
2363
  return JSON.parse(text);
@@ -2314,6 +2399,7 @@ var init_anthropic = __esm({
2314
2399
  out.set(entry.custom_id, { type: "failed", error: entry.result.error?.message ?? entry.result.type });
2315
2400
  continue;
2316
2401
  }
2402
+ this.recordUsage(entry.result.message?.usage);
2317
2403
  const text = entry.result.message?.content.find((b) => b.type === "text")?.text ?? "";
2318
2404
  try {
2319
2405
  out.set(entry.custom_id, { type: "items", items: parseReplyItems(text) });
@@ -2336,6 +2422,7 @@ var init_anthropic = __esm({
2336
2422
  output_config: { format: { type: "json_schema", schema: BATCH_SCHEMA } },
2337
2423
  messages: [{ role: "user", content }]
2338
2424
  }, { signal });
2425
+ this.recordUsage(res.usage);
2339
2426
  const text = res.content.find((b) => b.type === "text")?.text ?? "";
2340
2427
  return parseReplyItems(text);
2341
2428
  }
@@ -3133,6 +3220,30 @@ var init_pending_batch = __esm({
3133
3220
  }
3134
3221
  });
3135
3222
 
3223
+ // src/server/log.ts
3224
+ import { appendFileSync, readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
3225
+ import { resolve as resolve5 } from "path";
3226
+ function logPath(projectRoot) {
3227
+ return resolve5(projectRoot, ".glotfile", "log.jsonl");
3228
+ }
3229
+ function appendLog(projectRoot, entry) {
3230
+ ensureGlotfileDir(projectRoot);
3231
+ appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
3232
+ }
3233
+ function readLog(projectRoot, limit = 100) {
3234
+ const path = logPath(projectRoot);
3235
+ if (!existsSync7(path)) return [];
3236
+ const lines = readFileSync7(path, "utf8").split("\n").filter((l) => l.trim() !== "");
3237
+ const entries = lines.map((l) => JSON.parse(l));
3238
+ return entries.reverse().slice(0, limit);
3239
+ }
3240
+ var init_log = __esm({
3241
+ "src/server/log.ts"() {
3242
+ "use strict";
3243
+ init_glotfile_dir();
3244
+ }
3245
+ });
3246
+
3136
3247
  // src/server/ai/batch-run.ts
3137
3248
  function buildBatchJobs(reqs, batchSize) {
3138
3249
  const byLocale = /* @__PURE__ */ new Map();
@@ -3180,7 +3291,9 @@ async function submitBatchTranslation(state, provider, reqs, batchSize, model, p
3180
3291
  return pending;
3181
3292
  }
3182
3293
  async function applyBatchResults(load, persist, provider, pending, projectRoot, ai) {
3294
+ provider.takeUsage?.();
3183
3295
  const outcomes = await provider.translationBatchResults(pending.batchId);
3296
+ const batchUsage = provider.takeUsage?.();
3184
3297
  const fresh = load();
3185
3298
  const isStale = (r) => {
3186
3299
  const entry = fresh.keys[r.key];
@@ -3189,13 +3302,19 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
3189
3302
  const applied = [];
3190
3303
  const results = [];
3191
3304
  const retryReqs = [];
3192
- let staleSkipped = 0;
3305
+ const stale = [];
3306
+ const jobFailures = [];
3193
3307
  for (const job of pending.jobs) {
3194
3308
  const outcome = outcomes.get(job.customId);
3195
3309
  const itemsById = outcome?.type === "items" ? new Map(outcome.items.map((i) => [i.id, i])) : null;
3310
+ if (!itemsById) {
3311
+ if (!outcome) jobFailures.push({ customId: job.customId, locale: job.locale, type: "missing" });
3312
+ else if (outcome.type === "malformed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "malformed", raw: outcome.raw });
3313
+ else if (outcome.type === "failed") jobFailures.push({ customId: job.customId, locale: job.locale, type: "failed", error: outcome.error });
3314
+ }
3196
3315
  for (const stored of job.requests) {
3197
3316
  if (isStale(stored)) {
3198
- staleSkipped++;
3317
+ stale.push({ key: stored.key, locale: stored.targetLocale });
3199
3318
  continue;
3200
3319
  }
3201
3320
  const { sourceHash: _hash, ...req } = stored;
@@ -3214,7 +3333,20 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
3214
3333
  const retryResults = await runLocaleParallel(
3215
3334
  retryReqs,
3216
3335
  provider,
3217
- {},
3336
+ {
3337
+ // Record the raw reply so an unparseable retry response is diagnosable
3338
+ // from the activity log instead of vanishing into per-item errors.
3339
+ onMalformedReply: (raw, batchSize, locale) => {
3340
+ appendLog(projectRoot, {
3341
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3342
+ kind: "translate",
3343
+ summary: `Malformed model reply (${locale}, batch of ${batchSize})`,
3344
+ model: pending.model,
3345
+ locale,
3346
+ raw
3347
+ });
3348
+ }
3349
+ },
3218
3350
  ai.concurrency,
3219
3351
  void 0,
3220
3352
  ai.batchSize
@@ -3222,10 +3354,34 @@ async function applyBatchResults(load, persist, provider, pending, projectRoot,
3222
3354
  applied.push(...retryReqs);
3223
3355
  results.push(...retryResults);
3224
3356
  }
3357
+ const retryUsage = provider.takeUsage?.();
3358
+ const pricing = resolvePricing({ ...ai, model: pending.model });
3359
+ let estimatedCostUsd;
3360
+ if (pricing && (batchUsage || retryUsage)) {
3361
+ estimatedCostUsd = (batchUsage ? estimateUsageCostUsd(batchUsage, pricing, BATCH_PRICE_MULTIPLIER) : 0) + (retryUsage ? estimateUsageCostUsd(retryUsage, pricing) : 0);
3362
+ }
3363
+ let usage;
3364
+ if (batchUsage || retryUsage) {
3365
+ usage = batchUsage ?? { inputTokens: 0, outputTokens: 0, cacheCreationInputTokens: 0, cacheReadInputTokens: 0 };
3366
+ if (retryUsage) addUsage(usage, retryUsage);
3367
+ }
3225
3368
  const { written, errors } = applyResults(fresh, applied, results);
3226
3369
  persist(fresh);
3227
3370
  clearPendingBatch(projectRoot);
3228
- return { written, errors, staleSkipped, retried: retryReqs.length, screenshotsSkipped };
3371
+ const costSuffix = estimatedCostUsd !== void 0 ? ` (~$${estimatedCostUsd.toFixed(2)})` : "";
3372
+ appendLog(projectRoot, {
3373
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3374
+ kind: "translate",
3375
+ summary: `Applied batch ${pending.batchId}: wrote ${written}, ${errors.length} error(s), ${retryReqs.length} retried, ${stale.length} stale${costSuffix}`,
3376
+ model: pending.model,
3377
+ items: applied.map((r) => ({ id: r.id, key: r.key, source: r.source, targetLocale: r.targetLocale })),
3378
+ results,
3379
+ jobFailures: jobFailures.length ? jobFailures : void 0,
3380
+ stale: stale.length ? stale : void 0,
3381
+ usage,
3382
+ estimatedCostUsd
3383
+ });
3384
+ return { written, errors, staleSkipped: stale.length, retried: retryReqs.length, screenshotsSkipped };
3229
3385
  }
3230
3386
  var init_batch_run = __esm({
3231
3387
  "src/server/ai/batch-run.ts"() {
@@ -3234,55 +3390,8 @@ var init_batch_run = __esm({
3234
3390
  init_batch();
3235
3391
  init_run();
3236
3392
  init_pending_batch();
3237
- }
3238
- });
3239
-
3240
- // src/server/ai/pricing.ts
3241
- function bareModelId(model) {
3242
- let id = model.trim().toLowerCase();
3243
- const slash = id.lastIndexOf("/");
3244
- if (slash !== -1) id = id.slice(slash + 1);
3245
- const anth = id.lastIndexOf("anthropic.");
3246
- if (anth !== -1) id = id.slice(anth + "anthropic.".length);
3247
- return id;
3248
- }
3249
- function resolvePricing(ai) {
3250
- if (ai.inputPricePerMTok !== void 0 && ai.outputPricePerMTok !== void 0) {
3251
- return { source: "profile", inputPerMTok: ai.inputPricePerMTok, outputPerMTok: ai.outputPricePerMTok };
3252
- }
3253
- if (FREE_PROVIDERS.has(ai.provider)) return { source: "builtin", inputPerMTok: 0, outputPerMTok: 0 };
3254
- const id = bareModelId(ai.model);
3255
- let best;
3256
- for (const row of PRICE_TABLE) {
3257
- if (id.startsWith(row[0]) && (!best || row[0].length > best[0].length)) best = row;
3258
- }
3259
- return best ? { source: "builtin", inputPerMTok: best[1], outputPerMTok: best[2] } : null;
3260
- }
3261
- var PRICE_TABLE, FREE_PROVIDERS;
3262
- var init_pricing = __esm({
3263
- "src/server/ai/pricing.ts"() {
3264
- "use strict";
3265
- PRICE_TABLE = [
3266
- ["claude-fable-5", 10, 50],
3267
- ["claude-mythos-5", 10, 50],
3268
- // Deprecated Opus 4.1 / 4.0 cost 3x the 4.5+ generation — they must outrank
3269
- // the shorter "claude-opus-4" prefix (4-2025 covers the dated Opus 4 full IDs).
3270
- ["claude-opus-4-1", 15, 75],
3271
- ["claude-opus-4-0", 15, 75],
3272
- ["claude-opus-4-2025", 15, 75],
3273
- ["claude-opus-4", 5, 25],
3274
- ["claude-sonnet-4", 3, 15],
3275
- ["claude-haiku-4", 1, 5],
3276
- ["claude-3-5-haiku", 0.8, 4],
3277
- ["gpt-5.5-pro", 30, 180],
3278
- ["gpt-5.5", 5, 30],
3279
- ["gpt-5.4-pro", 30, 180],
3280
- ["gpt-5.4-mini", 0.75, 4.5],
3281
- ["gpt-5.4-nano", 0.2, 1.25],
3282
- ["gpt-5.4", 2.5, 15],
3283
- ["gpt-5.3-codex", 1.75, 14]
3284
- ];
3285
- FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "claude-code"]);
3393
+ init_log();
3394
+ init_pricing();
3286
3395
  }
3287
3396
  });
3288
3397
 
@@ -3349,30 +3458,6 @@ var init_estimate = __esm({
3349
3458
  }
3350
3459
  });
3351
3460
 
3352
- // src/server/log.ts
3353
- import { appendFileSync, readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
3354
- import { resolve as resolve5 } from "path";
3355
- function logPath(projectRoot) {
3356
- return resolve5(projectRoot, ".glotfile", "log.jsonl");
3357
- }
3358
- function appendLog(projectRoot, entry) {
3359
- ensureGlotfileDir(projectRoot);
3360
- appendFileSync(logPath(projectRoot), JSON.stringify(entry) + "\n", "utf8");
3361
- }
3362
- function readLog(projectRoot, limit = 100) {
3363
- const path = logPath(projectRoot);
3364
- if (!existsSync7(path)) return [];
3365
- const lines = readFileSync7(path, "utf8").split("\n").filter((l) => l.trim() !== "");
3366
- const entries = lines.map((l) => JSON.parse(l));
3367
- return entries.reverse().slice(0, limit);
3368
- }
3369
- var init_log = __esm({
3370
- "src/server/log.ts"() {
3371
- "use strict";
3372
- init_glotfile_dir();
3373
- }
3374
- });
3375
-
3376
3461
  // src/server/scan.ts
3377
3462
  import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
3378
3463
  import { resolve as resolve6 } from "path";
@@ -6836,6 +6921,7 @@ function createApi(deps) {
6836
6921
  persist(fresh);
6837
6922
  totalWritten += written;
6838
6923
  allErrors.push(...errors);
6924
+ const usage = provider.takeUsage?.();
6839
6925
  appendLog(projectRoot, {
6840
6926
  at: (/* @__PURE__ */ new Date()).toISOString(),
6841
6927
  kind: "translate",
@@ -6846,7 +6932,9 @@ function createApi(deps) {
6846
6932
  const req = reqById.get(r.id);
6847
6933
  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 };
6848
6934
  }),
6849
- results: batchResults
6935
+ results: batchResults,
6936
+ usage,
6937
+ estimatedCostUsd: usageCostUsd(usage, aiCfg)
6850
6938
  });
6851
6939
  const ld = (localeDone.get(locale) ?? 0) + batchResults.length;
6852
6940
  localeDone.set(locale, ld);
@@ -6918,11 +7006,14 @@ function createApi(deps) {
6918
7006
  }, aiCfg.concurrency, void 0, aiCfg.batchSize);
6919
7007
  const latest = load();
6920
7008
  ({ written, errors } = applyResults(latest, toTranslate, results, void 0, force));
7009
+ const usage = provider.takeUsage?.();
6921
7010
  const entry = {
6922
7011
  at: (/* @__PURE__ */ new Date()).toISOString(),
6923
7012
  kind: "translate",
6924
7013
  summary: `Translated ${toTranslate.length} item(s)`,
6925
7014
  model: aiCfg.model,
7015
+ usage,
7016
+ estimatedCostUsd: usageCostUsd(usage, aiCfg),
6926
7017
  system: buildSystemPrompt(toTranslate.some((r) => r.plural !== void 0)),
6927
7018
  // Log the screenshot PATH only — never the image bytes.
6928
7019
  items: toTranslate.map((r) => ({
@@ -7020,17 +7111,7 @@ function createApi(deps) {
7020
7111
  if (!supportsBatchTranslate(provider)) {
7021
7112
  return c.json({ error: `Provider "${aiCfg.provider}" does not support batch mode.` }, 400);
7022
7113
  }
7023
- const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot, {
7024
- batchSize: aiCfg.batchSize,
7025
- concurrency: aiCfg.concurrency
7026
- });
7027
- appendLog(projectRoot, {
7028
- at: (/* @__PURE__ */ new Date()).toISOString(),
7029
- kind: "translate",
7030
- summary: `Applied batch ${pending.batchId}: wrote ${outcome.written}, ${outcome.retried} retried, ${outcome.staleSkipped} stale`,
7031
- model: aiCfg.model,
7032
- results: []
7033
- });
7114
+ const outcome = await applyBatchResults(load, persist, provider, pending, projectRoot, aiCfg);
7034
7115
  console.log(`[batch] applied ${pending.batchId} \u2014 wrote ${outcome.written}, ${outcome.errors.length} error(s)`);
7035
7116
  return c.json(outcome);
7036
7117
  }));
@@ -7181,6 +7262,7 @@ function createApi(deps) {
7181
7262
  const batch = raw;
7182
7263
  const fresh = load();
7183
7264
  const { written, errors } = applyContext(fresh, chunk2, batch.items ?? [], void 0, body.force === true);
7265
+ const usage = provider.takeUsage?.();
7184
7266
  appendLog(projectRoot, {
7185
7267
  at: (/* @__PURE__ */ new Date()).toISOString(),
7186
7268
  kind: "context",
@@ -7188,7 +7270,9 @@ function createApi(deps) {
7188
7270
  model: aiCfg.model,
7189
7271
  system,
7190
7272
  items: chunk2.map((t) => ({ id: t.id, key: t.key, source: t.source })),
7191
- results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error }))
7273
+ results: (batch.items ?? []).map((r) => ({ id: r.id, value: r.context, error: r.error })),
7274
+ usage,
7275
+ estimatedCostUsd: usageCostUsd(usage, aiCfg)
7192
7276
  });
7193
7277
  persist(fresh);
7194
7278
  totalWritten += written;
@@ -7229,6 +7313,7 @@ var init_api = __esm({
7229
7313
  init_batch_run();
7230
7314
  init_pending_batch();
7231
7315
  init_estimate();
7316
+ init_pricing();
7232
7317
  init_log();
7233
7318
  init_schema();
7234
7319
  init_run3();
@@ -7391,6 +7476,7 @@ init_provider();
7391
7476
  init_batch_run();
7392
7477
  init_pending_batch();
7393
7478
  init_estimate();
7479
+ init_pricing();
7394
7480
  init_log();
7395
7481
  init_scan();
7396
7482
  init_scanner();
@@ -7713,11 +7799,14 @@ async function runTranslate(args) {
7713
7799
  if (!batchCallbackFired) {
7714
7800
  ({ written, errors } = applyResults(state, toTranslate, results));
7715
7801
  }
7802
+ const usage = provider.takeUsage?.();
7716
7803
  appendLog(projectRoot, {
7717
7804
  at: (/* @__PURE__ */ new Date()).toISOString(),
7718
7805
  kind: "translate",
7719
7806
  summary: `Translated ${toTranslate.length} item(s)`,
7720
7807
  model: ai.model,
7808
+ usage,
7809
+ estimatedCostUsd: usageCostUsd(usage, ai),
7721
7810
  system: buildSystemPrompt(toTranslate.some((r) => r.plural !== void 0)),
7722
7811
  items: toTranslate.map((r) => ({
7723
7812
  id: r.id,
@@ -7752,7 +7841,7 @@ async function applyPending(args, provider, pending, ai) {
7752
7841
  provider,
7753
7842
  pending,
7754
7843
  projectRoot,
7755
- { batchSize: ai.batchSize, concurrency: ai.concurrency }
7844
+ ai
7756
7845
  );
7757
7846
  reportApply(outcome);
7758
7847
  }